/* keys.c -- Key binding and evaluating (this should be called events.c)
   $Id: keys.c,v 1.88 2004/11/15 18:29:19 jsh Exp $

   Copyright (C) 1999 John Harper <john@dcs.warwick.ac.uk>

   This file is part of sawmill.

   sawmill 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, or (at your option)
   any later version.

   sawmill 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with sawmill; see the file COPYING.   If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "sawmill.h"
#include "keys.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>

/* max number of milliseconds between successive-clicks */
#define DEFAULT_DOUBLE_CLICK_TIME 250

/* current_event holds the event we're processing (or 0s), last_event
   contains the previously processed event.  */
static u_long current_event[2], last_event[2];

/* print_prefix means echo all events upto the end of the key-sequence.
   printed_this_prefix says the last event has been echoed. */
static bool print_prefix, printed_this_prefix;

/* Buffer holding the events making this key-sequence. */
#define EVENT_BUFSIZ 20
static u_long event_buf[EVENT_BUFSIZ]; /* one event = (code,mods) */
static int event_index;

/* Data for testing double-clicks */
static Time last_click;
static u_long last_click_button;
static int click_count;

/* These control which types of keyboard events we actually evaluate */
DEFSYM(eval_modifier_events, "eval-modifier-events");
DEFSYM(eval_key_release_events, "eval-key-release-events");

DEFSYM(global_keymap, "global-keymap");
DEFSYM(root_window_keymap, "root-window-keymap");
DEFSYM(override_keymap, "override-keymap");
DEFSYM(unbound_key_hook, "unbound-key-hook");
DEFSYM(keymap, "keymap");

DEFSYM(this_command, "this-command");
DEFSYM(last_command, "last-command");
DEFSYM(prefix_arg, "prefix-arg");
DEFSYM(current_prefix_arg, "current-prefix-arg");

DEFSYM(async_pointer, "async-pointer");
DEFSYM(async_keyboard, "async-keyboard");
DEFSYM(sync_pointer, "sync-pointer");
DEFSYM(sync_keyboard, "sync-keyboard");
DEFSYM(replay_pointer, "replay-pointer");
DEFSYM(replay_keyboard, "replay-keyboard");
DEFSYM(sync_both, "sync-both");
DEFSYM(async_both, "async-both");

DEFSYM(display_message, "display-message");
DEFSYM(call_command, "call-command");

static repv next_keymap_path;

DEFSYM(multi_click_delay, "multi-click-delay");

/* The X modifiers being used for Meta, Alt, and Hyper */
static u_long meta_mod, alt_mod, hyper_mod, super_mod;

/* The user-customizable modifier; used for default key bindings. This
   shouldn't include any bits that don't have a fixed meaning. */
static u_long wm_mod = EV_MOD_META;

/* The X modifiers bound to the Num_Lock and Scroll_Lock keysyms */
static u_long num_lock_mod, scroll_lock_mod;

DEFSYM(meta_keysyms, "meta-keysyms");
DEFSYM(alt_keysyms, "alt-keysyms");
DEFSYM(hyper_keysyms, "hyper-keysyms");
DEFSYM(super_keysyms, "super-keysyms");

static void grab_keymap_event (repv km, long code, long mods, bool grab);
static void grab_all_keylist_events (repv map, bool grab);

static int all_buttons[9] = { Button1, Button2, Button3, Button4, Button5, Button6, Button7, Button8, Button9 };

/* locks: currently LockMask, num_lock, and scroll_lock */
static int total_lock_combs, all_lock_mask;
static int all_lock_combs[2*2*2];


/* Translate from X events to Lisp events */

static u_long
direct_modifiers (u_long mods)
{
    /* Do this first, since it may contain other indirect mods */
    if (wm_mod != 0 && (mods & EV_MOD_WM))
	mods = (mods & ~EV_MOD_WM) | wm_mod;

    if (meta_mod != 0 && (mods & EV_MOD_META))
	mods = (mods & ~EV_MOD_META) | meta_mod;
    if (alt_mod != 0 && (mods & EV_MOD_ALT))
	mods = (mods & ~EV_MOD_ALT) | alt_mod;
    if (hyper_mod != 0 && (mods & EV_MOD_HYPER))
	mods = (mods & ~EV_MOD_HYPER) | hyper_mod;
    if (super_mod != 0 && (mods & EV_MOD_SUPER))
	mods = (mods & ~EV_MOD_SUPER) | super_mod;

    return mods;
}

static u_long
indirect_modifiers (u_long mods)
{
    if(mods & meta_mod)
	mods = (mods & ~meta_mod) | EV_MOD_META;
    if(mods & alt_mod)
	mods = (mods & ~alt_mod) | EV_MOD_ALT;
    if(mods & hyper_mod)
	mods = (mods & ~hyper_mod) | EV_MOD_HYPER;
    if(mods & super_mod)
	mods = (mods & ~super_mod) | EV_MOD_SUPER;

    return mods;
}

/* Translate the X key or button event XEV to *CODE and *MODS */
static bool
translate_event(u_long *code, u_long *mods, XEvent *xev)
{
    repv multi_click_delay;
    int delay;

    bool ret = FALSE;
    switch(xev->type)
    {
    case KeyRelease:
	if (global_symbol_value (Qeval_key_release_events) == Qnil)
	    break;
	/* FALL THROUGH */

    case KeyPress:
	*mods = xev->xkey.state & ~all_lock_mask;
	if (xev->type == KeyRelease)
	    *mods |= EV_MOD_RELEASE;
	if(*mods & ShiftMask)
	{
	    KeySym normal, shifted;
	    normal = XKeycodeToKeysym (dpy, xev->xkey.keycode, 0);
	    shifted = XKeycodeToKeysym (dpy, xev->xkey.keycode, 1);

	    /* Some keys don't have keysym at index 1, if not treat it as
	       normal keysym shifted.

	       But if the keysym at index 1 is the same as that in index
	       0, then preserve the shift modifier */

	    if (shifted == NoSymbol)
		*code = normal;
	    else
	    {
		*code = shifted;
		if (shifted != normal)
		    *mods &= ~ShiftMask;
	    }
	}
	else
	    *code = XKeycodeToKeysym(xev->xany.display, xev->xkey.keycode, 0);
	if(*code != NoSymbol
	   && (!IsModifierKey (*code)
	       || global_symbol_value (Qeval_modifier_events) == Qt))
	{
	    *mods |= EV_TYPE_KEY;
	    ret = TRUE;
	}
	break;

    case ButtonPress:

	if(xev->xbutton.button == last_click_button)
        {
            multi_click_delay = global_symbol_value (Qmulti_click_delay);
            if (rep_INTP(multi_click_delay))
                delay = rep_INT(multi_click_delay);
            else
                delay = DEFAULT_DOUBLE_CLICK_TIME;
  
            if (xev->xbutton.time < (last_click + delay))
            {
                click_count++;
            }
            else
                click_count = 1;
        }
	else
	    click_count = 1;

	switch (click_count)
	{
	case 2:
	    *code = EV_CODE_MOUSE_CLICK2;
	    break;

	case 3:
	    *code = EV_CODE_MOUSE_CLICK3;
	    break;

	default:
	    *code = EV_CODE_MOUSE_CLICK1;
	}
	last_click = xev->xbutton.time;
	last_click_button = xev->xbutton.button;
	goto button;

    case ButtonRelease:
	switch (click_count)
	{
	case 2:
	    *code = EV_CODE_MOUSE_UP2;
	    break;

	case 3:
	    *code = EV_CODE_MOUSE_UP3;
	    break;

	default:
	    *code = EV_CODE_MOUSE_UP1;
	}

    button:
	*mods = xev->xbutton.state & ~all_lock_mask;
	*mods |= EV_TYPE_MOUSE;
	switch(xev->xbutton.button)
	{
	case Button1:
	    *mods |= Button1Mask;
	    break;
	case Button2:
	    *mods |= Button2Mask;
	    break;
	case Button3:
	    *mods |= Button3Mask;
	    break;
	case Button4:
	    *mods |= Button4Mask;
	    break;
	case Button5:
	    *mods |= Button5Mask;
	    break;
	case Button6:
	    *mods |= Button6Mask;
	    break;
	case Button7:
	    *mods |= Button7Mask;
	    break;
	case Button8:
	    *mods |= Button8Mask;
	    break;
	case Button9:
	    *mods |= Button9Mask;
	    break;
	}
	ret = TRUE;
	break;

    case MotionNotify:
	*code = EV_CODE_MOUSE_MOVE;
	*mods = xev->xmotion.state & ~all_lock_mask;
	*mods |= EV_TYPE_MOUSE;
	ret = TRUE;
	break;
    }

    if (ret)
	*mods = indirect_modifiers (*mods);

    return ret;
}

