/*
 * Copyright 2000, 2001, 2002 by Paul Mattes.
 *   Permission to use, copy, modify, and distribute this software and its
 *   documentation for any purpose and without fee is hereby granted,
 *   provided that the above copyright notice appear in all copies and that
 *   both that copyright notice and this permission notice appear in
 *   supporting documentation.
 *
 * c3270 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 file LICENSE for more details.
 */

/*
 *	keymap.c
 *		A curses-based 3270 Terminal Emulator
 *		Keyboard mapping
 */

#include "globals.h"
#include <errno.h>
#include "appres.h"
#include "resources.h"

#include "hostc.h"
#include "keymapc.h"
#include "macrosc.h"
#include "popupsc.h"
#include "screenc.h"
#include "statusc.h"
#include "trace_dsc.h"
#include "utilc.h"

#undef COLS
extern int cCOLS;

#undef COLOR_BLACK
#undef COLOR_RED    
#undef COLOR_GREEN
#undef COLOR_YELLOW
#undef COLOR_BLUE   
#undef COLOR_WHITE
#if defined(HAVE_NCURSES_H) /*[*/
#include <ncurses.h>
#else /*][*/
#include <curses.h>
#endif /*]*/

#define KM_3270_ONLY	0x0010	/* used in 3270 mode only */
#define KM_NVT_ONLY	0x0020	/* used in NVT mode only */
#define KM_INACTIVE	0x0040	/* wrong NVT/3270 mode, or overridden */

#define KM_KEYMAP	0x8000
#define KM_HINTS	(KM_CTRL|KM_META)

struct keymap {
	struct keymap *next;
	struct keymap *successor;
	int ncodes;		/* number of key codes */
	int *codes;		/* key codes (ASCII or ncurses symbols) */
	int *hints;		/* hints (flags) */
	char *file;		/* file or resource name */
	int line;		/* keymap line number */
	char *action;		/* actions to perform */
};

#define IS_INACTIVE(k)	((k)->hints[0] & KM_INACTIVE)

static struct keymap *master_keymap = NULL;
static struct keymap **nextk = &master_keymap;

static Boolean last_3270 = False;
static Boolean last_nvt = False;

static int lookup_ccode(const char *s);
static void keymap_3270_mode(Boolean);

#define codecmp(k1, k2, len)	\
	memcmp((k1)->codes, (k2)->codes, (len)*sizeof(int))

static void read_one_keymap(const char *name, const char *fn, const char *r0,
    int flags);
static void clear_keymap(void);
static void set_inactive(void);

/*
 * Parse a key definition.
 * Returns <0 for error, 1 for key found and parsed, 0 for nothing found.
 * Returns the balance of the string and the character code.
 * Is destructive.
 */
static int
parse_keydef(char **str, int *ccode, int *hint)
{
	char *s = *str;
	char *t;
	char *ks;
	int flags = 0;
	KeySym Ks;

	/* Check for nothing. */
	while (isspace(*s))
		s++;
	if (!*s)
		return 0;
	*str = s;

	s = strstr(s, "<Key>");
	if (s == CN)
		return -1;
	ks = s + 5;
	*s = '\0';
	s = *str;
	while (*s) {
		while (isspace(*s))
			s++;
		if (!*s)
			break;
		if (!strncmp(s, "Meta", 4)) {
			flags |= KM_META;
			s += 4;
		} else if (!strncmp(s, "Ctrl", 4)) {
			flags |= KM_CTRL;
			s += 4;
		} else
			return -2;
	}
	s = ks;
	while (isspace(*s))
		s++;
	if (!*s)
		return -3;

	t = s;
	while (*t && !isspace(*t))
		t++;
	if (*t)
		*t++ = '\0';
	Ks = StringToKeysym(s);
	if (Ks == NoSymbol) {
		*ccode = lookup_ccode(s);
		if (*ccode == -1)
			return -4;
		if (flags)
			return -5;
	} else
		*ccode = (int)Ks;

	if (flags & KM_CTRL)
		*ccode &= 0x1f;
	if (flags & KM_META)
		*ccode |= 0x80;

	/* Return the remaining string, and success. */
	*str = t;
	*hint = flags;
	return 1;
}

static char *pk_errmsg[] = {
	"Missing <Key>",
	"Unknown modifier",
	"Missing keysym",
	"Unknown keysym",
	"Can't use modifier with curses symbol"
};

