// Keys.cc for Fluxbox - an X11 Window manager
// Copyright (c) 2001-2002 Henrik Kinnunen (fluxgen@linuxmail.org)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//$Id: Keys.cc,v 1.21 2002/12/01 13:41:57 rathnor Exp $


#include "Keys.hh"

#include "StringUtil.hh"

#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif // HAVE_CONFIG_H


#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif	// HAVE_CTYPE_H

#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif	// HAVE_SYS_TYPES_H

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif	// HAVE_SYS_WAIT_H

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif	// HAVE_UNISTD_H

#ifdef	HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif	// HAVE_SYS_STAT_H

#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include <X11/keysym.h>

#include <iostream>
#include <fstream>
#include <vector>
#include <cassert>
#include <memory>

using namespace std;

Keys::t_actionstr Keys::m_actionlist[] = {
    {"Minimize", ICONIFY},
    {"Raise", RAISE},
    {"Lower", LOWER},
    {"Close", CLOSE},
    {"AbortKeychain", ABORTKEYCHAIN},
    {"Workspace", WORKSPACE},
    {"Workspace1", WORKSPACE1},
    {"Workspace2", WORKSPACE2},
    {"Workspace3", WORKSPACE3},
    {"Workspace4", WORKSPACE4},
    {"Workspace5", WORKSPACE5},
    {"Workspace6", WORKSPACE6},
    {"Workspace7", WORKSPACE7},
    {"Workspace8", WORKSPACE8},
    {"Workspace9", WORKSPACE9},
    {"Workspace10", WORKSPACE10},
    {"Workspace11", WORKSPACE11},
    {"Workspace12",  WORKSPACE12},
    {"SendToWorkspace", SENDTOWORKSPACE},
    {"NextWorkspace", NEXTWORKSPACE},
    {"PrevWorkspace", PREVWORKSPACE},
    {"LeftWorkspace", LEFTWORKSPACE},
    {"RightWorkspace", RIGHTWORKSPACE},
    {"KillWindow", KILLWINDOW},
    {"NextWindow", NEXTWINDOW},
    {"PrevWindow", PREVWINDOW},
    {"NextTab", NEXTTAB},
    {"PrevTab", PREVTAB},
    {"FirstTab", FIRSTTAB},
    {"LastTab", LASTTAB},
    {"MoveTabPrev", MOVETABPREV},
    {"MoveTabNext", MOVETABNEXT},
    {"ShadeWindow", SHADE},
    {"MaximizeWindow", MAXIMIZE},
    {"StickWindow", STICK},
    {"ExecCommand", EXECUTE},
    {"MaximizeVertical", VERTMAX},
    {"MaximizeHorizontal", HORIZMAX},
    {"NudgeRight", NUDGERIGHT},
    {"NudgeLeft", NUDGELEFT},
    {"NudgeUp", NUDGEUP},
    {"NudgeDown", NUDGEDOWN},
    {"BigNudgeRight", BIGNUDGERIGHT},
    {"BigNudgeLeft", BIGNUDGELEFT},
    {"BigNudgeUp", BIGNUDGEUP},
    {"BigNudgeDown", BIGNUDGEDOWN},
    {"HorizontalIncrement", HORIZINC},
    {"VerticalIncrement", VERTINC},
    {"HorizontalDecrement", HORIZDEC},
    {"VerticalDecrement", VERTDEC},
    {"ToggleDecor", TOGGLEDECOR},	
    {"ToggleTab", TOGGLETAB}, 
    {"RootMenu", ROOTMENU},
    {0, LASTKEYGRAB}
};	

Keys::Keys(Display *display, const char *filename):
    m_capslock_mod(0),
    m_numlock_mod(0),
    m_scrolllock_mod(0),
    m_abortkey(0),
    m_display(display)
{
    determineModmap();
    assert(display);
    if (filename != 0)
        load(filename);
}

Keys::~Keys() {	
    ungrabKeys();
    deleteTree();
}