/* Translate the Lisp key event EV to X keycode *KEYCODE and modifier
   mask *STATE, returning true if successful. */
static bool
translate_event_to_x_key (repv ev, u_int *keycode, u_int *state)
{
    if (rep_INT(EVENT_MODS(ev)) & EV_TYPE_KEY)
    {
	u_int s = rep_INT(EVENT_MODS(ev)) & EV_MOD_MASK;
	KeySym sym = rep_INT(EVENT_CODE(ev));
	KeySym normal, shifted;
	u_int k = XKeysymToKeycode (dpy, sym);

	if (k == 0)
	    return FALSE;

	s = direct_modifiers (s);

	/* Check if we need a shift modifier */
	normal = XKeycodeToKeysym (dpy, k, 0);
	shifted = XKeycodeToKeysym (dpy, k, 1);
	if (sym != normal && sym == shifted)
	    s |= ShiftMask;

	if (s & EV_MOD_RELEASE)
	    s &= ~EV_MOD_RELEASE;
	if (s == EV_MOD_ANY)
	    s = AnyModifier;

	if ((s & EV_VIRT_MOD_MASK) != 0)
	    return FALSE;

	*state = s;
	*keycode = k;
	return TRUE;
    }
    else
	return FALSE;
}

/* Translate the Lisp button event EV to X button identifier *BUTTON and
   modifier mask *STATE, returning true if successful. */
static u_int
translate_event_to_x_button (repv ev, u_int *button, u_int *state)
{
    if (rep_INT(EVENT_MODS(ev)) & EV_TYPE_MOUSE)
    {
	u_long mods = rep_INT(EVENT_MODS(ev));
	static struct { u_int button; u_int mask; } buttons[] = {
	    { Button1, Button1Mask },
	    { Button2, Button2Mask },
	    { Button3, Button3Mask },
	    { Button4, Button4Mask },
	    { Button5, Button5Mask },
	    { Button6, Button6Mask },
	    { Button7, Button7Mask },
	    { Button8, Button8Mask },
	    { Button9, Button9Mask },
	    { 0, 0 }
	};
	int i;

	for (i = 0; buttons[i].button != 0; i++)
	{
	    if (mods & buttons[i].mask)
	    {
		u_int s;
		mods &= ~buttons[i].mask;
		s = direct_modifiers (mods & EV_MOD_MASK);
		if (s == EV_MOD_ANY)
		    s = AnyModifier;
		if ((s & EV_VIRT_MOD_MASK) == 0)
		{
		    *button = buttons[i].button;
		    *state = s;
		    return buttons[i].mask;
		}
	    }
	}
	/* no actual button specified. if mod_any is set, then just
	   return this; hope the caller does the Right Thing */
	if (mods & EV_MOD_ANY)
	{
	    *state = AnyModifier;
	    *button = 0;		/* anything.. */
	    return 1;
	}
    }
    return 0;
}


/* Keymap searching */

static inline bool
compare_events (u_long code1, u_long mods1, u_long code2, u_long mods2)
{
    return (code1 == code2
	    && ((mods1 == mods2)
		|| ((mods2 & wm_mod) == wm_mod
		    && ((mods2 & ~wm_mod) | EV_MOD_WM) == mods1)
		|| (((mods1 & EV_MOD_MASK) & EV_MOD_ANY)
		    /* this allows things like Any-C-x, mapping to C-x
		       _plus_ any other modifiers */
		    && (((mods1 & ~EV_MOD_ANY) & mods2)
			== (mods1 & ~EV_MOD_ANY)))));
}

/* Search the keymap KM for a binding of CODE&MODS.
   If CALLBACK is non-nil it's a function to call for the binding found.
   If this function returns true, then this binding is acceptable and
   is returned from the function. */
static repv
search_keymap(repv km, u_long code, u_long mods, bool (*callback)(repv key))
{
    /* If it's a symbol, dereference it. */
    while(rep_SYMBOLP(km) && !rep_NILP(km) && !rep_INTERRUPTP)
    {
	repv tem = Fsymbol_value(km, Qt);
	if(tem == km)
	    break;
	km = tem;
	rep_TEST_INT;
    }

    /* Find the list of bindings to scan. */
    if rep_CONSP(km)
	km = rep_CDR(km);
    else
	return rep_NULL;

    /* Scan them for a match.. */
    while(rep_CONSP(km))
    {
	rep_TEST_INT; if(rep_INTERRUPTP) break;
	if(rep_CONSP(rep_CAR(km)))
	{
	    repv ev = KEY_EVENT(rep_CAR(km));
	    if(compare_events (rep_INT(EVENT_CODE(ev)),
			       rep_INT(EVENT_MODS(ev)),
			       code, mods))
	    {
		repv key = rep_CAR(km);
		if(callback == 0 || callback(key))
		    return key;
	    }
	    km = rep_CDR(km);
	}
	else
	    /* An inherited sub-keymap. Start scanning it */
	    km = rep_CDR(km);
    }
    return rep_NULL;
}

/* Search for a binding of CODE&MODS.  */
static repv
lookup_binding(u_long code, u_long mods, bool (*callback)(repv key),
	       repv context_keymap, Lisp_Window *current_window)
{
    repv k = rep_NULL, nkp = next_keymap_path;
    next_keymap_path = rep_NULL;

    if(nkp == rep_NULL || nkp == Qglobal_keymap)
    {
	repv tem = global_symbol_value (Qoverride_keymap);
	if (tem != Qnil && !rep_VOIDP(tem))
	    k = search_keymap (tem, code, mods, callback);
	else
	{
	    /* 1. search keymap for active window decoration */
	    k = search_keymap(context_keymap, code, mods, callback);

	    if (!k && (current_x_event != 0
		       && (current_x_event->xany.window == root_window
			   || current_x_event->xany.window
			   == no_focus_window)))
	    {
		/* 2. if event from root, search the root-window-keymap */
		k = search_keymap (Qroot_window_keymap, code, mods, callback);
	    }

	    if (!k && current_window)
	    {
		/* 3. search focused/pointer window keymap property */
		tem = Fwindow_get (rep_VAL(current_window), Qkeymap);
		if (tem && tem != Qnil)
		    k = search_keymap(tem, code, mods, callback);
	    }
	    if(!k)
		/* 4. search global-keymap */
		k = search_keymap(Qglobal_keymap, code, mods, callback);
	}
    }
    else
    {
	rep_GC_root gc_nkp;
	rep_PUSHGC(gc_nkp, nkp);
	while(!k && rep_CONSP(nkp))
	{
	    k = search_keymap(rep_CAR(nkp), code, mods, callback);
	    nkp = rep_CDR(nkp);
	}
	rep_POPGC;
    }
    return (k != rep_NULL && KEYP(k)) ? KEY_COMMAND(k) : rep_NULL;
}