/*
 * Locate a keymap resource or file.
 * Returns 0 for do-nothing, 1 for success, -1 for error.
 * On success, returns the full name of the resource or file (which must be
 *  freed) in '*fullname'.
 * On success, returns a resource string (which must be closed) or NULL
 *  (indicating a file name to open is in *fullname) in '*r'.
 */
static int
locate_keymap(const char *name, char **fullname, char **r)
{
	char *rs;			/* resource value */
	char *fnx;			/* expanded file name */
	int a;				/* access(fnx) */

	/* Return nothing, to begin with. */
	*fullname = CN;
	*r = CN;

	/* See if it's a resource. */
	rs = get_fresource(ResKeymap ".%s", name);

	/* If there's a plain version, return it. */
	if (rs != CN) {
		*fullname = NewString(name);
		*r = NewString(rs);
		return 1;
	}

	/* See if it's a file. */
	fnx = do_subst(name, True, True);
	a = access(fnx, R_OK);

	/* If there's a plain version, return it. */
	if (a == 0) {
		*fullname = fnx;
		return 1;
	}

	/* No dice. */
	Free(fnx);
	return -1;
}

/* Add a keymap entry. */
static void
add_keymap_entry(int ncodes, int *codes, int *hints, const char *file,
    int line, const char *action)
{
	struct keymap *k;
	struct keymap *j;

	/* Allocate a new node. */
	k = Malloc(sizeof(struct keymap));
	k->next = NULL;
	k->successor = NULL;
	k->ncodes = ncodes;
	k->codes = Malloc(ncodes * sizeof(int));
	(void) memcpy(k->codes, codes, ncodes * sizeof(int));
	k->hints = Malloc(ncodes * sizeof(int));
	(void) memcpy(k->hints, hints, ncodes * sizeof(int));
	k->file = NewString(file);
	k->line = line;
	k->action = NewString(action);

	/* See if it's inactive, or supercedes other entries. */
	if ((!last_3270 && (k->hints[0] & KM_3270_ONLY)) ||
	    (!last_nvt  && (k->hints[0] & KM_NVT_ONLY))) {
		k->hints[0] |= KM_INACTIVE;
	} else for (j = master_keymap; j != NULL; j = j->next) {
		/* It may supercede other entries. */
		if (j->ncodes == k->ncodes &&
		    !codecmp(j, k, k->ncodes)) {
			j->hints[0] |= KM_INACTIVE;
			j->successor = k;
		}
	}

	/* Link it in. */
	*nextk = k;
	nextk = &k->next;
}

/*
 * Read a keymap from a file.
 * Returns 0 for success, -1 for an error.
 *
 * Keymap files look suspiciously like x3270 keymaps, but aren't.
 */
static void
read_keymap(const char *name)
{
	char *name_3270 = xs_buffer("%s.3270", name);
	char *name_nvt = xs_buffer("%s.nvt", name);
	int rc, rc_3270, rc_nvt;
	char *fn, *fn_3270, *fn_nvt;
	char *r0, *r0_3270, *r0_nvt;

	rc = locate_keymap(name, &fn, &r0);
	rc_3270 = locate_keymap(name_3270, &fn_3270, &r0_3270);
	rc_nvt = locate_keymap(name_nvt, &fn_nvt, &r0_nvt);
	if (rc < 0 && rc_3270 < 0 && rc_nvt < 0) {
		xs_warning("No such keymap resource or file: %s",
		    name);
		Free(name_3270);
		Free(name_nvt);
		return;
	}

	if (rc >= 0) {
		read_one_keymap(name, fn, r0, 0);
		Free(fn);
		Free(r0);
	}
	if (rc_3270 >= 0) {
		read_one_keymap(name_3270, fn_3270, r0_3270, KM_3270_ONLY);
		Free(fn_3270);
		Free(r0_3270);
	}
	if (rc_nvt >= 0) {
		read_one_keymap(name_nvt, fn_nvt, r0_nvt, KM_NVT_ONLY);
		Free(fn_nvt);
		Free(r0_nvt);
	}
	Free(name_3270);
	Free(name_nvt);
}

/*
 * Read a keymap from a file.
 * Returns 0 for success, -1 for an error.
 *
 * Keymap files look suspiciously like x3270 keymaps, but aren't.
 */