//--------- deleteTree -----------
// Destroys the keytree and m_abortkey
//--------------------------------
void Keys::deleteTree() {
    while (!m_keylist.empty()) {
        if (m_keylist.back() && m_keylist.back() != 0)
            delete m_keylist.back();		
        m_keylist.pop_back();
    }
    if (m_abortkey) {
        delete m_abortkey;
        m_abortkey=0;
    }	
}

//-------- ungrabKeys ---------
// Ungrabs the keys
//-----------------------------
void Keys::ungrabKeys() {
    for (int screen=0; screen<ScreenCount(m_display); screen++) {
        XUngrabKey(m_display, AnyKey, AnyModifier,
                   RootWindow(m_display, screen));		
    }
}

//-------------- load ----------------
// Load and grab keys
// Returns true on success else false
// TODO: error checking
//------------------------------------
bool Keys::load(const char *filename) {
    if (!filename)
        return false;
	
    //ungrab all keys
    ungrabKeys();

    //free memory of previous grabs
    deleteTree();

    XSync(m_display, False);
						
    //open the file
    ifstream infile(filename);
    if (!infile)
        return false; // faild to open file

    int line=0;//current line, so we can tell the user where the fault is

    while (!infile.eof()) {
        string linebuffer;

        getline(infile, linebuffer);

        line++;
        vector<string> val;
        //Parse arguments
        StringUtil::stringtok(val, linebuffer.c_str());

        //must have at least 1 argument
        if (val.size() <= 0)
            continue;
			
        if (val[0][0] == '#') //the line is commented
            continue;
		
        unsigned int key=0, mod=0;		
        char keyarg=0;
        t_key *current_key=0, *last_key=0;
		
        for (unsigned int argc=0; argc<val.size(); argc++) {

            if (val[argc][0]!=':') {
                keyarg++;
                if (keyarg==1) //first arg is modifier
                    mod = getModifier(val[argc].c_str());
                else if (keyarg>1) {

                    //keyarg=0;
                    int tmpmod=getModifier(val[argc].c_str());
                    if(tmpmod)
                        mod|=tmpmod; //If it's a modifier
                    else { 
                        key = getKey(val[argc].c_str()); // else get the key
                        if (key == 0) {
                            cerr<<"["<<filename<<"]: Invalid key/modifier on line("<<
                                line<<"): "<<linebuffer<<endl;
                            break; // get next line
                        }
                        if (!current_key) {
                            current_key = new t_key(key, mod);
                            last_key = current_key;
                        } else { 
                            t_key *temp_key = new t_key(key, mod);
                            last_key->keylist.push_back(temp_key);
                            last_key = temp_key;
                        }
                    }
                }			

            } else {
				
                unsigned int i=0;

                for (i=0; i< LASTKEYGRAB; i++) {
                    // +1 on the val[argc] because we dont want to compare the ':'
                    if (strcasecmp(m_actionlist[i].string, val[argc].c_str()+1) == 0)
                        break;	
                }

                if (i < LASTKEYGRAB ) {
                    if (!current_key) {
                        cerr<<"Error on line: "<<line<<endl;
                        cerr<<linebuffer<<endl;
                        delete current_key;
                        current_key = 0;
                        last_key = 0;
                        break; //break out and read next line
                    }

                    //special case for grabAbortKeychain
                    if (m_actionlist[i].action == ABORTKEYCHAIN) {
                        if (last_key!=current_key)
                            cerr<<"Keys: "<<m_actionlist[i].string<<" cant be in chained mode"<<endl;
                        else if (m_abortkey)
                            cerr<<"Keys: "<<m_actionlist[i].string<<" is already bound."<<endl;
                        else
                            m_abortkey = new t_key(current_key->key, current_key->mod, ABORTKEYCHAIN);

                        delete current_key;
                        current_key = 0;
                        last_key = 0;
                        break; //break out and read next line
                    }
					
                    last_key->action = m_actionlist[i].action;
                    switch(last_key->action) {
                    case Keys::EXECUTE:
                        last_key->execcommand = 
                            const_cast<char *>
                            (StringUtil::strcasestr(linebuffer.c_str(),
                                                    getActionStr(Keys::EXECUTE))+
                             strlen(getActionStr(Keys::EXECUTE)));
                        break;
                    case WORKSPACE:
                    case SENDTOWORKSPACE:
                        if (argc + 1 < val.size())
                            last_key->param = atoi( val[argc+1].c_str());
                        else
                            last_key->param = 0;
                        break;
                    case NEXTWINDOW:
                    case PREVWINDOW:
                        if (argc + 1 < val.size())
                            last_key->param = atoi( val[argc+1].c_str());
                        else
                            last_key->param = 0;
                        break;
                    case LEFTWORKSPACE:
                    case RIGHTWORKSPACE:
                    case NEXTWORKSPACE:
                    case PREVWORKSPACE:
                        if (argc + 1 < val.size())
                            last_key->param = atoi( val[argc+1].c_str());
                        else
                            last_key->param = 1;
                        break;
                    case NUDGERIGHT:
                    case NUDGELEFT:
                    case NUDGEUP:
                    case NUDGEDOWN:
                        if (argc + 1 < val.size())
                            last_key->param = atoi( val[argc+1].c_str());
                        else
                            last_key->param = 2;
                        break;
                    default:
                        break;
                    }

                    //add the keychain to list										
                    if (!mergeTree(current_key))
                        cerr<<"Keys: Failed to merge keytree!"<<endl;
				
                    //clear keypointers now that we have them in m_keylist					
                    delete current_key;
                    current_key = 0;
                    last_key = 0;
					
                }	else { //destroy list if no action is found
#ifdef DEBUG
                    cerr<<"Didnt find action="<<val[argc]<<endl;
#endif // DEBUG			
                    //destroy current_key ... this will also destroy the last_key										
                    delete current_key;
                    current_key = 0;
                    last_key = 0;
                }
				
                break; //dont process this linebuffer more
            }	
        }
    }

    return true;
}