static bool
eval_input_callback(repv key)
{
    repv cmd = KEY_COMMAND(key);
    if(rep_SYMBOLP(cmd))
    {
	cmd = Fsymbol_value (cmd, Qt);
	if (rep_FUNARGP(cmd))
	{
	    repv fun = rep_FUNARG(cmd)->fun;
	    if(rep_CONSP(fun) && rep_CAR(fun) == Qautoload)
	    {
		/* An autoload, try to load it. */
		rep_GC_root gc_key;
		rep_PUSHGC (gc_key, key);
		cmd = rep_call_with_closure (cmd, rep_load_autoload, cmd);
		rep_POPGC;
		if(cmd == rep_NULL)
		    return FALSE;
	    }
	}
    }
    if(Fkeymapp (cmd) != Qnil)
    {
	/* A prefix key, add its list to the next-keymap-path. */
	next_keymap_path = Fcons(cmd, next_keymap_path
				 ? next_keymap_path : Qnil);
	/* Look for more prefix keys */
	return FALSE;
    }
    if (cmd == Qnil)
	return FALSE;
    next_keymap_path = rep_NULL;
    return TRUE;
}

static repv
lookup_event_binding (u_long code, u_long mods, repv context_map)
{
    Lisp_Window *w = 0;
    if (current_x_event && (mods & EV_TYPE_MOUSE))
    {
	/* If a mouse event, look for bindings in the window that
	   the button was pressed in, not where the input focus is. */
	repv tem = Fquery_button_press_window ();
	if (tem && WINDOWP(tem))
	    w = VWIN(tem);
    }
    else
	w = focus_window;

    return lookup_binding(code, mods, eval_input_callback, context_map, w);
}

/* Process the event CODE+MODS. OS-INPUT-MSG is the raw input event
   from the window-system, this is only used to cook a string from.  */
repv
eval_input_event(repv context_map)
{
    u_long code, mods;
    repv result = Qnil, cmd, orig_next_keymap_path = next_keymap_path;

    if (!translate_event (&code, &mods, current_x_event))
	return Qnil;

    event_buf[event_index++] = code;
    event_buf[event_index++] = mods;
    if(event_index == EVENT_BUFSIZ)
	event_index = 0;
    printed_this_prefix = FALSE;

    current_event[0] = code;
    current_event[1] = mods;

    cmd = lookup_event_binding (code, mods, context_map);

    if (next_keymap_path == rep_NULL)
    {
	if (print_prefix)
	    rep_call_lisp1 (global_symbol_value (Qdisplay_message), Qnil);
	print_prefix = FALSE;
	event_index = 0;
    }

    if(cmd != rep_NULL)
    {
	/* Found a binding for this event; evaluate it. */
	result = rep_call_lisp1 (Fsymbol_value (Qcall_command, Qt), cmd);
    }
    else if(next_keymap_path != rep_NULL)
    {
	/* We already handled some prefixes. */
	Fset (Qthis_command, Qkeymap);
	result = Qnil;

	/* Grab the input devices for the next event */
	Fgrab_keyboard (focus_window ? rep_VAL(focus_window) : Qnil, Qt, Qt);
    }
    else if(orig_next_keymap_path != rep_NULL
	    && orig_next_keymap_path != Qglobal_keymap)
    {
	/* A multi-key binding, but no final step; clear the prefix
	   argument for the next command and beep. */
	Fset (Qprefix_arg, Qnil);
	Fbeep();
    }
    else
    {
	/* An unbound key with no prefix keys. */

	repv hook = global_symbol_value (Qunbound_key_hook);
	if (!rep_VOIDP (hook) && hook != Qnil)
	    result = Fcall_hook(Qunbound_key_hook, Qnil, Qor);
	else if (rep_recurse_depth == 0
		 && (mods & (EV_TYPE_KEY | EV_MOD_RELEASE)) == EV_TYPE_KEY)
	{
	    /* We're receiving events that we have no way of handling..
	       I think this gives us justification in aborting in case
	       we've got stuck with an unbreakable grab somehow.. */
	    Fungrab_keyboard ();
	    Fungrab_pointer ();
	    result = Fthrow (Qtop_level, Qnil);
	}
    }

    /* Out of a multi-key sequence, ungrab */
    if (orig_next_keymap_path && !next_keymap_path)
	Fungrab_keyboard ();

    last_event[0] = current_event[0];
    last_event[1] = current_event[1];

    /* Only lose the current event when in the topmost event loop. */
    if (rep_recurse_depth == 0)
	current_event[0] = current_event[1] = 0;

    if (print_prefix)
	print_event_prefix ();

    return result;
}


/* Translate text->event and vice versa */

struct key_def {
    const char *name;
    u_long mods, code;
};

static struct key_def default_mods[] = {
    { "S",	  ShiftMask },
    { "Shift",	  ShiftMask },
    { "C",        ControlMask },
    { "Control",  ControlMask },
    { "M",        EV_MOD_META },
    { "Meta",     EV_MOD_META },
    { "A",        EV_MOD_ALT },
    { "Alt",      EV_MOD_ALT },
    { "H",        EV_MOD_HYPER },
    { "Hyper",    EV_MOD_HYPER },
    { "Super",    EV_MOD_SUPER },
    { "W",	  EV_MOD_WM },
    { "Mod1",     Mod1Mask },
    { "Mod2",     Mod2Mask },
    { "Mod3",     Mod3Mask },
    { "Mod4",     Mod4Mask },
    { "Mod5",     Mod5Mask },
    { "Button1",  Button1Mask },
    { "Button2",  Button2Mask },
    { "Button3",  Button3Mask },
    { "Button4",  Button4Mask },
    { "Button5",  Button5Mask },
    { "Button6",  Button6Mask },
    { "Button7",  Button7Mask },
    { "Button8",  Button8Mask },
    { "Button9",  Button9Mask },
    { "Any",      EV_MOD_ANY },
    { "Release",  EV_MOD_RELEASE },
    { 0, 0 }
};

static struct key_def default_codes[] = {
    { "Click",    EV_TYPE_MOUSE, EV_CODE_MOUSE_CLICK1 },
    { "Click1",   EV_TYPE_MOUSE, EV_CODE_MOUSE_CLICK1 },
    { "Click2",   EV_TYPE_MOUSE, EV_CODE_MOUSE_CLICK2 },
    { "Click3",   EV_TYPE_MOUSE, EV_CODE_MOUSE_CLICK3 },
    { "Off",      EV_TYPE_MOUSE, EV_CODE_MOUSE_UP1 },
    { "Off1",     EV_TYPE_MOUSE, EV_CODE_MOUSE_UP1 },
    { "Off2",     EV_TYPE_MOUSE, EV_CODE_MOUSE_UP2 },
    { "Off3",     EV_TYPE_MOUSE, EV_CODE_MOUSE_UP3 },
    { "Move",     EV_TYPE_MOUSE, EV_CODE_MOUSE_MOVE },

    { "SPC",      EV_TYPE_KEY, XK_space },
    { "Space",    EV_TYPE_KEY, XK_space },
    { "TAB",      EV_TYPE_KEY, XK_Tab },
    { "RET",      EV_TYPE_KEY, XK_Return },
    { "ESC",      EV_TYPE_KEY, XK_Escape },
    { "BS",       EV_TYPE_KEY, XK_BackSpace },
    { "DEL",      EV_TYPE_KEY, XK_Delete },

