
#include <stdio.h>
#include <ctype.h>
#include "nawm.h"
#include "bindings.h"
#include "cmd.h"

KeySym *keyboard_mapping = NULL;
int keysyms_per_keycode;
int whatmode = DEFAULT_MODE;

/*
 * string_to_keycode --- look up keysym (in string form) in Xlation
 * table, return keycode and modifiers.  returns 1 if found.  Keysyms
 * can also be in the form #nnn, where nnn is a keycode.
 */

int string_to_keycode (sym_name, code, modifier) 
    char *sym_name;			/* KeySym name */
    KeyCode *code;			/* Return */
    int *modifier;			/* Return */
{
    KeySym sym;
    KeyCode n;
    int m, q = -1;
    extern int min_keycode, max_keycode;
    
    if ((sym = XStringToKeysym(sym_name)) == NoSymbol)
	if (*sym_name == '#' && isdigit(sym_name[1]))
	    sym = (KeySym) atoi(sym_name+1);
	else
	    return 0;
    for (n = min_keycode; n <= max_keycode; n++)
	for (m = 0; m < keysyms_per_keycode; m++)
	    if (keyboard_mapping[++q] == sym) {	
		*code = n;
		*modifier = m;
		return 1;
	    }
    return 0;
}

KeySym keycode_to_keysym (keycode)
     int keycode;
{
    return (keyboard_mapping[(keycode - min_keycode) * keysyms_per_keycode]);
}



skip_ws(cp)
    char **cp;
{
    while (isspace(**cp)) (*cp)++;
}

char *get_id(cp)
    char **cp;
{
    char *ret;
    
    skip_ws(cp);
    ret = *cp;
    if (**cp == '"') {
	 ret++;
	 do (*cp)++; while (**cp &&  (**cp != '"'));
    } else {
	 while (**cp && !isspace(**cp)) (*cp)++;
    }
    /* invariant here: (**cp) is WS or NUL or '"' */
    /* if it's non-NUL terminate the string and move on,
       otherwise leave the pointer on the NUL */
    if (**cp) *(*cp)++ = 0;
    return ret;
}


int get_int (cp)
    char **cp;
{
    char *ptr;
    ptr = get_id(cp);
    /* [gsstark:19951115.1905EST] handle "-screenwidth", "- screenwidth", etc*/
    /* [gsstark:19960127.0752EST] it would be nice if this also handled 
                                  things like -x -x_mark etc. */
    if (*ptr=='-')
      if (*++ptr) 
	return(-get_int(&ptr));
      else
	return(-get_int(cp));
    else if(!strcasecmp(ptr, "screenwidth")) 
        return DisplayWidth(dpy, 0);
    else if(!strcasecmp(ptr, "screenheight")) 
	return DisplayHeight(dpy, 0);
    else if(!strcasecmp(ptr, "xctr")) 
	return DisplayWidth(dpy, 0)/2;
    else if(!strcasecmp(ptr, "yctr")) 
	return DisplayHeight(dpy, 0)/2;
    else if(!strcasecmp(ptr, "x"))
	return USE_X;
    else if(!strcasecmp(ptr, "y"))
	return USE_Y;
    else if(!strcasecmp(ptr, "x_mark"))
	return USE_MARKERX;
    else if(!strcasecmp(ptr, "y_mark"))
	return USE_MARKERY;
    else if(!strcasecmp(ptr, "dx_mark"))
	return USE_DXMARKER;
    else if(!strcasecmp(ptr, "dy_mark"))
	return USE_DYMARKER;
    else return atoi(ptr);
}


read_config_file(f)
    FILE *f;
{
#define BUFCHUNK 100
    int bufmax=BUFCHUNK;
    char *buf=malloc(bufmax+1), *c, *cmd;

    if (!keyboard_mapping)
	keyboard_mapping = XGetKeyboardMapping(dpy,
					       min_keycode,
					       max_keycode - min_keycode + 1,
					       &keysyms_per_keycode);
    while(fgets(buf,bufmax+1,f)) {
	while (!(c=strchr(buf,'\n')) || (*(c-1)=='\\' && ((*(c-1)) = (*c = ' ')))) {
	    buf = (char *)realloc(buf,(bufmax+=BUFCHUNK)+1);
	    if (! fgets(strchr(buf,'\0'),BUFCHUNK+1,f)) break;
	}
	if (c = strchr(buf, '#')) 
	    *c = 0;
	c = buf;
	cmd = get_id(&c);
	if (*cmd) 
	    if (!strcmp(cmd, "mode"))
		whatmode = get_int(&c);
	    else if (!strcmp(cmd, "anymode"))
		whatmode = ANY_MODE;
	    else if (!strcmp(cmd, "endmode"))
		whatmode = DEFAULT_MODE;
	    else
		if (!strcmp(cmd, "key"))
		    parse_key_command (c);
		else if (!strcmp(cmd, "button"))
		    parse_mouse_command(c);
		else if (!strcmp(cmd, "motion")) 
		    parse_motion_command(c);
		else if (!strcmp(cmd, "init"))
		    do_init_command(c);
		else {
		    fputs (cmd, stdout);
		    puts (" not understood");
		    exit(1);
		}
    }
    free(buf);
    fclose(f);
}