//--------- grabKey ---------------------
// Grabs a key with the modifier
// and with numlock,capslock and scrollock
//----------------------------------------
void Keys::grabKey(unsigned int key, unsigned int mod) {

    for (int screen=0; screen<ScreenCount(m_display); screen++) {
		
        Window root = RootWindow(m_display, screen);
		
        XGrabKey(m_display, key, mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);
						
        // Grab with numlock, capslock and scrlock	

        //numlock	
        XGrabKey(m_display, key, mod|m_numlock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);		
        //scrolllock
        XGrabKey(m_display, key, mod|m_scrolllock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);	
        //capslock
        XGrabKey(m_display, key, mod|m_capslock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);
	
        //capslock+numlock
        XGrabKey(m_display, key, mod|m_capslock_mod|m_numlock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);

        //capslock+scrolllock
        XGrabKey(m_display, key, mod|m_capslock_mod|m_scrolllock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);						
	
        //capslock+numlock+scrolllock
        XGrabKey(m_display, key, mod|m_capslock_mod|m_scrolllock_mod|m_numlock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);						

        //numlock+scrollLock
        XGrabKey(m_display, key, mod|m_numlock_mod|m_scrolllock_mod,
                 root, True,
                 GrabModeAsync, GrabModeAsync);
	
    }
			
}

