/*
 * dumpkeys.c
 *
 * derived from version 0.81 - aeb@cwi.nl
 * Fix: escape quotes and backslashes in strings
 * Fix: after  dumpkeys > x; loadkeys x; dumpkeys > y
 *      the files x and y should be identical
 * Added: compose key support
 *
 * for 0.83: output a "+" for KT_LETTER
 * for 0.85: with -i option: also output MAX_DIACR
 * for 0.86: with -l option: also tell about synonyms
 * for 0.87: output "charset iso-8859-x" so that loadkeys
 *      can handle the output of dumpkeys -c
 */
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/types.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <sys/ioctl.h>
#include <string.h>
#include "ksyms.h"

#ifndef KT_LETTER
#define KT_LETTER KT_LATIN
#endif

#define VERSION "0.87"
static int fd;

int get_bind(u_char index, u_char table) {
	struct kbentry ke;

	ke.kb_index = index;
	ke.kb_table = table;
	if (ioctl(fd, KDGKBENT, (unsigned long)&ke)) {
		fprintf(stderr, "KDGKBENT at index %d in table %d: ",
			index, table);
		perror("");
		exit(1);
	}
	return ke.kb_value;
}

void print_keysym(int code, char numeric) {
	int t;
	int v;
	char *p;

	printf(" ");
	t = KTYP(code);
	v = KVAL(code);
	if (t == KT_LETTER) {
	        t = KT_LATIN;
		    printf("+");
	}
	if (
		!numeric &&
		t < syms_size &&
		v < syms[t].size &&
		(p = syms[t].table[v])[0]
	)
		printf("%-16s", p);
	else
		printf("0x%04x          ", code);
}

/* there's a race in here - we might destroy a change */
char valid_type(int t) {
	struct kbentry ke;
	int old_value;
	char status;

	ke.kb_index = 0;
	ke.kb_table = 0;
	if (ioctl(fd, KDGKBENT, (unsigned long)&ke))
		return 0;
	old_value = ke.kb_value;
	ke.kb_value = K(t, 0);
	status = ioctl(fd, KDSKBENT, (unsigned long)&ke) == 0;
	ke.kb_value = old_value;
	ioctl(fd, KDSKBENT, (unsigned long)&ke);
	return status;
}

/* there's a race in here */
u_char maximum_val(int t) {
	struct kbentry ke;
	int old_value;
	int i;

	ke.kb_index = 0;
	ke.kb_table = 0;
	if (ioctl(fd, KDGKBENT, (unsigned long)&ke))
		return 0;
	old_value = ke.kb_value;

	for (i = 0; i < 256; i++) {
		ke.kb_value = K(t, i);
		if (ioctl(fd, KDSKBENT, (unsigned long)&ke))
			break;
	}

	ke.kb_value = old_value;
	ioctl(fd, KDSKBENT, (unsigned long)&ke);

	return i - 1;
}


void show_short_info(void) {
	int i;

	printf("keycode range supported by kernel:           0 - %d\n",
		NR_KEYS - 1);
	printf("number of actions bindable to a key:         %d\n",
		NR_KEYMAPS);
	printf("ranges of action codes supported by kernel:\n");
	for (i = 0; valid_type(i); i++)
		printf("	0x%04x - 0x%04x\n",
			K(i, 0), K(i, maximum_val(i)));
	printf("number of function keys supported by kernel: %d\n", NR_FUNC);
	printf("total space available for function strings:  %d bytes\n",
		FUNC_BUFSIZE);
	printf("total space available for compose key definitions: %d bytes\n",
	        MAX_DIACR);
}