static void
read_one_keymap(const char *name, const char *fn, const char *r0, int flags)
{
	char *r = CN;			/* resource value */
	char *r_copy = CN;		/* initial value of r */
	FILE *f = NULL;			/* resource file */
	char buf[1024];			/* file read buffer */
	int line = 0;			/* line number */
	char *left, *right;		/* chunks of line */
	static int ncodes = 0;
	static int maxcodes = 0;
	static int *codes = NULL, *hints = NULL;
	int rc = 0;

	/* Find the resource or file. */
	if (r0 != CN)
		r = r_copy = NewString(r0);
	else {
		f = fopen(fn, "r");
		if (f == NULL) {
			xs_warning("Cannot open file: %s", fn);
			return;
		}
	}

	while ((r != CN)? (rc = split_dresource(&r, &left, &right)):
		          fgets(buf, sizeof(buf), f) != CN) {
		char *s;
		int ccode;
		int pkr;
		int hint;

		line++;

		/* Skip empty lines and comments. */
		if (r == CN) {
			s = buf;
			while (isspace(*s))
				s++;
			if (!*s || *s == '!' || *s == '#')
				continue;
		}

		/* Split. */
		if (rc < 0 ||
		    (r == CN && split_dresource(&s, &left, &right) < 0)) {
			popup_an_error("%s, line %d: syntax error",
			    fn, line);
			goto done;
		}

		pkr = parse_keydef(&left, &ccode, &hint);
		if (pkr == 0) {
			popup_an_error("%s, line %d: Missing <Key>",
			    fn, line);
			goto done;
		}
		if (pkr < 0) {
			popup_an_error("%s, line %d: %s",
			    fn, line, pk_errmsg[-1 - pkr]);
			goto done;
		}

		/* Accumulate keycodes. */
		ncodes = 0;
		do {
			if (++ncodes > maxcodes) {
				maxcodes = ncodes;
				codes = Realloc(codes, maxcodes * sizeof(int));
				hints = Realloc(hints, maxcodes * sizeof(int));
			}
			codes[ncodes - 1] = ccode;
			hints[ncodes - 1] = hint;
			pkr = parse_keydef(&left, &ccode, &hint);
			if (pkr < 0) {
				popup_an_error("%s, line %d: %s",
				    fn, line, pk_errmsg[-1 - pkr]);
				goto done;
			}
		} while (pkr != 0);

		/* Add it to the list. */
		hints[0] |= flags;
		add_keymap_entry(ncodes, codes, hints, fn, line, right);
	}

    done:
	Free(r_copy);
	if (f != NULL)
		fclose(f);
}

/* Multi-key keymap support. */
static struct keymap *current_match = NULL;
static int consumed = 0;
static char *ignore = "[ignore]";

/* Find the shortest keymap with a longer match than k. */
static struct keymap *
longer_match(struct keymap *k, int nc)
{
	struct keymap *j;
	struct keymap *shortest = NULL;

	for (j = master_keymap; j != NULL; j = j->next) {
		if (IS_INACTIVE(j))
			continue;
		if (j != k && j->ncodes > nc && !codecmp(j, k, nc)) {
			if (j->ncodes == nc+1)
				return j;
			if (shortest == NULL || j->ncodes < shortest->ncodes)
				shortest = j;
		}
	}
	return shortest;
}

/*
 * Helper function that returns a keymap action, sets the status line, and
 * traces the result.  
 *
 * If s is NULL, then this is a failed initial lookup.
 * If s is 'ignore', then this is a lookup in progress (k non-NULL) or a
 *  failed multi-key lookup (k NULL).
 * Otherwise, this is a successful lookup.
 */
static char *
status_ret(char *s, struct keymap *k)
{
	/* Set the compose indicator based on the new value of current_match. */
	if (k != NULL)
		status_compose(True, ' ', KT_STD);
	else
		status_compose(False, 0, KT_STD);

	if (s != NULL && s != ignore)
		trace_event(" %s:%d -> %s\n", current_match->file,
		    current_match->line, s);
	if ((current_match = k) == NULL)
		consumed = 0;
	return s;
}

/* Timeout for ambiguous keymaps. */
static struct keymap *timeout_match = NULL;
static unsigned long kto = 0L;

static void
key_timeout(void)
{
	trace_event("Timeout, using shortest keymap match\n");
	kto = 0L;
	current_match = timeout_match;
	push_keymap_action(status_ret(timeout_match->action, NULL));
	timeout_match = NULL;
}

static struct keymap *
ambiguous(struct keymap *k, int nc)
{
	struct keymap *j;

	if ((j = longer_match(k, nc)) != NULL) {
		trace_event(" ambiguous keymap match, shortest is %s:%d, "
		    "setting timeout\n", j->file, j->line);
		timeout_match = k;
		kto = AddTimeOut(500L, key_timeout);
	}
	return j;
}