//------------ getModifier ---------------
// Returns the modifier for the modstr
// else zero on failure.
// TODO fix more masks
//----------------------------------------
unsigned int Keys::getModifier(const char *modstr) {
    if (!modstr)
        return 0;
    struct t_modlist{
        char *string;
        unsigned int mask;
        bool operator == (const char *modstr) {
            return  (strcasecmp(string, modstr) == 0 && mask !=0);
        }
    } modlist[] = {
        {"SHIFT", ShiftMask},
        {"CONTROL", ControlMask},
        {"MOD1", Mod1Mask},
        {"MOD2", Mod2Mask},
        {"MOD3", Mod3Mask},
        {"MOD4", Mod4Mask},
        {"MOD5", Mod5Mask},
        {0, 0}
    };

    // find mod mask string
    for (unsigned int i=0; modlist[i].string!=0; i++) {
        if (modlist[i] == modstr)		
            return modlist[i].mask;		
    }
	
    return 0;	
}

//----------- getKey ----------------
// Returns keycode of keystr on success
// else it returns zero
//-----------------------------------
unsigned int Keys::getKey(const char *keystr) {
    if (!keystr)
        return 0;
    return XKeysymToKeycode(m_display,
                            XStringToKeysym(keystr));
}

//--------- getAction -----------------
// returns the KeyAction of the XKeyEvent
//-------------------------------------
Keys::KeyAction Keys::getAction(XKeyEvent *ke) {	
    static t_key *next_key = 0;
    //remove numlock, capslock and scrolllock
    ke->state &= ~Mod2Mask & ~Mod5Mask & ~LockMask;
	
    if (m_abortkey && *m_abortkey==ke) { //abort current keychain
        next_key = 0;		
        return m_abortkey->action;
    }
	
    if (!next_key) {
	
        for (unsigned int i=0; i<m_keylist.size(); i++) {
            if (*m_keylist[i] == ke) {
                if (m_keylist[i]->keylist.size()) {
                    next_key = m_keylist[i];
                    break; //end for-loop 
                } else {
                    if (m_keylist[i]->action == Keys::EXECUTE)
                        m_execcmdstring = m_keylist[i]->execcommand; //update execcmdstring if action is grabExecute
                    m_param = m_keylist[i]->param;
                    return m_keylist[i]->action;
                }
            }
        }
	
    } else { //check the nextkey
        t_key *temp_key = next_key->find(ke);
        if (temp_key) {
            if (temp_key->keylist.size()) {
                next_key = temp_key;								
            } else {
                next_key = 0;
                if (temp_key->action == Keys::EXECUTE)
                    m_execcmdstring = temp_key->execcommand; //update execcmdstring if action is grabExecute
                return temp_key->action;
            }
        }  else  {
            temp_key = next_key;		
            next_key = 0;
            if (temp_key->action == Keys::EXECUTE)
                m_execcmdstring = temp_key->execcommand; //update execcmdstring if action is grabExecute
            return temp_key->action;				
        }
		
    }
	
    return Keys::LASTKEYGRAB;
}

//--------- reconfigure -------------
// deletes the tree and load configuration
// returns true on success else false
//-----------------------------------
bool Keys::reconfigure(const char *filename) {
    deleteTree();
    return load(filename);
}

//------------- getActionStr ------------------
// Tries to find the action for the key
// Returns actionstring on success else
// 0 on failure
//---------------------------------------------
const char *Keys::getActionStr(KeyAction action) {
    for (unsigned int i=0; m_actionlist[i].string!=0 ; i++) {
        if (m_actionlist[i].action == action) 
            return m_actionlist[i].string;
    }
	
    return 0;
}

#ifdef DEBUG
//--------- showTree -----------
// Debug function that show the
// keytree. Starts with the
// rootlist
//------------------------------
void Keys::showTree() {
    for (unsigned int i=0; i<m_keylist.size(); i++) {
        if (m_keylist[i]) {
            cerr<<i<<" ";
            showKeyTree(m_keylist[i]);	
        } else
            cerr<<"Null @ "<<i<<endl;
    }
}