    /* X defines lots of long names for these simple keys...  */
    { " ",        EV_TYPE_KEY, XK_space },
    { "!",        EV_TYPE_KEY, XK_exclam },
    { "\"",       EV_TYPE_KEY, XK_quotedbl },
    { "#",        EV_TYPE_KEY, XK_numbersign },
    { "$",        EV_TYPE_KEY, XK_dollar },
    { "%",        EV_TYPE_KEY, XK_percent },
    { "&",        EV_TYPE_KEY, XK_ampersand },
    { "'",        EV_TYPE_KEY, XK_quoteright },
    { "(",        EV_TYPE_KEY, XK_parenleft },
    { ")",        EV_TYPE_KEY, XK_parenright },
    { "*",        EV_TYPE_KEY, XK_asterisk },
    { "+",        EV_TYPE_KEY, XK_plus },
    { ",",        EV_TYPE_KEY, XK_comma },
    { "-",        EV_TYPE_KEY, XK_minus },
    { ".",        EV_TYPE_KEY, XK_period },
    { "/",        EV_TYPE_KEY, XK_slash },
    { ":",        EV_TYPE_KEY, XK_colon },
    { ";",        EV_TYPE_KEY, XK_semicolon },
    { "<",        EV_TYPE_KEY, XK_less },
    { "=",        EV_TYPE_KEY, XK_equal },
    { ">",        EV_TYPE_KEY, XK_greater },
    { "?",        EV_TYPE_KEY, XK_question },
    { "@",        EV_TYPE_KEY, XK_at },
    { "[",        EV_TYPE_KEY, XK_bracketleft },
    { "\\",       EV_TYPE_KEY, XK_backslash },
    { "]",        EV_TYPE_KEY, XK_bracketright },
    { "^",        EV_TYPE_KEY, XK_asciicircum },
    { "_",        EV_TYPE_KEY, XK_underscore },
    { "`",        EV_TYPE_KEY, XK_quoteleft },
    { "{",        EV_TYPE_KEY, XK_braceleft },
    { "|",        EV_TYPE_KEY, XK_bar },
    { "}",        EV_TYPE_KEY, XK_braceright },
    { "~",        EV_TYPE_KEY, XK_asciitilde },

    { 0, 0, 0 }
};

/* Puts the integers defining the event described in DESC into CODE
   and MODS. */
static bool
lookup_event(u_long *code, u_long *mods, u_char *desc)
{
    char *tem;
    char buf[100];
    *code = *mods = 0;

    /* First handle all modifiers */
    while(*desc && (tem = strchr(desc + 1, '-')) != 0)
    {
	struct key_def *x = default_mods;

	memcpy(buf, desc, tem - (char *)desc);
	buf[tem - (char *)desc] = 0;

	while(x->name != 0)
	{
	    if(strcasecmp(buf, x->name) == 0)
	    {
		*mods |= x->mods;
		break;
	    }
	    x++;
	}
	if(x->name == 0)
	    goto error;

	desc = tem + 1;
    }

    /* Then go for the code itself */
    {
	struct key_def *x = default_codes;
	u_int ks;
	while(x->name != 0)
	{
	    if(strcasecmp(desc, x->name) == 0)
	    {
		*mods |= x->mods;
		*code = x->code;
		if ((x->mods == EV_TYPE_MOUSE)
		    && (*mods & (EV_MOD_BUTTON_MASK | EV_MOD_ANY)) == 0)
		{
		    /* Button events must include at least one of the button
		       modifiers (otherwise they can never be generated) */
		    return FALSE;
		}
		else
		    return TRUE;
	    }
	    x++;
	}
	ks = XStringToKeysym(desc);
	if(ks != NoSymbol)
	{
	    if (*mods & ShiftMask)
	    {
		KeySym lower, upper;
		XConvertCase (ks, &lower, &upper);
		if (ks == lower && upper != lower)
		{
		    /* canonify `S-x' to `X' */
		    *mods &= ~ShiftMask;
		    ks = upper;
		}
	    }
	    *mods |= EV_TYPE_KEY;
	    *code = ks;
	    return TRUE;
	}
	else
	    goto error;
    }

error:
    Fsignal(Qbad_event_desc, rep_LIST_1(rep_string_dup(desc)));
    return FALSE;
}

/* Constructs the name of the event defined by CODE and MODS in BUF.  */
static bool
lookup_event_name(u_char *buf, u_long code, u_long mods)
{
    int i;
    struct key_def *x;
    u_long type = mods & EV_TYPE_MASK;

    char *end = buf, *tem;
    *buf = 0;

    mods &= EV_MOD_MASK;
    for(i = 32; i >= 0 && mods != 0; i--)	/* magic numbers!? */
    {
	u_long mask = 1 << i;
	if(mods & mask)
	{
	    mods &= ~mask;
	    x = default_mods;
	    while(x->name != 0)
	    {
		if(x->mods == mask)
		{
		    strcpy (end, x->name);
		    end += strlen (x->name);
		    break;
		}
		x++;
	    }
	    *end++ = '-';
	}
    }

    x = default_codes;
    while(x->name != 0)
    {
	if(type == x->mods && code == x->code)
	{
	    strcpy(end, x->name);
	    return TRUE;
	}
	x++;
    }
    tem = XKeysymToString((KeySym)code);
    if(tem != 0)
    {
	strcpy(end, tem);
	return TRUE;
    }
    return FALSE;
}

/* If necessary, print the name of the current event prefix. Returns true
   if in the middle of a multi-key sequence.  */
bool
print_event_prefix(void)
{
    int i;
    u_char buf[256];
    u_char *bufp = buf;

    if (next_keymap_path == rep_NULL)
	return FALSE;
    else if (printed_this_prefix)
	return TRUE;

    print_prefix = TRUE;
    for (i = 0; i < event_index; i += 2)
    {
	if (lookup_event_name (bufp, event_buf[i], event_buf[i+1]))
	{
	    bufp += strlen (bufp);
	    *bufp++ = ' ';
	}
    }
    if (next_keymap_path != rep_NULL)
    {
	if (bufp > buf)
	    bufp--;			/* erase the last space */
	*bufp++ = '.';
	*bufp++ = '.';
	*bufp++ = '.';
    }

    rep_call_lisp1 (global_symbol_value (Qdisplay_message),
		    rep_string_dupn (buf, bufp - buf));
    printed_this_prefix = TRUE;

    return TRUE;
}


/* Lisp functions */

DEFUN("make-keymap", Fmake_keymap, Smake_keymap, (void), rep_Subr0) /*
::doc:sawfish.wm.events#make-keymap::
make-keymap

Return a new keymap suitable for storing bindings in. This is a cons cell
looking like `(keymap . LIST-OF-BINDINGS)', LIST-OF-BINDINGS is initially
nil.
::end:: */
{
    return Fcons(Qkeymap, Qnil);
}

DEFUN("bind-keys", Fbind_keys, Sbind_keys, (repv args), rep_SubrN) /*
::doc:sawfish.wm.events#bind-keys::
bind-keys KEYMAP { EVENT-DESCRIPTION COMMAND }...

Adds key bindings to KEYMAP. Each EVENT-DESCRIPTION is a string naming an
event to bind to the corresponding COMMAND.

Returns KEYMAP when successful.
::end:: */
{
    repv km, arg1;
    if (!rep_CONSP(args))
	return rep_signal_missing_arg (1);
    km = rep_CAR(args);
    args = rep_CDR(args);
    while (rep_CONSP(args) && rep_CONSP(rep_CDR(args)))
    {
	u_long code, mods;
	repv key;
	arg1 = rep_CAR(args);
	args = rep_CDR(args);
	if (rep_STRINGP(arg1))
	{
	    if (!lookup_event (&code, &mods, rep_STR(arg1)))
		return Fsignal (Qbad_event_desc, rep_LIST_1(arg1));
	}
	else if(Feventp(arg1) != Qnil)
	{
	    code = rep_INT(rep_CAR(arg1));
	    mods = rep_INT(rep_CDR(arg1));
	}
	else
	    return Fsignal (Qbad_event_desc, rep_LIST_1(arg1));

	key = search_keymap (km, code, mods, 0);
	if (key != rep_NULL && rep_CONSP(key))
	    KEY_COMMAND(key) = rep_CAR(args);
	else
	{
	    key = MAKE_KEY(MAKE_EVENT(rep_MAKE_INT(code), rep_MAKE_INT(mods)),
			   rep_CAR(args));
	    rep_CDR(km) = Fcons(key, rep_CDR(km));
	    grab_keymap_event (km, code, mods, TRUE);
	}
	args = rep_CDR(args);
    }
    return km;
}