/*
 * Look up an key in the keymap, return the matching action if there is one.
 *
 * This code implements the mutli-key lookup, by returning dummy actions for
 * partial matches.
 *
 * It also handles keyboards that generate ESC for the Meta or Alt key.
 */
char *
lookup_key(int code)
{
	struct keymap *j, *k;
	int n_shortest = 0;

	/* If there's a timeout pending, cancel it. */
	if (kto) {
		RemoveTimeOut(kto);
		kto = 0L;
		timeout_match = NULL;
	}

	/* If there's no match pending, find the shortest one. */
	if (current_match == NULL) {
		struct keymap *shortest = NULL;

		for (k = master_keymap; k != NULL; k = k->next) {
			if (IS_INACTIVE(k))
				continue;
			if (code == k->codes[0]) {
				if (k->ncodes == 1) {
					shortest = k;
					break;
				}
				if (shortest == NULL ||
				    k->ncodes < shortest->ncodes) {
					shortest = k;
					n_shortest++;
				}
			}
		}
		if (shortest != NULL) {
			current_match = shortest;
			consumed = 0;
		} else
			return NULL;
	}

	/* See if this character matches the next one we want. */
	if (code == current_match->codes[consumed]) {
		consumed++;
		if (consumed == current_match->ncodes) {
			/* Final match. */
			j = ambiguous(current_match, consumed);
			if (j == NULL)
				return status_ret(current_match->action, NULL);
			else
				return status_ret(ignore, j);
		} else {
			/* Keep looking. */
			trace_event(" partial keymap match in %s:%d %s\n",
			    current_match->file, current_match->line,
			    (n_shortest > 1)? " and other(s)": "");
			return status_ret(ignore, current_match);
		}
	}

	/* It doesn't.  Try for a better candidate. */
	for (k = master_keymap; k != NULL; k = k->next) {
		if (IS_INACTIVE(k))
			continue;
		if (k == current_match)
			continue;
		if (k->ncodes > consumed &&
		    !codecmp(k, current_match, consumed) &&
		    k->codes[consumed] == code) {
			consumed++;
			if (k->ncodes == consumed) {
				j = ambiguous(k, consumed);
				if (j == NULL) {
					current_match = k;
					return status_ret(k->action,
					    NULL);
				} else
					return status_ret(ignore, j);
			} else
				return status_ret(ignore, k);
		}
	}

	/* Complain. */
	beep();
	trace_event(" keymap lookup failure after partial match\n");
	return status_ret(ignore, NULL);
}