void dump_symbols(void) {
	int t;
	int v;
	char *p;

	printf("Symbols recognized by dumpkeys:\n(numeric value, symbol)\n\n");
	for (t = 0; t < syms_size; t++)
		for (v = 0; v < syms[t].size; v++)
			if ((p = syms[t].table[v])[0])
				printf("0x%04x\t%s\n", K(t, v), p);
	printf("\nThe following synonyms are recognized:\n\n");
	for (t = 0; t < syn_size; t++)
	    printf("%-15s for %s\n", synonyms[t].synonym,
		   synonyms[t].official_name);
	printf("
Recognized modifier names and their column numbers:

shift	%d
altgr	%d
control	%d
alt	%d
", (1 << KG_SHIFT), (1 << KG_ALTGR), (1 << KG_CTRL), (1 << KG_ALT));
}

void print_mod(int x) {
	if (x & (1 << KG_SHIFT))
		printf("shift   ");
	if (x & (1 << KG_ALTGR))
		printf("altgr   ");
	if (x & (1 << KG_CTRL))
		printf("control ");
	if (x & (1 << KG_ALT))
		printf("alt     ");
}

void print_bind(int buf[], int i, int j, char numeric) {
	printf("\t");
	print_mod(j);
	printf("keycode %3d =", i);
	print_keysym(buf[j], numeric);
	printf("\n");
}

void dump_keys(char full_table, char numeric) {
	int i, j, k;
	int buf[NR_KEYMAPS];
	int vmax;
	int vlast;
	int vcount;
	int count;
	int isletter, islatin, isasexpected;
	int typ0, val0;

	for (i = 0; i < NR_KEYS; i++) {
		for (j = 0; j < NR_KEYMAPS; j++)
			buf[j] = get_bind(i, j);

		typ0 = KTYP(buf[0]);
		val0 = KVAL(buf[0]);
		islatin = (typ0 == KT_LATIN || typ0 == KT_LETTER);
		isletter = (islatin &&
			    ((val0 >= 'A' && val0 <= 'Z') ||
			     (val0 >= 'a' && val0 <= 'z')));
		isasexpected = 0;
		if (isletter && NR_KEYMAPS == 16) {
		    for(j = 8; j < 16; j++)
		      if(KTYP(buf[j]) != KT_META ||
			 KVAL(buf[j]) != KVAL(buf[j-8]))
			goto unexpected;
		    for(j = 4; j < 8; j++)
		      if(KTYP(buf[j] != KT_LATIN) ||
			 KVAL(buf[j] != (val0 & ~96)))
			goto unexpected;
		    for(j = 2; j < 4; j++)
		      if(buf[j] != buf[j-2])
			goto unexpected;
		    if(KTYP(buf[1]) != typ0 || KVAL(buf[1]) != (val0 ^ 32))
		      goto unexpected;
		    isasexpected = 1;
		}
	      unexpected:

		for (j = NR_KEYMAPS - 1; j >= 0; j--)
			if (buf[j] != K_HOLE)
				break;

		vmax = j;
		vlast = j;
		vcount = 0;
		for ( ; j > 0; j--) {
			if (buf[j] != K_HOLE)
				vcount++;
			else if (vmax - j + 1 > 2 * vcount)
				vlast = j - 1;
		}
		if (vlast == 0 && vmax > 0)
		        if(isletter || !islatin)
			        vlast = 1;

		count = 0;
		for (j = 1; j < NR_KEYMAPS; j++)
			if (buf[0] != buf[j])
				count++;

		printf("keycode %3d =", i);
		if (full_table) {
		        for (j = 0; j < NR_KEYMAPS; j++)
			        print_keysym(buf[j], numeric);
			printf("\n");
		} else
		if (!islatin && count < NR_KEYMAPS / 2) {
			if (buf[0] != K_HOLE)
				print_keysym(buf[0], numeric);
			printf("\n");
			for (j = 1; j < NR_KEYMAPS; j++)
				if (buf[0] != buf[j])
					print_bind(buf, i, j, numeric);
		} else
		if (isasexpected) {
		        /* suppress the + for ordinary a-zA-Z */
			print_keysym(K(KT_LATIN, val0), numeric);
			printf("\n");
		} else {
			for (k = 0; k <= vlast; k++)
				print_keysym(buf[k], numeric);
			printf("\n");
			for (; k <= vmax; k++)
				if (buf[k] != K_HOLE)
					print_bind(buf, i, k, numeric);
		}
	}
}

void dump_funcs(void) {
	int i;
	struct kbsentry fbuf;
	char *p;

	for (i = 0; i < NR_FUNC; i++) {
		fbuf.kb_func = i;
		if (ioctl(fd, KDGKBSENT, (unsigned long)&fbuf)) {
			fprintf(stderr, "KDGKBSENT at index %d: ", i);
			perror("");
			exit(1);
		}
		printf("string %s = \"", syms[KT_FN].table[i]);
		for (p = fbuf.kb_string; *p; p++) {
		        if (*p == '"' || *p == '\\') {
			        putchar('\\'); putchar(*p);
			} else if (isgraph(*p))
				putchar(*p);
			else
				printf("\\%03o", *p);
		}
		printf("\"\n");
	}
}

#ifdef KDGKBDIACR
/* isgraph() does not know about iso-8859; printing the character
   unescaped makes the output easier to check. Maybe this should
   be an option. */
void
outchar (unsigned char c) {
        printf("'");
        printf((c == '\'' || c == '\\') ? "\\%c"
	       : (isgraph(c) || c >= 0200) ? "%c"
	       : "\\%03o", c);
	printf("'");
}

void dump_diacs(void) {
        struct kbdiacrs kd;
	int i;

	if(ioctl(fd, KDGKBDIACR, (unsigned long)&kd)) {
	    fprintf(stderr, "KDGKBDIACR failed\n");
	    perror("");
	    exit(1);
	}
	for (i = 0; i < kd.kb_cnt; i++) {
	        printf("compose ");
		outchar(kd.kbdiacr[i].diacr);
		printf(" ");
		outchar(kd.kbdiacr[i].base);
		printf(" to ");
		outchar(kd.kbdiacr[i].result);
		printf("\n");
	}
}
#endif        

void usage(void) {
	fprintf(stderr, "\
dumpkeys version " VERSION "\

usage: dumpkeys [options...]

valid options are:

	-h --help	display this help text
	-i --short-info	display information about keyboard driver
	-l --long-info	display above and symbols known to loadkeys
	-n --numeric	display keytable in hexadecimal notation
	-f --full-table	don't use short-hand notations
	   --funcs-only	display only the function key strings
	   --keys-only	display only key bindings
%s	-c --charset=iso-8859-{1,2,3,4,8}
			interpret character action codes to be from the
			specified character set
",
#ifdef KDGKBDIACR
"           --compose-only  display only compose key combinations\n"
#else
""
#endif
);
	exit(1);
}

int
main (int argc, char *argv[]) {
	const char *short_opts = "hilsnfc:";
	const struct option long_opts[] = {
		{ "help",	no_argument,		NULL, 'h' },
		{ "short-info",	no_argument,		NULL, 'i' },
		{ "long-info",	no_argument,		NULL, 'l' },
		{ "numeric",	no_argument,		NULL, 'n' },
		{ "full-table",	no_argument,		NULL, 'f' },
		{ "funcs-only",	no_argument,		NULL, 't' },
		{ "keys-only",	no_argument,		NULL, 'k' },
#ifdef KDGKBDIACR
		{ "compose-only",no_argument,           NULL, 'd' },
#endif
		{ "charset",	required_argument,	NULL, 'c' },
		{ NULL,	0, NULL, 0 }
	};
	int c;
	char long_info = 0;
	char short_info = 0;
	char numeric = 0;
	char full_table = 0;
	char funcs_only = 0;
	char keys_only = 0;
#ifdef KDGKBDIACR
	char diac_only = 0;
#endif
	char charset = 1;

	while ((c = getopt_long(argc, argv,
		short_opts, long_opts, NULL)) != -1) {
		switch (c) {
			case 'i':
				short_info = 1;
				break;
			case 's':
			case 'l':
				long_info = 1;
				break;
			case 'n':
				numeric = 1;
				break;
			case 'f':
				full_table = 1;
				break;
			case 't':
				funcs_only = 1;
				break;
			case 'k':
				keys_only = 1;
				break;
#ifdef KDGKBDIACR
			case 'd':
				diac_only = 1;
				break;
#endif
			case 'c':
				if (strlen(optarg) != 10 || /* iso-8859-x */
					strncmp(optarg, "iso-8859-", 9) ||
					!index("12348", optarg[9]))
					usage();
				charset = optarg[9] - '0';
				printf("charset \"%s\"\n", optarg);
				set_charset(optarg);
				break;
			case 'h':
			case '?':
				usage();
		}
	}

	if (optind < argc)
		usage();

	if ((fd = open("/dev/console", O_RDONLY)) < 0) {
		perror("when opening /dev/console");
		exit(1);
	}

	if (short_info || long_info) {
		show_short_info();
		if (long_info)
			dump_symbols();
		exit(0);
	}

#ifdef KDGKBDIACR
	if (!diac_only) {
#endif
	    if (!funcs_only)
		dump_keys(full_table, numeric);
	    if (!keys_only)
		dump_funcs();
#ifdef KDGKBDIACR
	}
	if (!funcs_only && !keys_only)
	        dump_diacs();
#endif

	exit(0);
}