DEFUN("unbind-keys", Funbind_keys, Sunbind_keys, (repv args), rep_SubrN) /*
::doc:sawfish.wm.events#unbind-keys::
unbind-keys KEY-MAP EVENT-DESCRIPTION...
::end:: */
{
    repv km, arg1;
    if (!rep_CONSP(args))
	return rep_signal_missing_arg (1);

    km = rep_CAR(args);
    if (!rep_CONSP(km))
	return rep_signal_arg_error(km, 1);

    args = rep_CDR(args);
    while (rep_CONSP(args))
    {
	u_long code, mods;
	repv *keyp;
	arg1 = rep_CAR(args);
	if (rep_STRINGP(arg1))
	{
	    if (!lookup_event (&code, &mods, rep_STR(arg1)))
		return Fsignal (Qbad_event_desc, rep_LIST_1(arg1));
	}
	else if(Feventp(arg1) != Qnil)
	{
	    code = rep_INT(rep_CAR(arg1));
	    mods = rep_INT(rep_CDR(arg1));
	}
	else
	    return Fsignal(Qbad_event_desc, rep_LIST_1(arg1));

	keyp = &rep_CDR(km);
	while (rep_CONSP(*keyp))
	{
	    repv cell = rep_CAR(*keyp);
	    if (rep_CONSP(cell))
	    {
		if ((rep_INT(EVENT_MODS(KEY_EVENT(cell))) == mods)
		    && (rep_INT(EVENT_CODE(KEY_EVENT(cell))) == code))
		{
		    *keyp = rep_CDR(*keyp);
		}
		else
		    keyp = &rep_CDR(*keyp);
	    }
	    rep_TEST_INT; if(rep_INTERRUPTP) return rep_NULL;
	}
	grab_keymap_event (km, code, mods, FALSE);

	args = rep_CDR(args);
    }
    return km;
}

DEFUN("grab-keymap", Fgrab_keymap, Sgrab_keymap, (repv map), rep_Subr1) /*
::doc:sawfish.wm.events#grab-keymap::
grab-keymap KEYMAP

Grab any events in KEYMAP that need to be grabbed so that bindings in
KEYMAP may be serviced.
::end:: */
{
    rep_DECLARE1(map, rep_CONSP);
    grab_all_keylist_events (map, TRUE);
    return map;
}

DEFUN("ungrab-keymap", Fungrab_keymap, Sungrab_keymap, (repv map), rep_Subr1)/*
::doc:sawfish.wm.events#ungrab-keymap::
ungrab-keymap KEYMAP

Ungrab any events in KEYMAP that would have been grabbed so that bindings in
KEYMAP may be serviced.
::end:: */
{
    rep_DECLARE1(map, rep_CONSP);
    grab_all_keylist_events (map, FALSE);
    return map;
}

DEFSTRING(not_in_handler, "Not in event handler");
DEFUN("current-event-string", Fcurrent_event_string, Scurrent_event_string, (void), rep_Subr0) /*
::doc:sawfish.wm.events#current-event-string::
current-event-string

Returns the string which would have been inserted by the current event if
a Lisp function hadn't been called instead.
::end:: */
{
    KeySym ks;
    u_char buf[256];
    int len;

    if(current_x_event == 0)
	return Fsignal(Qerror, rep_LIST_1(rep_VAL(&not_in_handler)));

    len = XLookupString(&current_x_event->xkey,
			 buf, sizeof (buf) - 1, &ks, NULL);
    if(len > 0)
	return rep_string_dupn(buf, len);
    else
	return rep_null_string();
}

DEFUN("current-event", Fcurrent_event, Scurrent_event, (void), rep_Subr0) /*
::doc:sawfish.wm.events#current-event::
current-event

Return the event which caused the current command to be invoked.
::end:: */
{
    if(current_event[1])
	return MAKE_EVENT(rep_MAKE_INT(current_event[0]),
			  rep_MAKE_INT(current_event[1]));
    else
	return Qnil;
}

DEFUN("proxy-current-event", Fproxy_current_event, Sproxy_current_event,
      (repv win, repv mask, repv prop), rep_Subr3) /*
::doc:sawfish.wm.events#proxy-current-event::
proxy-current-event WINDOW [MASK] [PROPAGATE]

Send the current X event to WINDOW, either a window object, a numeric
window id, or the symbol `root'. If a ButtonPress event the pointer
grab will be released first.
::end:: */
{
    Window w = x_win_from_arg (win);
    if (w == 0)
	return WINDOWP(win) ? Qnil : rep_signal_arg_error (win, 1);

    if (current_x_event != 0)
    {
	long e_mask = (rep_INTP(mask) ? rep_INT(mask)
		       : get_event_mask (current_x_event->type));
	if (current_x_event->type == ButtonPress)
	    ungrab_pointer ();
	XSendEvent (dpy, w, prop == Qnil ? False : True,
		    e_mask, current_x_event);
	return Qt;
    }
    else
	return Qnil;
}

DEFUN("allow-events", Fallow_events, Sallow_events, (repv mode), rep_Subr1) /*
::doc:sawfish.wm.events#allow-events::
allow-events MODE
::end:: */
{
    int x_mode;
    if (mode == Qasync_pointer)
	x_mode = AsyncPointer;
    else if (mode == Qasync_keyboard)
	x_mode = AsyncKeyboard;
    else if (mode == Qsync_pointer)
	x_mode = SyncPointer;
    else if (mode == Qsync_keyboard)
	x_mode = SyncKeyboard;
    else if (mode == Qreplay_pointer)
	x_mode = ReplayPointer;
    else if (mode == Qreplay_keyboard)
	x_mode = ReplayKeyboard;
    else if (mode == Qsync_both)
	x_mode = SyncBoth;
    else if (mode == Qasync_both)
	x_mode = AsyncBoth;
    else
	return rep_signal_arg_error (mode, 1);

    XAllowEvents (dpy, x_mode, last_event_time);
    return Qt;
}

DEFUN("last-event", Flast_event, Slast_event, (void), rep_Subr0) /*
::doc:sawfish.wm.events#last-event::
last-event

Return the previous event which occurred.
::end:: */
{
    if(last_event[1])
	return MAKE_EVENT(rep_MAKE_INT(last_event[0]),
			  rep_MAKE_INT(last_event[1]));
    else
	return Qnil;
}

DEFUN("event-name", Fevent_name, Sevent_name, (repv ev), rep_Subr1) /*
::doc:sawfish.wm.events#event-name::
event-name EVENT

Returns a string naming the event EVENT.
::end:: */
{
    u_char buf[256];
    if(!EVENTP(ev))
	return rep_signal_arg_error(ev, 1);

    if(lookup_event_name(buf, rep_INT(EVENT_CODE(ev)),
			 rep_INT(EVENT_MODS(ev))))
    {
	return rep_string_dup(buf);
    }
    else
	return Qnil;
}

DEFUN("lookup-event", Flookup_event, Slookup_event, (repv name), rep_Subr1) /*
::doc:sawfish.wm.events#lookup-event::
lookup-event EVENT-NAME

Return the event whose name is EVENT-NAME.
::end:: */
{
    u_long code, mods;
    rep_DECLARE1(name, rep_STRINGP);

    if(lookup_event(&code, &mods, rep_STR(name)))
	return MAKE_EVENT(rep_MAKE_INT(code), rep_MAKE_INT(mods));
    else
	return Qnil;
}

DEFUN("lookup-event-binding", Flookup_event_binding, Slookup_event_binding, (repv ev), rep_Subr1) /*
::doc:sawfish.wm.events#lookup-event-binding::
lookup-event-binding EVENT

Return the command currently associated with the event EVENT.

Note that `currently associated' means that the currently active set of
keymaps is used to resolve the binding. This means the window and
frame-part that received the current event for pointer events, or the
currently focused window for keyboard events.
::end:: */
{
    repv res, context = Qnil;
    u_long code, mods;

    if(!EVENTP(ev))
	return(rep_signal_arg_error(ev, 1));

    code = rep_INT(EVENT_CODE(ev));
    mods = rep_INT(EVENT_MODS(ev));

    if (mods & EV_TYPE_MOUSE)
    {
	/* look for a context map */
	if (clicked_frame_part != 0 && clicked_frame_part->clicked)
	    context = get_keymap_for_frame_part (clicked_frame_part);
    }

    res = lookup_event_binding(code, mods, context);
    return res ? res : Qnil;
}