static struct {
	const char *name;
	int code;
} ncurses_key[] = {
	{ "BREAK",	KEY_BREAK },
	{ "DOWN",	KEY_DOWN },
	{ "UP",		KEY_UP },
	{ "LEFT",	KEY_LEFT },
	{ "RIGHT",	KEY_RIGHT },
	{ "HOME",	KEY_HOME },
	{ "BACKSPACE",	KEY_BACKSPACE },
	{ "F0",		KEY_F0 },
	{ "DL",		KEY_DL },
	{ "IL",		KEY_IL },
	{ "DC",		KEY_DC },
	{ "IC",		KEY_IC },
	{ "EIC",	KEY_EIC },
	{ "CLEAR",	KEY_CLEAR },
	{ "EOS",	KEY_EOS },
	{ "EOL",	KEY_EOL },
	{ "SF",		KEY_SF },
	{ "SR",		KEY_SR },
	{ "NPAGE",	KEY_NPAGE },
	{ "PPAGE",	KEY_PPAGE },
	{ "STAB",	KEY_STAB },
	{ "CTAB",	KEY_CTAB },
	{ "CATAB",	KEY_CATAB },
	{ "ENTER",	KEY_ENTER },
	{ "SRESET",	KEY_SRESET },
	{ "RESET",	KEY_RESET },
	{ "PRINT",	KEY_PRINT },
	{ "LL",		KEY_LL },
	{ "A1",		KEY_A1 },
	{ "A3",		KEY_A3 },
	{ "B2",		KEY_B2 },
	{ "C1",		KEY_C1 },
	{ "C3",		KEY_C3 },
	{ "BTAB",	KEY_BTAB },
	{ "BEG",	KEY_BEG },
	{ "CANCEL",	KEY_CANCEL },
	{ "CLOSE",	KEY_CLOSE },
	{ "COMMAND",	KEY_COMMAND },
	{ "COPY",	KEY_COPY },
	{ "CREATE",	KEY_CREATE },
	{ "END",	KEY_END },
	{ "EXIT",	KEY_EXIT },
	{ "FIND",	KEY_FIND },
	{ "HELP",	KEY_HELP },
	{ "MARK",	KEY_MARK },
	{ "MESSAGE",	KEY_MESSAGE },
	{ "MOVE",	KEY_MOVE },
	{ "NEXT",	KEY_NEXT },
	{ "OPEN",	KEY_OPEN },
	{ "OPTIONS",	KEY_OPTIONS },
	{ "PREVIOUS",	KEY_PREVIOUS },
	{ "REDO",	KEY_REDO },
	{ "REFERENCE",	KEY_REFERENCE },
	{ "REFRESH",	KEY_REFRESH },
	{ "REPLACE",	KEY_REPLACE },
	{ "RESTART",	KEY_RESTART },
	{ "RESUME",	KEY_RESUME },
	{ "SAVE",	KEY_SAVE },
	{ "SBEG",	KEY_SBEG },
	{ "SCANCEL",	KEY_SCANCEL },
	{ "SCOMMAND",	KEY_SCOMMAND },
	{ "SCOPY",	KEY_SCOPY },
	{ "SCREATE",	KEY_SCREATE },
	{ "SDC",	KEY_SDC },
	{ "SDL",	KEY_SDL },
	{ "SELECT",	KEY_SELECT },
	{ "SEND",	KEY_SEND },
	{ "SEOL",	KEY_SEOL },
	{ "SEXIT",	KEY_SEXIT },
	{ "SFIND",	KEY_SFIND },
	{ "SHELP",	KEY_SHELP },
	{ "SHOME",	KEY_SHOME },
	{ "SIC",	KEY_SIC },
	{ "SLEFT",	KEY_SLEFT },
	{ "SMESSAGE",	KEY_SMESSAGE },
	{ "SMOVE",	KEY_SMOVE },
	{ "SNEXT",	KEY_SNEXT },
	{ "SOPTIONS",	KEY_SOPTIONS },
	{ "SPREVIOUS",	KEY_SPREVIOUS },
	{ "SPRINT",	KEY_SPRINT },
	{ "SREDO",	KEY_SREDO },
	{ "SREPLACE",	KEY_SREPLACE },
	{ "SRIGHT",	KEY_SRIGHT },
	{ "SRSUME",	KEY_SRSUME },
	{ "SSAVE",	KEY_SSAVE },
	{ "SSUSPEND",	KEY_SSUSPEND },
	{ "SUNDO",	KEY_SUNDO },
	{ "SUSPEND",	KEY_SUSPEND },
	{ "UNDO",	KEY_UNDO },
	{ CN, 0 }
};

/* Look up a curses symbolic key. */
static int
lookup_ccode(const char *s)
{
	int i;
	unsigned long f;
	char *ptr;

	for (i = 0; ncurses_key[i].name != CN; i++) {
		if (!strcmp(s, ncurses_key[i].name))
			return ncurses_key[i].code;
	}
	if (s[0] == 'F' &&
	    (f = strtoul(s + 1, &ptr, 10)) < 64 &&
	    ptr != s + 1 &&
	    *ptr == '\0') {
		return KEY_F(f);
	}
	return -1;
}

/* Look up a curses key code. */
static const char *
lookup_cname(int ccode)
{
	int i;

	for (i = 0; ncurses_key[i].name != CN; i++) {
		if (ccode == ncurses_key[i].code)
			return ncurses_key[i].name;
	}
	if (ccode >= KEY_F0 && ccode < KEY_F0 + 64) {
		static char buf[10];

		(void) sprintf(buf, "F%d", ccode - KEY_F0);
		return buf;
	}
	return CN;
}

/* Read each of the keymaps specified by the keymap resource. */
void
keymap_init(void)
{
	char *s0, *s;
	char *comma;
	static Boolean initted = False;

	/* In case this is a subsequent call, wipe out the current keymap. */
	clear_keymap();

	read_keymap("base");
	if (appres.key_map != CN) {
		s = s0 = NewString(appres.key_map);
		while ((comma = strchr(s, ',')) != CN) {
			*comma = '\0';
			if (*s)
				read_keymap(s);
			s = comma + 1;
		}
		if (*s)
			read_keymap(s);
		Free(s0);
	}

	last_3270 = IN_3270;
	last_nvt = IN_ANSI;
	set_inactive();

	if (!initted) {
		register_schange(ST_3270_MODE, keymap_3270_mode);
		register_schange(ST_CONNECT, keymap_3270_mode);
		initted = True;
	}
}