read_nawmrc()
{
    char buf[300];
    FILE *f;
    
    strcpy (buf, getenv("HOME"));
    strcat (buf, "/.nawmrc");
    if (!(f = fopen(buf, "r"))) {
	perror(buf);
	exit(1);
    }
    read_config_file(f);
}

BindInfo parse_bind_info (cp, bstorage)
    char **cp;
    BindInfo bstorage;			/* NULL to use main binding list */
{
    BindInfo b;
    char *cmd = get_id(cp);
    char *name;
    char *word;
    int state;
    
    if (!*cmd) return NULL;
    b = bstorage ? bstorage : (BindInfo) realloc_binding();
    b->whatmode = whatmode;
    if (!strcmp(cmd, "warptowindow")) {
	b->func = warp_to_window_cmd;
    } else if (!strcmp(cmd, "iconify")) {
	b->func = iconify_cmd;
    } else if (!strcmp(cmd, "deiconify")) {
	b->func = deiconify_cmd;
    } else if (!strcmp(cmd, "map")) {
	b->func = map_cmd;
    } else if (!strcmp(cmd, "unmap")) {
	b->func = unmap_cmd;
    } else if (!strcmp(cmd, "find")) {
	b->func = find_cmd;
	name = get_id(cp);
	malloc_strcpy(b->data.find.win_name, name);
    } else if (!strcmp(cmd, "warp")) {
	b->func = warp_cmd;
	b->data.warp.dx = get_int(cp);
	b->data.warp.dy = get_int(cp);
    } else if (!strcmp(cmd, "warpto")) {
	b->func = warp_to_cmd;
	b->data.warpto.x = get_int(cp);
	b->data.warpto.y = get_int(cp);
    } else if (!strcmp(cmd, "move")) {
	b->func = move_cmd;
	b->data.move.dx = get_int(cp);
	b->data.move.dy = get_int(cp);
    } else if (!strcmp(cmd, "moveto")) {
	b->func = move_to_cmd;
	b->data.moveto.x = get_int(cp);
	b->data.moveto.y = get_int(cp);
    } else if (!strcmp(cmd, "screen")) {
	b->func = screen_cmd;
	b->data.screen.dx = get_int(cp);
	b->data.screen.dy = get_int(cp);
    } else if (!strcmp(cmd, "screento")) {
	b->func = screen_to_cmd;
	b->data.screento.x = get_int(cp);
	b->data.screento.y = get_int(cp);
    } else if (!strcmp(cmd, "carry")) {
	b->func = carry_cmd;
	b->data.carry.dx = get_int(cp);
	b->data.carry.dy = get_int(cp);
    } else if (!strcmp(cmd, "carryto")) {
	b->func = carry_to_cmd;
	b->data.carryto.x = get_int(cp);
	b->data.carryto.y = get_int(cp);
    } else if (!strcmp(cmd, "size")) {
	b->func = size_cmd;
	b->data.size.dx = get_int(cp);
	b->data.size.dy = get_int(cp);
    } else if (!strcmp(cmd, "sizeto")) {
	b->func = size_to_cmd;
	b->data.sizeto.x = get_int(cp);
	b->data.sizeto.y = get_int(cp);
    } else if (!strcmp(cmd, "lower")) {
	b->func = lower_cmd;
    } else if (!strcmp(cmd, "raise")) {
	b->func = raise_cmd;
    } else if (!strcmp(cmd, "prev")) {
	b->func = prev_cmd;
    } else if (!strcmp(cmd, "next")) {
	b->func = next_cmd;
    } else if (!strcmp(cmd, "bell")) {
	b->func = bell_cmd;
    } else if (!strcmp(cmd, "kill")) {
	b->func = kill_cmd;
    } else if (!strcmp(cmd, "bind")) {
	b->func = bind_cmd;
    } else if (!strcmp(cmd, "unbind")) {
	b->func = unbind_cmd;
    } else if (!strcmp(cmd, "grab")) {
	b->func = grab_pointer_cmd;
    } else if (!strcmp(cmd, "ungrab")) {
	b->func = ungrab_pointer_cmd;
    } else if (!strcmp(cmd, "setmarker")) {
	b->func = set_marker_cmd;
    } else if (!strcmp(cmd, "exchmark")) {
	b->func = exch_mark_cmd;
    } else if (!strcmp(cmd, "setx")) {
	b->func = set_x_cmd;
	b->data.setx.x = get_int(cp);
    } else if (!strcmp(cmd, "sety")) {
	b->func = set_y_cmd;
	b->data.sety.y = get_int(cp);
    } else if (!strcmp(cmd, "setmode")) {
	b->func = set_mode_cmd;
	b->data.setmode.mode = get_int(cp);
    } else if (!strcmp(cmd, "endmode")) {
	b->func = set_mode_cmd;
	b->data.setmode.mode = DEFAULT_MODE;
    } else if (!strcmp(cmd, "mouseclick")) {
	b->func = mouseclick_cmd;
	parse_modifiers(cp, &word, &state);
	b->data.mouseclick.modifiers = state;
	b->data.mouseclick.button = get_int(&word);
	b->data.mouseclick.x = get_int(cp);
	if ((b->data.mouseclick.x < 0) &&
	    (b->data.mouseclick.x >= -DisplayWidth(dpy, 0)))
	    b->data.mouseclick.x += DisplayWidth(dpy, 0);
	b->data.mouseclick.y = get_int(cp);
	if ((b->data.mouseclick.y < 0) &&
	    (b->data.mouseclick.x >= -DisplayHeight(dpy, 0)))
	  b->data.mouseclick.y += DisplayHeight(dpy, 0);
    } else if (!strcmp(cmd, "break")) {
        b->func = break_cmd;
    } else if (!strcmp(cmd, "system")) {
	b->func = system_cmd;
	name = get_id(cp);
	malloc_strcpy(b->data.system.cmd, name);
    } else if (!strcmp(cmd, "exit")) {
        b->func = exit_cmd;
    } else {
	fputs (cmd, stderr);
	fputs (": command for binding unknown\n", stderr);
	exit(1);
    }
    return b;
}