//---------- showKeyTree --------
// Debug function to show t_key tree
//-------------------------------
void Keys::showKeyTree(t_key *key, unsigned int w) {	
    for (unsigned int i=0; i<w+1; i++)
        cerr<<"-";	
    if (!key->keylist.empty()) {
        for (unsigned int i=0; i<key->keylist.size(); i++) {
            cerr<<"( "<<(int)key->key<<" "<<(int)key->mod<<" )";
            showKeyTree(key->keylist[i], 4);		
            cerr<<endl;
        }
    } else
        cerr<<"( "<<(int)key->key<<" "<<(int)key->mod<<" ) {"<<getActionStr(key->action)<<"}"<<endl;
}
#endif //DEBUG

//------------ mergeTree ---------------
// Merges two chains and binds new keys
// Returns true on success else false.
//---------------------------------------
bool Keys::mergeTree(t_key *newtree, t_key *basetree) {
    if (basetree==0) {
        unsigned int baselist_i=0;
        for (; baselist_i<m_keylist.size(); baselist_i++) {
            if (m_keylist[baselist_i]->mod == newtree->mod && 
                m_keylist[baselist_i]->key == newtree->key) {
                if (newtree->keylist.size() && m_keylist[baselist_i]->action == LASTKEYGRAB) {
                    //assumes the newtree only have one branch
                    return mergeTree(newtree->keylist[0], m_keylist[baselist_i]);
                } else
                    break;
            }
        }

        if (baselist_i == m_keylist.size()) {
            grabKey(newtree->key, newtree->mod);
            m_keylist.push_back(new t_key(newtree));			
            if (newtree->keylist.size())
                return mergeTree(newtree->keylist[0], m_keylist.back());
            return true;
        }
		
    } else {
        unsigned int baselist_i = 0;
        for (; baselist_i<basetree->keylist.size(); baselist_i++) {
            if (basetree->keylist[baselist_i]->mod == newtree->mod &&
                basetree->keylist[baselist_i]->key == newtree->key) {
                if (newtree->keylist.size()) {
                    //assumes the newtree only have on branch
                    return mergeTree(newtree->keylist[0], basetree->keylist[baselist_i]);
                } else
                    return false;
            }					
        }
        //if it wasn't in the list grab the key and add it to the list
        if (baselist_i==basetree->keylist.size()) {			
            grabKey(newtree->key, newtree->mod);
            basetree->keylist.push_back(new t_key(newtree));
            if (newtree->keylist.size())
                return mergeTree(newtree->keylist[0], basetree->keylist.back());
            return true;		
        }		
    }
	
    return false;
}

Keys::t_key::t_key(unsigned int key_, unsigned int mod_, KeyAction action_) {
    action = action_;
    key = key_;
    mod = mod_;	
    param = 0;
}

Keys::t_key::t_key(t_key *k) {
    action = k->action;
    key = k->key;
    mod = k->mod;
    execcommand = k->execcommand;
    param = k-> param;
}

Keys::t_key::~t_key() {	
    while (!keylist.empty()) {		
        t_key *k = keylist.back();
        if (k != 0) { // make sure we don't have a bad key pointer
            delete k;
            keylist.pop_back();
        }
    }
}

void Keys::determineModmap() {
    // mask to use for modifier
    int mods[] = {
        ShiftMask,
        LockMask,
        ControlMask,
        Mod1Mask,
        Mod2Mask,
        Mod3Mask,
        Mod4Mask,
        Mod5Mask,
        0
    };
	
    XModifierKeymap *map = XGetModifierMapping(m_display);
    // find modifiers and set them
    for (int i=0, realkey=0; i<8; ++i) {
        for (int key=0; key<map->max_keypermod; ++key, ++realkey) {

            if (map->modifiermap[realkey] == 0)
                continue;

            KeySym ks = XKeycodeToKeysym(m_display, map->modifiermap[realkey], 0);

            switch (ks) {
            case XK_Caps_Lock:
                m_capslock_mod = mods[i];
                break;
            case XK_Scroll_Lock:
                m_scrolllock_mod = mods[i];
                break;
            case XK_Num_Lock:
                m_numlock_mod = mods[i];
                break;
            }
        }
    }
    XFreeModifiermap(map);
}