DEFUN("search-keymap", Fsearch_keymap, Ssearch_keymap,
      (repv ev, repv km), rep_Subr2) /*
::doc:sawfish.wm.events#search-keymap::
search-keymap EVENT KEYMAP

Return the (COMMAND . EVENT) binding of EVENT in KEYMAP, or nil.
::end:: */
{
    repv res;
    rep_DECLARE1(ev, EVENTP);
    res = search_keymap(km, rep_INT(EVENT_CODE(ev)),
			rep_INT(EVENT_MODS(ev)), 0);
    return res ? res : Qnil;
}

DEFUN("x-lookup-keysym", Fx_lookup_keysym,
      Sx_lookup_keysym, (repv name), rep_Subr1) /*
::doc:sawfish.wm.events#x-lookup-keysym::
x-lookup-keysym NAME

Return the X11 keysym (an integer), named by the Lisp symbol NAME.
::end:: */
{
    KeySym sym;
    rep_DECLARE1(name, rep_SYMBOLP);
    sym = XStringToKeysym (rep_STR(rep_SYM(name)->name));
    return sym == NoSymbol ? Qnil : rep_MAKE_INT (sym);
}

DEFUN("x-keysym-name", Fx_keysym_name, Sx_keysym_name, (repv ks), rep_Subr1) /*
::doc:sawfish.wm.events#x-keysym-name::
x-keysym-name KEYSYM

Return the Lisp symbol naming the X11 keysym represented by the integer
KEYSYM.
::end:: */
{
    char *id;
    rep_DECLARE1(ks, rep_INTP);
    id = XKeysymToString (rep_INT(ks));
    return !id ? Qnil : Fintern (rep_string_dup (id), rep_obarray);
}

DEFUN("keymapp", Fkeymapp, Skeymapp, (repv arg), rep_Subr1) /*
::doc:sawfish.wm.events#keymapp::
keymapp ARG

Returns t if ARG can be used as a keymap.
::end:: */
{
    return (rep_CONSP(arg) && rep_CAR(arg) == Qkeymap) ? Qt : Qnil;
}

DEFUN("eventp", Feventp, Seventp, (repv arg), rep_Subr1) /*
::doc:sawfish.wm.events#eventp::
eventp ARG

Returns t if the ARG is an input event.
::end:: */
{
    return EVENTP(arg) ? Qt : Qnil;
}

DEFUN("event-match", Fevent_match,
      Sevent_match, (repv ev1, repv ev2), rep_Subr2) /*
::doc:sawfish.wm.events#event-match::
event-match EVENT1 EVENT2
::end:: */
{
    rep_DECLARE1 (ev1, EVENTP);
    rep_DECLARE2 (ev2, EVENTP);

    return (compare_events (rep_INT (EVENT_CODE (ev1)),
			    rep_INT (EVENT_MODS (ev1)),
			    rep_INT (EVENT_CODE (ev2)),
			    rep_INT (EVENT_MODS (ev2)))
	    || compare_events (rep_INT (EVENT_CODE (ev2)),
			       rep_INT (EVENT_MODS (ev2)),
			       rep_INT (EVENT_CODE (ev1)),
			       rep_INT (EVENT_MODS (ev1)))) ? Qt : Qnil;
}

DEFUN("forget-button-press", Fforget_button_press,
      Sforget_button_press, (void), rep_Subr0)
{
    last_click = 0;
    return Qt;
}

/* Find the bottom-most window below TOP containing (X,Y) that selects
   for button events */