parse_modifiers (cp, nextword, state)
    char **cp;
    char **nextword;			/* return */
    unsigned int *state;		/* return */
{
    char *word;
    
    *state = 0;
    do {
	word = get_id(cp);
	if (!strcasecmp(word, "shift"))
	    *state |= ShiftMask;
	else if (!strcasecmp(word, "control"))
	    *state |= ControlMask;
	else if (!strcasecmp(word, "meta"))
	    *state |= Mod1Mask;
	else if (!strcasecmp(word, "mod2"))
	    *state |= Mod2Mask;
	else if (!strcasecmp(word, "mod3"))
	    *state |= Mod3Mask;
	else if (!strcasecmp(word, "mod4"))
	    *state |= Mod4Mask;
	else if (!strcasecmp(word, "mod5"))
	    *state |= Mod5Mask;
	else if (!strcasecmp(word, "any"))
	    *state = AnyModifier;
	else
	    break;
    } while (1);
    *nextword = word;
}

parse_key_command (c)
    char *c;
{
    KeyCode code;
    unsigned int state;
    int entryno;
    char *word;
    BindInfo b;
    
    parse_modifiers (&c, &word, &state);
    if (!(string_to_keycode(word, &code, &entryno))) {    
	fputs (word, stderr);
	fputs (": key name (keysym) unknown\n", stderr);
	exit(1);
    }
    while (b = parse_bind_info(&c, NULL)) 
	add_key_binding (code, state, b);
}

parse_mouse_command (c)
    char *c;
{
    BindInfo b;
    unsigned int state;
    unsigned int button = 0;
    char *word;
    
    parse_modifiers (&c, &word, &state);
    if (!strcasecmp(word, "left"))
	button = Button1;
    else if (!strcasecmp(word, "middle"))
	button = Button2;
    else if (!strcasecmp(word, "right"))
	button = Button3;
    else if (word[0] == '#')
	button = atoi(word[1]);
    else {
	fputs (word, stderr);
	fputs (": mouse button unknown\n", stderr);
	exit(1);
    }
    while (b = parse_bind_info(&c, NULL)) 
	add_button_binding (button, state, b);
}

parse_motion_command(c)
    char* c;
{
    BindInfo b;
    
    while (b = parse_bind_info(&c, NULL))
	add_motion_binding(AnyModifier, b);
}

do_init_command(c)
    char *c;
{
    BindInfo_S bind;
    
    while (parse_bind_info(&c, &bind))
	do_binding(&bind, NULL);
}    