/* Erase the current keymap. */
static void
clear_keymap(void)
{
	struct keymap *k, *next;

	for (k = master_keymap; k != NULL; k = next) {
		next = k->next;
		Free(k->codes);
		Free(k->hints);
		Free(k->file);
		Free(k->action);
		Free(k);
	}
	master_keymap = NULL;
	nextk = &master_keymap;
}

/* Set the inactive flags for the current keymap. */
static void
set_inactive(void)
{
	struct keymap *k;

	/* Clear the inactive flags and successors. */
	for (k = master_keymap; k != NULL; k = k->next) {
		k->hints[0] &= ~KM_INACTIVE;
		k->successor = NULL;
	}

	/* Turn off elements which have the wrong mode. */
	for (k = master_keymap; k != NULL; k = k->next) {
		/* If the mode is wrong, turn it off. */
		if ((!last_3270 && (k->hints[0] & KM_3270_ONLY)) ||
		    (!last_nvt  && (k->hints[0] & KM_NVT_ONLY))) {
			k->hints[0] |= KM_INACTIVE;
		}
	}

	/* Turn off elements with successors. */
	for (k = master_keymap; k != NULL; k = k->next) {
		struct keymap *j;
		struct keymap *last_j = NULL;

		if (IS_INACTIVE(k))
			continue;

		/* If it now has a successor, turn it off. */
		for (j = k->next; j != NULL; j = j->next) {
			if (!IS_INACTIVE(j) &&
			    k->ncodes == j->ncodes &&
			    !codecmp(k, j, k->ncodes)) {
				last_j = j;
			}
		}
		if (last_j != NULL) {
			k->successor = last_j;
			k->hints[0] |= KM_INACTIVE;
		}
	}
}

/* 3270/NVT mode change. */
static void
keymap_3270_mode(Boolean ignored unused)
{
	if (last_3270 != IN_3270 || last_nvt != IN_ANSI) {
		last_3270 = IN_3270;
		last_nvt = IN_ANSI;
		set_inactive();
	}
}

/*
 * Decode a key.
 * Accepts a hint as to which form was used to specify it, if it came from a
 * keymap definition.
 */
const char *
decode_key(int k, int hint, char *buf)
{
	const char *n, *n2;
	char *s = buf;

	/* Try ncurses first. */
	if (k > 0xff) {
		if ((n = lookup_cname(k)) != CN) {
			(void) sprintf(buf, "<Key>%s", n);
			return buf;
		} else
			return "?";
	}
	n = KeysymToString((KeySym)k);
	n2 = KeysymToString((KeySym)(k & 0x7f));
	if (n != CN) {
		if (hint) {
			if ((hint & KM_META) && n2 != CN && n2 != n)
				(void) sprintf(s, "Meta<Key>%s", n2);
			else
				(void) sprintf(s, "<Key>%s", n);
		} else {
			s += sprintf(s, "<Key>%s", n);
			if (n2 != CN && n2 != n)
				(void) sprintf(s, " (or Meta<Key>%s)", n2);
		}
		return buf;
	}
	if ((k & 0x7f) < ' ') {
		n = KeysymToString(k + '@');
		n2 = KeysymToString((k & 0x7f) + '@');

		if (hint) {
			if ((hint & KM_META) && n2 != CN && n2 != n)
				(void) sprintf(s, "Ctrl Meta<Key>%s", n2);
			else
				(void) sprintf(s, "Ctrl<Key>%s", n);
		} else {
			s += sprintf(s, "Ctrl<Key>%s", n);
			if (n2 != CN && n2 != n)
				(void) sprintf(s, " (or Ctrl Meta<Key>%s)", n2);
		}
		return buf;
	} else
		return "?";
}

/* Dump the current keymap. */
void
keymap_dump(void)
{
	struct keymap *k;

	for (k = master_keymap; k != NULL; k = k->next) {
		if (k->successor != NULL)
			action_output("[%s:%d]  (replaced by %s:%d)", k->file,
			    k->line, k->successor->file, k->successor->line);
		else if (!IS_INACTIVE(k)) {
			int i;
			char buf[1024];
			char *s = buf;
			char dbuf[128];

			for (i = 0; i < k->ncodes; i++) {
				s += sprintf(s, " %s",
				    decode_key(k->codes[i],
					(k->hints[i] & KM_HINTS) | KM_KEYMAP,
					    dbuf));
			}
			action_output("[%s:%d]%s: %s", k->file, k->line,
			    buf, k->action);
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1