static Window
window_getting_button_event(Window top, int x, int y)
{
    Window w = top, w2 = w, child;
    XWindowAttributes wa;

    /* Find the bottom-most child of W containing (X,Y) */
    do {
	XTranslateCoordinates (dpy, w, w2, x, y, &x, &y, &child);
	w = w2;
	if (child)
	    w2 = child;
    } while (child);

    /* Then work up until TOP is reached, or a window selecting
       button events is found */
again:
    XGetWindowAttributes (dpy, w, &wa);
    if (w != top
	&& !(wa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
    {
	Window d1, *d2, parent;
	u_int d3;
	    
	XQueryTree (dpy, w, &d1, &parent, &d2, &d3);
	if (d2 != 0)
	    XFree (d2);
	if (parent != 0)
	{
	    w = parent;
	    goto again;
	}
    }

    return w;
}

DEFUN("synthesize-event", Fsynthesize_event, Ssynthesize_event,
      (repv event, repv win, repv propagate), rep_Subr3) /*
::doc:sawfish.wm.events#synthesize-event::
synthesize-event EVENT WINDOW [PROPAGATE]
::end:: */
{
    XEvent ev;
    repv ptr = Fquery_pointer (Qnil);
    Window w = x_win_from_arg (win);
    Window child, dummy;
    int x_offset, y_offset;

    if (w == 0)
	return WINDOWP(win) ? Qnil : rep_signal_arg_error (win, 1);
    if (rep_STRINGP (event))
	event = Flookup_event (event);
    if (!event || !ptr)
	return rep_NULL;
    rep_DECLARE (1, event, EVENTP (event));
    ev.xany.display = dpy;
    ev.xany.window = w;

    if (WINDOWP(win))
    {
	x_offset = -VWIN(win)->attr.x;
	y_offset = -VWIN(win)->attr.y;
	if (VWIN(win)->reparented) 
	{
	    x_offset += VWIN(win)->frame_x;
	    y_offset += VWIN(win)->frame_y;
	}
    }
    else
	x_offset = y_offset = 0;

    switch (rep_INT(EVENT_MODS(event)) & EV_TYPE_MASK)
    {
	u_int mask;

    case EV_TYPE_KEY:
	if (!translate_event_to_x_key (event, &ev.xkey.keycode,
				       &ev.xkey.state))
	{
	    return rep_signal_arg_error (event, 1);
	}
	ev.xkey.root = root_window;
	ev.xkey.subwindow = 0;
	ev.xkey.time = last_event_time;
	ev.xkey.x_root = rep_INT (rep_CAR (ptr));
	ev.xkey.y_root = rep_INT (rep_CDR (ptr));
	ev.xkey.x = (ev.xkey.x_root + x_offset);
	ev.xkey.y = (ev.xkey.y_root + y_offset);
	ev.xkey.same_screen = True;
	ev.xany.type = KeyPress;
	XSendEvent (dpy, w, propagate != Qnil, KeyPressMask, &ev);
	ev.xany.type = KeyRelease;
	XSendEvent (dpy, w, propagate != Qnil, KeyReleaseMask, &ev);
	break;

    case EV_TYPE_MOUSE:
	mask = translate_event_to_x_button (event, &ev.xbutton.button,
					    &ev.xbutton.state);
	if (!mask || ev.xbutton.button == 0)
	    return rep_signal_arg_error (event, 1);

	ev.xbutton.root = root_window;
	ev.xbutton.subwindow = 0;
	ev.xbutton.time = last_event_time;
	ev.xbutton.x = rep_INT (rep_CAR (ptr)) + x_offset;
	ev.xbutton.y = rep_INT (rep_CDR (ptr)) + y_offset;
	XTranslateCoordinates (dpy, w, root_window,
			       ev.xbutton.x, ev.xbutton.y,
			       &ev.xbutton.x_root, &ev.xbutton.y_root,
			       &dummy);
	child = window_getting_button_event (w, ev.xbutton.x, ev.xbutton.y);
	XTranslateCoordinates (dpy, w, child,
			       ev.xbutton.x, ev.xbutton.y,
			       &ev.xbutton.x, &ev.xbutton.y,
			       &dummy);
	ev.xbutton.same_screen = True;
	ev.xbutton.window = child;
	ev.xany.type = ButtonPress;
	XSendEvent (dpy, child, propagate != Qnil, ButtonPressMask, &ev);
	ev.xany.type = ButtonRelease;
	ev.xbutton.state |= mask;
	XSendEvent (dpy, child, propagate != Qnil, ButtonReleaseMask, &ev);
	break;

    default:
	return rep_signal_arg_error (event, 1);
    }
    return Qt;
}


/* Find the lisp modifier mask used by the meta and alt keys. This code
   shamelessly stolen from Emacs 19. :-) */

DEFSTRING(meta_l, "Meta_L");
DEFSTRING(meta_r, "Meta_R");
DEFSTRING(alt_l, "Alt_L");
DEFSTRING(alt_r, "Alt_R");
DEFSTRING(hyper_l, "Hyper_L");
DEFSTRING(hyper_r, "Hyper_R");
DEFSTRING(super_l, "Super_L");
DEFSTRING(super_r, "Super_R");

static void
nconc (repv x, repv y)
{
    repv *ptr = &x;

    while (rep_CONSP (*ptr))
	ptr = rep_CDRLOC (*ptr);

    *ptr = y;
}

static void
find_meta(void)
{
    int min_code, max_code;
    KeySym *syms;
    int syms_per_code;
    XModifierKeymap *mods;
    repv meta_syms = Qnil, alt_syms = Qnil;
    repv hyper_syms = Qnil, super_syms = Qnil;

#if defined (XlibSpecificationRelease) && XlibSpecificationRelease >= 4
    XDisplayKeycodes(dpy, &min_code, &max_code);
#else
    min_code = dpy->min_keycode;
    max_code = dpy->max_keycode;
#endif

    /* reset to default values. */
    meta_mod = alt_mod = hyper_mod = super_mod = 0;
    num_lock_mod = scroll_lock_mod = 0;

    Fset (Qmeta_keysyms, Qnil);
    Fset (Qalt_keysyms, Qnil);
    Fset (Qhyper_keysyms, Qnil);
    Fset (Qsuper_keysyms, Qnil);

    syms = XGetKeyboardMapping(dpy, min_code, max_code - min_code + 1,
			       &syms_per_code);
    mods = XGetModifierMapping(dpy);

    {
	int row, col;

	for(row = 3; row < 8; row++)
	{
	    for(col = 0; col < mods->max_keypermod; col++)
	    {
		KeyCode code = mods->modifiermap[(row * mods->max_keypermod)
						+ col];
		int code_col;
		if(code == 0)
		    continue;
		for(code_col = 0; code_col < syms_per_code; code_col++)
		{
		    int sym = syms[((code - min_code) * syms_per_code)
				  + code_col];
		    switch(sym)
		    {
		    case XK_Meta_L: case XK_Meta_R:
			meta_mod = 1 << row;
			meta_syms = Fcons (sym == XK_Meta_L ? rep_VAL(&meta_l)
					   : rep_VAL(&meta_r), meta_syms);
			break;

		    case XK_Alt_L: case XK_Alt_R:
			alt_mod = 1 << row;
			alt_syms = Fcons (sym == XK_Alt_L ? rep_VAL(&alt_l)
					  : rep_VAL(&alt_r), alt_syms);
			break;

		    case XK_Hyper_L: case XK_Hyper_R:
			hyper_mod = 1 << row;
			hyper_syms = Fcons (sym == XK_Hyper_L
					    ? rep_VAL(&hyper_l)
					    : rep_VAL(&hyper_r), hyper_syms);
			break;

		    case XK_Super_L: case XK_Super_R:
			super_mod = 1 << row;
			super_syms = Fcons (sym == XK_Super_L
					    ? rep_VAL(&super_l)
					    : rep_VAL(&super_r), super_syms);
			break;

		    case XK_Num_Lock:
			num_lock_mod = 1 << row;
			break;

		    case XK_Scroll_Lock:
			scroll_lock_mod = 1 << row;
			break;
		    }
		}
	    }
	}
    }

    XFree((char *)syms);
    XFreeModifiermap(mods);

    if (meta_mod == 0 && alt_mod == 0 && hyper_mod == 0 && super_mod == 0)
	return;

    if (meta_mod == 0)
	meta_mod = alt_mod;
    if (meta_mod == 0)
	meta_mod = hyper_mod;
    if (meta_mod == 0)
	meta_mod = super_mod;

    if (meta_mod == alt_mod)
    {
	nconc (meta_syms, alt_syms);
	alt_syms = meta_syms;
    }
    if (meta_mod == hyper_mod)
    {
	nconc (meta_syms, hyper_syms);
	hyper_syms = meta_syms;
    }
    if (meta_mod == super_mod)
    {
	nconc (meta_syms, super_syms);
	super_syms = meta_syms;
    }

    Fset (Qmeta_keysyms, meta_syms);
    Fset (Qalt_keysyms, alt_syms);
    Fset (Qhyper_keysyms, hyper_syms);
    Fset (Qsuper_keysyms, super_syms);
}

static void
build_lock_mods (void)
{
    int i;
    total_lock_combs = 2 * (num_lock_mod ? 2 : 1) * (scroll_lock_mod ? 2 : 1);
    memset (all_lock_combs, 0, sizeof (all_lock_combs));
    for (i = 0; i < total_lock_combs; i++)
    {
	if (i & 1)
	    all_lock_combs[i] |= LockMask;
	if (i & 2)
	    all_lock_combs[i] |= num_lock_mod ? num_lock_mod : scroll_lock_mod;
	if (i & 4)
	    all_lock_combs[i] |= scroll_lock_mod;
    }
    all_lock_mask = LockMask | num_lock_mod | scroll_lock_mod;
}

void
update_keyboard_mapping (void)
{
    find_meta ();
    build_lock_mods ();
}

static void
set_wm_modifier (u_long mods)
{
    wm_mod = indirect_modifiers (mods);
}

DEFUN ("set-wm-modifier", Fset_wm_modifier,
       Sset_wm_modifier, (repv mods), rep_Subr1) /*
::doc:sawfish.wm.events#set-wm-modifier::
set-wm-modifier MODIFIERS

Set the value of the `Window Manager' modifier to MODIFIERS, an integer.
::end:: */
{
    rep_DECLARE1 (mods, rep_INTP);
    set_wm_modifier (rep_INT (mods));
    return Qt;
}

DEFUN ("wm-modifier", Fwm_modifier, Swm_modifier, (void), rep_Subr0) /*
::doc:sawfish.wm.events#wm-modifier::
wm-modifier

Returns the current value of the `Window Manager' modifier, an integer.
::end:: */
{
    return rep_MAKE_INT (wm_mod);
}


/* Key and button grabbing */

static void
grab_event (Window grab_win, repv ev)
{
    switch (rep_INT(EVENT_MODS(ev)) & EV_TYPE_MASK)
    {
	u_int code, state;
	int i;

    case EV_TYPE_KEY:
	if (translate_event_to_x_key (ev, &code, &state))
	{
	    if (state != AnyModifier)
	    {
		for (i = 0; i < total_lock_combs; i++)
		{
		    XGrabKey (dpy, code, state | all_lock_combs[i], grab_win,
			      False, GrabModeSync, GrabModeSync);
		}
	    }
	    else
	    {
		XGrabKey (dpy, code, state, grab_win,
			  False, GrabModeSync, GrabModeSync);
	    }
	}
	break;

    case EV_TYPE_MOUSE:
	if (translate_event_to_x_button (ev, &code, &state))
	{
	    if (state != AnyModifier)
	    {
		for (i = 0; i < total_lock_combs; i++)
		{
		    XGrabButton (dpy, code, state | all_lock_combs[i],
				 grab_win, False, POINTER_GRAB_EVENTS,
				 GrabModeSync, GrabModeSync, None, None);
		}
	    }
	    else if (code != 0)
	    {
		XGrabButton (dpy, code, AnyModifier, grab_win,
			     False, POINTER_GRAB_EVENTS,
			     GrabModeSync, GrabModeSync, None, None);
	    }
	    else
	    {
		/* sawmill treats mouse buttons as modifiers, not as
		   codes, so for us AnyModifier includes all buttons.. */
		for (i = 0; i < 9; i++)
		{
		    XGrabButton (dpy, all_buttons[i], AnyModifier,
				 grab_win, False, POINTER_GRAB_EVENTS,
				 GrabModeSync, GrabModeSync, None, None);
		}
	    }
	}
    }
}

static void
ungrab_event (Window grab_win, repv ev)
{
    switch (rep_INT(EVENT_MODS(ev)) & EV_TYPE_MASK)
    {
	u_int code, state;
	int i;

    case EV_TYPE_KEY:
	if (translate_event_to_x_key (ev, &code, &state))
	{
	    if (state != AnyModifier)
	    {
		for (i = 0; i < total_lock_combs; i++)
		{
		    XUngrabKey (dpy, code, state | all_lock_combs[i],
				grab_win);
		}
	    }
	    else
		XUngrabKey (dpy, code, state, grab_win);
	}
	break;

    case EV_TYPE_MOUSE:
	if (translate_event_to_x_button (ev, &code, &state))
	{
	    if (state != AnyModifier)
	    {
		for (i = 0; i < total_lock_combs; i++)
		{
		    XUngrabButton (dpy, code, state | all_lock_combs[i],
				   grab_win);
		}
	    }
	    else if (code != 0)
	    {
		XUngrabButton (dpy, code, AnyModifier, grab_win);
	    }
	    else
	    {
		for (i = 0; i < 9; i++)
		    XUngrabButton (dpy, all_buttons[i], AnyModifier, grab_win);
	    }
	}
    }
}

static void
grab_keymap_event (repv km, long code, long mods, bool grab)
{
    Lisp_Window *w;
    repv ev = MAKE_EVENT(rep_MAKE_INT(code), rep_MAKE_INT(mods));
    repv global = Fsymbol_value (Qglobal_keymap, Qt);
    if (rep_SYMBOLP(km))
	km = Fsymbol_value (km, Qt);
    for (w = window_list; w != 0; w = w->next)
    {
	if (!WINDOW_IS_GONE_P (w))
	{
	    repv tem = Fwindow_get (rep_VAL(w), Qkeymap);
	    if (rep_SYMBOLP(tem) && tem != Qnil)
		tem = Fsymbol_value (tem, Qt);
	    if (km == global || tem == km)
		(grab ? grab_event : ungrab_event) (w->id, ev);
	}
    }
}

static void
grab_all_keylist_events (repv map, bool grab)
{
    repv tem = rep_CDR(map);
    while (rep_CONSP(tem) && !rep_INTERRUPTP)
    {
	repv key = KEY_EVENT(rep_CAR(tem));
	grab_keymap_event (map, rep_INT(EVENT_CODE(key)),
			   rep_INT(EVENT_MODS(key)), grab);
	tem = rep_CDR(tem);
	rep_TEST_INT;
    }
}

static void
grab_keylist_events (Window grab_win, repv list, bool grab)
{
    while (!rep_INTERRUPTP && rep_CONSP(list))
    {
	(grab ? grab_event : ungrab_event) (grab_win,
					    KEY_EVENT(rep_CAR(list)));
	list = rep_CDR(list);
	rep_TEST_INT;
    }
}

void
grab_keymap_events (Window grab_win, repv keymap, bool grab)
{
    /* If it's a symbol, dereference it. */
    while(rep_SYMBOLP(keymap) && !rep_NILP(keymap) && !rep_INTERRUPTP)
    {
	repv tem = Fsymbol_value(keymap, Qt);
	if(tem == keymap)
	    break;
	keymap = tem;
	rep_TEST_INT;
    }

    if (rep_CONSP(keymap))
	grab_keylist_events (grab_win, rep_CDR(keymap), grab);
}

/* Grab all bound events in client window W. */
void
grab_window_events (Lisp_Window *w, bool grab)
{
    repv tem;
    tem = Fsymbol_value (Qglobal_keymap, Qt);
    if (tem != Qnil && !rep_VOIDP(tem) && !WINDOW_IS_GONE_P (w))
	grab_keymap_events (w->id, tem, grab);
    tem = Fwindow_get (rep_VAL(w), Qkeymap);
    if (tem && tem != Qnil && !WINDOW_IS_GONE_P (w))
	grab_keymap_events (w->id, tem, grab);
}

static void
keymap_prop_change (Lisp_Window *w, repv prop, repv old, repv new)
{
    if (prop == Qkeymap && !WINDOW_IS_GONE_P (w))
    {
	/* A bit of a hack */
	grab_keymap_events (w->id, old, FALSE);
	grab_keymap_events (w->id, new, TRUE);
    }
}


/* initialisation */

void
keys_init(void)
{
    repv tem;

    rep_INTERN_SPECIAL(global_keymap);
    rep_INTERN_SPECIAL(root_window_keymap);
    rep_INTERN_SPECIAL(override_keymap);
    rep_INTERN_SPECIAL(unbound_key_hook);
    rep_INTERN_SPECIAL(eval_key_release_events);
    rep_INTERN_SPECIAL(eval_modifier_events);
    Fset (Qeval_key_release_events, Qnil);
    Fset (Qeval_modifier_events, Qnil);
    rep_INTERN(keymap);

    tem = rep_push_structure ("sawfish.wm.events");
    rep_ADD_SUBR(Smake_keymap);
    rep_ADD_SUBR(Sbind_keys);
    rep_ADD_SUBR(Sunbind_keys);
    rep_ADD_SUBR(Sgrab_keymap);
    rep_ADD_SUBR(Sungrab_keymap);
    rep_ADD_SUBR(Scurrent_event_string);
    rep_ADD_SUBR(Scurrent_event);
    rep_ADD_SUBR(Sproxy_current_event);
    rep_ADD_SUBR(Sallow_events);
    rep_ADD_SUBR(Slast_event);
    rep_ADD_SUBR(Sevent_name);
    rep_ADD_SUBR(Slookup_event);
    rep_ADD_SUBR(Slookup_event_binding);
    rep_ADD_SUBR(Ssearch_keymap);
    rep_ADD_SUBR(Skeymapp);
    rep_ADD_SUBR(Seventp);
    rep_ADD_SUBR(Sevent_match);
    rep_ADD_SUBR(Sforget_button_press);
    rep_ADD_SUBR(Sx_lookup_keysym);
    rep_ADD_SUBR(Sx_keysym_name);
    rep_ADD_SUBR(Ssynthesize_event);
    rep_ADD_SUBR(Sset_wm_modifier);
    rep_ADD_SUBR(Swm_modifier);
    rep_pop_structure (tem);

    rep_INTERN(async_pointer);
    rep_INTERN(async_keyboard);
    rep_INTERN(sync_pointer);
    rep_INTERN(sync_keyboard);
    rep_INTERN(replay_pointer);
    rep_INTERN(replay_keyboard);
    rep_INTERN(sync_both);
    rep_INTERN(async_both);

    rep_INTERN_SPECIAL(meta_keysyms);
    Fset (Qmeta_keysyms, Qnil);
    rep_INTERN_SPECIAL(alt_keysyms);
    Fset (Qalt_keysyms, Qnil);
    rep_INTERN_SPECIAL(hyper_keysyms);
    Fset (Qhyper_keysyms, Qnil);
    rep_INTERN_SPECIAL(super_keysyms);
    Fset (Qsuper_keysyms, Qnil);
    rep_INTERN_SPECIAL(multi_click_delay);
    Fset (Qmulti_click_delay, rep_MAKE_INT(DEFAULT_DOUBLE_CLICK_TIME));

    rep_INTERN_SPECIAL(this_command);
    rep_INTERN_SPECIAL(last_command);
    rep_INTERN_SPECIAL(prefix_arg);
    rep_INTERN_SPECIAL(current_prefix_arg);
    Fset (Qthis_command, Qnil);
    Fset (Qlast_command, Qnil);
    Fset (Qprefix_arg, Qnil);
    Fset (Qcurrent_prefix_arg, Qnil);

    rep_INTERN(display_message);
    rep_INTERN(call_command);

    rep_mark_static(&next_keymap_path);
 
    register_property_monitor (Qkeymap, keymap_prop_change);

    if (!batch_mode_p ())
	update_keyboard_mapping ();
}
