/* -- keyboard.c -- */

#include "x11vnc.h"
#include "xwrappers.h"
#include "xrecord.h"
#include "xinerama.h"
#include "pointer.h"
#include "userinput.h"
#include "win_utils.h"
#include "rates.h"
#include "cleanup.h"
#include "allowed_input_t.h"
#include "unixpw.h"
#include "v4l.h"
#include "linuxfb.h"
#include "uinput.h"
#include "macosx.h"
#include "screen.h"

void get_keystate(int *keystate);
void clear_modifiers(int init);
int track_mod_state(rfbKeySym keysym, rfbBool down, rfbBool set);
void clear_keys(void);
int get_autorepeat_state(void);
int get_initial_autorepeat_state(void);
void autorepeat(int restore, int bequiet);
void check_add_keysyms(void);
int add_keysym(KeySym keysym);
void delete_added_keycodes(int bequiet);
void initialize_remap(char *infile);
int sloppy_key_check(int key, rfbBool down, rfbKeySym keysym, int *new);
void switch_to_xkb_if_better(void);
char *short_kmbcf(char *str);
void initialize_allowed_input(void);
void initialize_modtweak(void);
void initialize_keyboard_and_pointer(void);
void get_allowed_input(rfbClientPtr client, allowed_input_t *input);
double typing_rate(double time_window, int *repeating);
int skip_cr_when_scaling(char *mode);
void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client);


static void delete_keycode(KeyCode kc, int bequiet);
static int count_added_keycodes(void);
static void add_remap(char *line);
static void add_dead_keysyms(char *str);
static void initialize_xkb_modtweak(void);
static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym,
    rfbClientPtr client);
static void tweak_mod(signed char mod, rfbBool down);
static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym,
    rfbClientPtr client);
static void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client);


/*
 * Routine to retreive current state keyboard.  1 means down, 0 up.
 */
void get_keystate(int *keystate) {
#if NO_X11
	RAWFB_RET_VOID
	if (!keystate) {}
	return;
#else
	int i, k;
	char keys[32];

	RAWFB_RET_VOID
	
	/* n.b. caller decides to X_LOCK or not. */
	XQueryKeymap(dpy, keys);
	for (i=0; i<32; i++) {
		char c = keys[i];

		for (k=0; k < 8; k++) {
			if (c & 0x1) {
				keystate[8*i + k] = 1;
			} else {
				keystate[8*i + k] = 0;
			}
			c = c >> 1;
		}
	}
#endif	/* NO_X11 */
}

/*
 * Try to KeyRelease any non-Lock modifiers that are down.
 */
void clear_modifiers(int init) {
#if NO_X11
	RAWFB_RET_VOID
	if (!init) {}
	return;
#else
	static KeyCode keycodes[256];
	static KeySym  keysyms[256];
	static char *keystrs[256];
	static int kcount = 0, first = 1;
	int keystate[256];
	int i, j, minkey, maxkey, syms_per_keycode;
	KeySym *keymap;
	KeySym keysym;
	KeyCode keycode;

	RAWFB_RET_VOID

	/* n.b. caller decides to X_LOCK or not. */
	if (first) {
		/*
		 * we store results in static arrays, to aid interrupted
		 * case, but modifiers could have changed during session...
		 */
		XDisplayKeycodes(dpy, &minkey, &maxkey);

		keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
		    &syms_per_keycode);

		for (i = minkey; i <= maxkey; i++) {
		    for (j = 0; j < syms_per_keycode; j++) {
			char *str;
			keysym = keymap[ (i - minkey) * syms_per_keycode + j ];
			if (keysym == NoSymbol || ! ismodkey(keysym)) {
				continue;
			}
			keycode = XKeysymToKeycode(dpy, keysym);
			if (keycode == NoSymbol) {
				continue;
			}
			keycodes[kcount] = keycode;
			keysyms[kcount]  = keysym;
			str = XKeysymToString(keysym);
			if (! str) str = "null";
			keystrs[kcount]  = strdup(str);
			kcount++;
		    }
		}
		XFree_wr((void *) keymap);
		first = 0;
	}
	if (init) {
		return;
	}
	
	get_keystate(keystate);
	for (i=0; i < kcount; i++) {
		keysym  = keysyms[i];
		keycode = keycodes[i];

		if (! keystate[(int) keycode]) {
			continue;
		}

		if (clear_mods) {
			rfbLog("clear_modifiers: up: %-10s (0x%x) "
			    "keycode=0x%x\n", keystrs[i], keysym, keycode);
		}
		XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime);
	}
	XFlush_wr(dpy);
#endif	/* NO_X11 */
}

static KeySym simple_mods[] = {
	XK_Shift_L, XK_Shift_R,
	XK_Control_L, XK_Control_R,
	XK_Meta_L, XK_Meta_R,
	XK_Alt_L, XK_Alt_R,
	XK_Super_L, XK_Super_R,
	XK_Hyper_L, XK_Hyper_R,
	XK_Mode_switch,
	NoSymbol
};
#define NSIMPLE_MODS 13

int track_mod_state(rfbKeySym keysym, rfbBool down, rfbBool set) {
	KeySym sym = (KeySym) keysym;	
	static rfbBool isdown[NSIMPLE_MODS];
	static int first = 1;
	int i, cnt = 0;

	/*
	 * simple tracking method for the modifier state without
	 * contacting the Xserver.  Ignores, of course what keys are
	 * pressed on the physical display.
	 *
	 * This is unrelated to our mod_tweak and xkb stuff.
	 * Just a simple thing for wireframe/scroll heuristics, 
	 * sloppy keys etc.
	 */

	if (first) {
		for (i=0; i<NSIMPLE_MODS; i++) {
			isdown[i] = FALSE;
		}
		first = 0;
	}

	if (sym != NoSymbol) {
		for (i=0; i<NSIMPLE_MODS; i++) {
			if (sym == simple_mods[i]) {
				if (set) {
					isdown[i] = down;
					return 1;
				} else {
					if (isdown[i]) {
						return 1;
					} else {
						return 0;
					}
				}
				break;
			}
		}
		/* not a modifier */
		if (set) {
			return 0;
		} else {
			return -1;
		}
	}

	/* called with NoSymbol: return number currently pressed: */
	for (i=0; i<NSIMPLE_MODS; i++) {
		if (isdown[i]) {
			cnt++;
		}
	}
	return cnt;
}

/*
 * Attempt to set all keys to Up position.  Can mess up typing at the
 * physical keyboard so use with caution.
 */
void clear_keys(void) {
	int k, keystate[256];

	RAWFB_RET_VOID
	
	/* n.b. caller decides to X_LOCK or not. */
	get_keystate(keystate);
	for (k=0; k<256; k++) {
		if (keystate[k]) {
			KeyCode keycode = (KeyCode) k;
			rfbLog("clear_keys: keycode=%d\n", keycode);
			XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime);
		}
	}
	XFlush_wr(dpy);
}
		
/*
 * Kludge for -norepeat option: we turn off keystroke autorepeat in
 * the X server when clients are connected.  This may annoy people at
 * the physical display.  We do this because 'key down' and 'key up'
 * user input events may be separated by 100s of ms due to screen fb
 * processing or link latency, thereby inducing the X server to apply
 * autorepeat when it should not.  Since the *client* is likely doing
 * keystroke autorepeating as well, it kind of makes sense to shut it
 * off if no one is at the physical display...
 */
static int save_auto_repeat = -1;

int get_autorepeat_state(void) {
#if NO_X11
	RAWFB_RET(0)
	return 0;
#else
	XKeyboardState kstate;

	RAWFB_RET(0)

	X_LOCK;
	XGetKeyboardControl(dpy, &kstate);
	X_UNLOCK;
	return kstate.global_auto_repeat;
#endif	/* NO_X11 */
}

int get_initial_autorepeat_state(void) {
	if (save_auto_repeat < 0) {
		save_auto_repeat = get_autorepeat_state();
	}
	return save_auto_repeat;
}

void autorepeat(int restore, int bequiet) {
#if NO_X11
	RAWFB_RET_VOID
	if (!restore || !bequiet) {}
	return;
#else
	int global_auto_repeat;
	XKeyboardControl kctrl;

	RAWFB_RET_VOID

	if (restore) {
		if (save_auto_repeat < 0) {
			return;		/* nothing to restore */
		}
		global_auto_repeat = get_autorepeat_state();
		X_LOCK;
		/* read state and skip restore if equal (e.g. no clients) */
		if (global_auto_repeat == save_auto_repeat) {
			X_UNLOCK;
			return;
		}

		kctrl.auto_repeat_mode = save_auto_repeat;
		XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl);
		XFlush_wr(dpy);
		X_UNLOCK;

		if (! bequiet && ! quiet) {
			rfbLog("Restored X server key autorepeat to: %d\n",
			    save_auto_repeat);
		}
	} else {
		global_auto_repeat = get_autorepeat_state();
		if (save_auto_repeat < 0) {
			/*
			 * we only remember the state at startup
			 * to avoid confusing ourselves later on.
			 */
			save_auto_repeat = global_auto_repeat;
		}

		X_LOCK;
		kctrl.auto_repeat_mode = AutoRepeatModeOff;
		XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl);
		XFlush_wr(dpy);
		X_UNLOCK;

		if (! bequiet && ! quiet) {
			rfbLog("Disabled X server key autorepeat.\n");
			if (no_repeat_countdown >= 0) {
				rfbLog("  to force back on run: 'xset r on' (%d "
				    "times)\n", no_repeat_countdown+1);
			}
		}
	}
#endif	/* NO_X11 */
}

/*
 * We periodically delete any keysyms we have added, this is to
 * lessen our effect on the X server state if we are terminated abruptly
 * and cannot clear them and also to clear out any strange little used
 * ones that would just fill up the keymapping. 
 */
void check_add_keysyms(void) {
	static time_t last_check = 0;
	int clear_freq = 300, quiet = 1, count; 
	time_t now = time(NULL);

	if (unixpw_in_progress) return;

	if (now > last_check + clear_freq) {
		count = count_added_keycodes();
		/*
		 * only really delete if they have not typed recently
		 * and we have added 8 or more.
		 */
		if (now > last_keyboard_input + 5 && count >= 8) {
			X_LOCK;
			delete_added_keycodes(quiet);
			X_UNLOCK;
		}
		last_check = now;
	}
}

static KeySym added_keysyms[0x100];

/* these are just for rfbLog messages: */
static KeySym alltime_added_keysyms[1024];
static int alltime_len = 1024;
static int alltime_num = 0;

int add_keysym(KeySym keysym) {
	static int first = 1;
	int n;
#if NO_X11
	if (first) {
		for (n=0; n < 0x100; n++) {
			added_keysyms[n] = NoSymbol;
		}
		first = 0;
	}
	RAWFB_RET(0)
	if (!keysym) {}
	return 0;
#else
	int minkey, maxkey, syms_per_keycode;
	int kc, ret = 0;
	KeySym *keymap;

	if (first) {
		for (n=0; n < 0x100; n++) {
			added_keysyms[n] = NoSymbol;
		}
		first = 0;
	}

	RAWFB_RET(0)

	if (keysym == NoSymbol) {
		return 0;
	}
	/* there can be a race before MappingNotify */
	for (n=0; n < 0x100; n++) {
		if (added_keysyms[n] == keysym) {
			return n;
		}
	}

	XDisplayKeycodes(dpy, &minkey, &maxkey);
	keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
	    &syms_per_keycode);

	for (kc = minkey+1; kc <= maxkey; kc++) {
		int i, j, didmsg = 0, is_empty = 1;
		char *str;
		KeySym new[8];

		for (n=0; n < syms_per_keycode; n++) {
			if (keymap[ (kc-minkey) * syms_per_keycode + n]
			    != NoSymbol) {
				is_empty = 0;
				break;
			}
		}
		if (! is_empty) {
			continue;
		}

		for (i=0; i<8; i++) {
			new[i] = NoSymbol;
		}
		if (add_keysyms == 2) {
			new[0] = keysym;	/* XXX remove me */
		} else {
			for(i=0; i < syms_per_keycode; i++) {
				new[i] = keysym;
				if (i >= 7) break;
			}
		}

		XChangeKeyboardMapping(dpy, kc, syms_per_keycode,
		    new, 1);

		if (alltime_num >= alltime_len) {
			didmsg = 1;	/* something weird */
		} else {
			for (j=0; j<alltime_num; j++) {
				if (alltime_added_keysyms[j] == keysym) {
					didmsg = 1;
					break;
				}
			}
		}
		if (! didmsg) {
			str = XKeysymToString(keysym);
			rfbLog("added missing keysym to X display: %03d "
			    "0x%x \"%s\"\n", kc, keysym, str ? str : "null");

			if (alltime_num < alltime_len) {
				alltime_added_keysyms[alltime_num++] = keysym;
			}
		}

		XFlush_wr(dpy);
		added_keysyms[kc] = keysym;
		ret = kc;
		break;
	}
	XFree_wr(keymap);
	return ret;
#endif	/* NO_X11 */
}

static void delete_keycode(KeyCode kc, int bequiet) {
#if NO_X11
	RAWFB_RET_VOID
	if (!kc || !bequiet) {}
	return;
#else
	int minkey, maxkey, syms_per_keycode, i;
	KeySym *keymap;
	KeySym ksym, new[8];
	char *str;

	RAWFB_RET_VOID

	XDisplayKeycodes(dpy, &minkey, &maxkey);
	keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
	    &syms_per_keycode);

	for (i=0; i<8; i++) {
		new[i] = NoSymbol;
	}

	XChangeKeyboardMapping(dpy, kc, syms_per_keycode, new, 1);

	if (! bequiet && ! quiet) {
		ksym = XKeycodeToKeysym(dpy, kc, 0);
		str = XKeysymToString(ksym);
		rfbLog("deleted keycode from X display: %03d 0x%x \"%s\"\n",
		    kc, ksym, str ? str : "null");
	}

	XFree_wr(keymap);
	XFlush_wr(dpy);
#endif	/* NO_X11 */
}

static int count_added_keycodes(void) {
	int kc, count = 0;
	for (kc = 0; kc < 0x100; kc++) {
		if (added_keysyms[kc] != NoSymbol) {
			count++;
		}
	}
	return count;
}

void delete_added_keycodes(int bequiet) {
	int kc;
	for (kc = 0; kc < 0x100; kc++) {
		if (added_keysyms[kc] != NoSymbol) {
			delete_keycode(kc, bequiet);
			added_keysyms[kc] = NoSymbol;
		}
	}
}

/*
 * The following is for an experimental -remap option to allow the user
 * to remap keystrokes.  It is currently confusing wrt modifiers...
 */
typedef struct keyremap {
	KeySym before;
	KeySym after;
	int isbutton;
	struct keyremap *next;
} keyremap_t;

static keyremap_t *keyremaps = NULL;

static void add_remap(char *line) {
	char str1[256], str2[256];
	KeySym ksym1, ksym2;
	int isbtn = 0;
	unsigned int i;
	static keyremap_t *current = NULL;
	keyremap_t *remap;

	if (sscanf(line, "%s %s", str1, str2) != 2) {
		rfbLogEnable(1);
		rfbLog("remap: invalid line: %s\n", line);
		clean_up_exit(1);
	}
	if (sscanf(str1, "0x%x", &i) == 1) {
		ksym1 = (KeySym) i;
	} else {
		ksym1 = XStringToKeysym(str1);
	}
	if (sscanf(str2, "0x%x", &i) == 1) {
		ksym2 = (KeySym) i;
	} else {
		ksym2 = XStringToKeysym(str2);
	}
	if (ksym2 == NoSymbol) {
		if (sscanf(str2, "Button%u", &i) == 1) {
			ksym2 = (KeySym) i;
			isbtn = 1; 
		}
	}
	if (ksym1 == NoSymbol || ksym2 == NoSymbol) {
		if (strcasecmp(str2, "NoSymbol") && strcasecmp(str2, "None")) {
			rfbLog("warning: skipping invalid remap line: %s", line);
			return;
		}
	}
	remap = (keyremap_t *) malloc((size_t) sizeof(keyremap_t));
	remap->before = ksym1;
	remap->after  = ksym2;
	remap->isbutton = isbtn;
	remap->next   = NULL;

	rfbLog("remapping: (%s, 0x%x) -> (%s, 0x%x) isbtn=%d\n", str1,
	    ksym1, str2, ksym2, isbtn);

	if (keyremaps == NULL) {
		keyremaps = remap;
	} else {
		current->next = remap;
	}
	current = remap;
}

static void add_dead_keysyms(char *str) {
	char *p, *q;
	int i;
	char *list[] = {
		"g grave dead_grave",
		"a acute dead_acute",
		"c asciicircum dead_circumflex",
		"t asciitilde dead_tilde",
		"m macron dead_macron",
		"b breve dead_breve",
		"D abovedot dead_abovedot",
		"d diaeresis dead_diaeresis",
		"o degree dead_abovering",
		"A doubleacute dead_doubleacute",
		"r caron dead_caron",
		"e cedilla dead_cedilla",
/*		"x XXX-ogonek dead_ogonek", */
/*		"x XXX-belowdot dead_belowdot", */
/*		"x XXX-hook dead_hook", */
/*		"x XXX-horn dead_horn", */
		NULL
	};

	p = str;

	while (*p != '\0') {
		if (isspace((unsigned char) (*p))) {
			*p = '\0';
		}
		p++;
	}

	if (!strcmp(str, "DEAD")) {
		for (i = 0; list[i] != NULL; i++) {
			p = list[i] + 2;
			add_remap(p);
		}
	} else if (!strcmp(str, "DEAD=missing")) {
		for (i = 0; list[i] != NULL; i++) {
			KeySym ksym, ksym2;
			int inmap = 0;

			p = strdup(list[i] + 2);
			q = strchr(p, ' ');
			if (q == NULL) {
				free(p);
				continue;
			}
			*q = '\0';
			ksym = XStringToKeysym(p);
			*q = ' ';
			if (ksym == NoSymbol) {
				free(p);
				continue;
			}
			if (XKeysymToKeycode(dpy, ksym)) {
				inmap = 1;
			}
#if LIBVNCSERVER_HAVE_XKEYBOARD
			if (! inmap && xkb_present && dpy) {
				int kc, grp, lvl;
				for (kc = 0; kc < 0x100; kc++) {
				    for (grp = 0; grp < 4; grp++) {
					for (lvl = 0; lvl < 8; lvl++) {
						ksym2 = XkbKeycodeToKeysym(dpy,
						    kc, grp, lvl);
						if (ksym2 == NoSymbol) {
							continue;
						}
						if (ksym2 == ksym) {
							inmap = 1;
							break;
						}
					}
				    }
				}
			}
#else
			if ((ksym2 = 0)) {}
#endif
			if (! inmap) {
				add_remap(p);
			}
			free(p);
		}
	} else if ((p = strchr(str, '=')) != NULL) {
		while (*p != '\0') {
			for (i = 0; list[i] != NULL; i++) {
				q = list[i];
				if (*p == *q) {
					q += 2;
					add_remap(q);
					break;
				}
			}
			p++;
		}
	}
}

/*
 * process the -remap string (file or mapping string)
 */
void initialize_remap(char *infile) {
	FILE *in;
	char *p, *q, line[256];

	if (keyremaps != NULL) {
		/* free last remapping */
		keyremap_t *next_remap, *curr_remap = keyremaps;
		while (curr_remap != NULL) {
			next_remap = curr_remap->next;
			free(curr_remap);
			curr_remap = next_remap;
		}
		keyremaps = NULL;
	}
	if (infile == NULL || *infile == '\0') {
		/* just unset remapping */
		return;
	}

	in = fopen(infile, "r"); 
	if (in == NULL) {
		/* assume cmd line key1-key2,key3-key4 */
		if (strstr(infile, "DEAD") == infile) {
			;
		} else if (!strchr(infile, '-')) {
			rfbLogEnable(1);
			rfbLog("remap: cannot open: %s\n", infile);
			rfbLogPerror("fopen");
			clean_up_exit(1);
		}
		if ((in = tmpfile()) == NULL) {
			rfbLogEnable(1);
			rfbLog("remap: cannot open tmpfile for %s\n", infile);
			rfbLogPerror("tmpfile");
			clean_up_exit(1);
		}

		/* copy in the string to file format */
		p = infile;
		while (*p) {
			if (*p == '-') {
				fprintf(in, " ");
			} else if (*p == ',' || *p == ' ' ||  *p == '\t') {
				fprintf(in, "\n");
			} else {
				fprintf(in, "%c", *p);
			}
			p++;
		}
		fprintf(in, "\n");
		fflush(in);	
		rewind(in);
	}

	while (fgets(line, 256, in) != NULL) {
		p = lblanks(line);
		if (*p == '\0') {
			continue;
		}
		if (strchr(line, '#')) {
			continue;
		}

		if (strstr(p, "DEAD") == p)  {
			add_dead_keysyms(p);
			continue;
		}
		if ((q = strchr(line, '-')) != NULL) {
			/* allow Keysym1-Keysym2 notation */
			*q = ' ';	
		}
		add_remap(p);
	}
	fclose(in);
}

/*
 * preliminary support for using the Xkb (XKEYBOARD) extension for handling
 * user input.  inelegant, slow, and incomplete currently... but initial
 * tests show it is useful for some setups.
 */
typedef struct keychar {
	KeyCode code;
	int group;
	int level;
} keychar_t;

/* max number of key groups and shift levels we consider */
#define GRP 4
#define LVL 8
static int lvl_max, grp_max, kc_min, kc_max;
static KeySym xkbkeysyms[0x100][GRP][LVL];
static unsigned int xkbstate[0x100][GRP][LVL];
static unsigned int xkbignore[0x100][GRP][LVL];
static unsigned int xkbmodifiers[0x100][GRP][LVL];
static int multi_key[0x100], mode_switch[0x100], skipkeycode[0x100];
static int shift_keys[0x100];

/*
 * for trying to order the keycodes to avoid problems, note the
 * *first* keycode bound to it.  kc_vec will be a permutation
 * of 1...256 to get them in the preferred order.
 */
static int kc_vec[0x100];
static int kc1_shift, kc1_control, kc1_caplock, kc1_alt;
static int kc1_meta, kc1_numlock, kc1_super, kc1_hyper;
static int kc1_mode_switch, kc1_iso_level3_shift, kc1_multi_key;
	
int sloppy_key_check(int key, rfbBool down, rfbKeySym keysym, int *new) {
	if (!sloppy_keys) {
		return 0;
	}

	RAWFB_RET(0)
#if NO_X11
	if (!key || !down || !keysym || !new) {}
	return 0;
#else
	
	if (!down && !keycode_state[key] && !IsModifierKey(keysym)) {
		int i, cnt = 0, downkey = -1;
		int nmods_down = track_mod_state(NoSymbol, FALSE, FALSE);
		int mods_down[256];

		if (nmods_down) {
			/* tracking to skip down modifier keycodes. */
			for(i=0; i<256; i++) {
				mods_down[i] = 0;
			}
			i = 0;
			while (simple_mods[i] != NoSymbol) {
				KeySym ksym = simple_mods[i];
				KeyCode k = XKeysymToKeycode(dpy, ksym);
				if (k != NoSymbol && keycode_state[(int) k]) {
					mods_down[(int) k] = 1;
				}
				
				i++;
			}
		}
		/*
		 * the keycode is already up... look for a single one
		 * (non modifier) that is down
		 */
		for (i=0; i<256; i++) {
			if (keycode_state[i]) {
				if (nmods_down && mods_down[i]) {
					continue;
				}
				cnt++;
				downkey = i;
			}
		}
		if (cnt == 1) {
			if (debug_keyboard) {
				fprintf(stderr, "    sloppy_keys: %d/0x%x "
				    "-> %d/0x%x  (nmods: %d)\n", (int) key,
				    (int) key, downkey, downkey, nmods_down);
			}
			*new = downkey;
			return 1;
		}
	}
	return 0;
#endif	/* NO_X11 */
}

#if !LIBVNCSERVER_HAVE_XKEYBOARD || SKIP_XKB

/* empty functions for no xkb */
static void initialize_xkb_modtweak(void) {}
static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym,
    rfbClientPtr client) {
	if (!client || !down || !keysym) {} /* unused vars warning: */
}
void switch_to_xkb_if_better(void) {}

#else

void switch_to_xkb_if_better(void) {
	KeySym keysym, *keymap;
	int miss_noxkb[256], miss_xkb[256], missing_noxkb = 0, missing_xkb = 0;
	int i, j, k, n, minkey, maxkey, syms_per_keycode;
	int syms_gt_4 = 0;
	int kc, grp, lvl;

	/* non-alphanumeric on us keyboard */
	KeySym must_have[] = {
		XK_exclam,
		XK_at,
		XK_numbersign,
		XK_dollar,
		XK_percent,
/*		XK_asciicircum, */
		XK_ampersand,
		XK_asterisk,
		XK_parenleft,
		XK_parenright,
		XK_underscore,
		XK_plus,
		XK_minus,
		XK_equal,
		XK_bracketleft,
		XK_bracketright,
		XK_braceleft,
		XK_braceright,
		XK_bar,
		XK_backslash,
		XK_semicolon,
/*		XK_apostrophe, */
		XK_colon,
		XK_quotedbl,
		XK_comma,
		XK_period,
		XK_less,
		XK_greater,
		XK_slash,
		XK_question,
/*		XK_asciitilde, */
/*		XK_grave, */
		NoSymbol
	};

	if (! use_modifier_tweak || got_noxkb) {
		return;
	}
	if (use_xkb_modtweak) {
		/* already using it */
		return;
	}
	RAWFB_RET_VOID

	XDisplayKeycodes(dpy, &minkey, &maxkey);

	keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
	    &syms_per_keycode);

	/* handle alphabetic char with only one keysym (no upper + lower) */
	for (i = minkey; i <= maxkey; i++) {
		KeySym lower, upper;
		/* 2nd one */
		keysym = keymap[(i - minkey) * syms_per_keycode + 1];
		if (keysym != NoSymbol) {
			continue;
		}
		/* 1st one */
		keysym = keymap[(i - minkey) * syms_per_keycode + 0];
		if (keysym == NoSymbol) {
			continue;
		}
		XConvertCase(keysym, &lower, &upper);
		if (lower != upper) {
			keymap[(i - minkey) * syms_per_keycode + 0] = lower;
			keymap[(i - minkey) * syms_per_keycode + 1] = upper;
		}
	}

	k = 0;
	while (must_have[k] != NoSymbol) {
		int gotit = 0;
		KeySym must = must_have[k];
		for (i = minkey; i <= maxkey; i++) {
		    for (j = 0; j < syms_per_keycode; j++) {
			keysym = keymap[(i-minkey) * syms_per_keycode + j];
			if (j >= 4) {
			    if (k == 0 && keysym != NoSymbol) {
				/* for k=0 count the high keysyms */
				syms_gt_4++;
				if (debug_keyboard > 1) {
					char *str = XKeysymToString(keysym);
					fprintf(stderr, "- high keysym mapping"
					    ": at %3d j=%d "
					    "'%s'\n", i, j, str ? str : "null");
				}
			    }
			    continue;
			}
			if (keysym == must) {
				if (debug_keyboard > 1) {
					char *str = XKeysymToString(must);
					fprintf(stderr, "- at %3d j=%d found "
					    "'%s'\n", i, j, str ? str : "null");
				}
				/* n.b. do not break, see syms_gt_4 above. */
				gotit = 1;
			}
		    }
		}
		if (! gotit) {
			if (debug_keyboard > 1) {
				char *str = XKeysymToString(must);
				KeyCode kc = XKeysymToKeycode(dpy, must);
				fprintf(stderr, "- did not find 0x%lx '%s'\t"
				    "Ks2Kc: %d\n", must, str ? str:"null", kc); 
				if (kc != None) {
					int j2;
					for(j2=0; j2<syms_per_keycode; j2++) {
						keysym = keymap[(kc-minkey) *
						    syms_per_keycode + j2];
						fprintf(stderr, "  %d=0x%lx",
						    j2, keysym);
					}
					fprintf(stderr, "\n");
				}
			}
			missing_noxkb++;
			miss_noxkb[k] = 1;
		} else {
			miss_noxkb[k] = 0;
		}
		k++;
	}
	n = k;

	XFree_wr(keymap);
	if (missing_noxkb == 0 && syms_gt_4 >= 8) {
		if (! raw_fb_str) {
			rfbLog("\n");
			rfbLog("XKEYBOARD: number of keysyms per keycode %d is greater\n", syms_per_keycode);
			rfbLog("  than 4 and %d keysyms are mapped above 4.\n", syms_gt_4);
			rfbLog("  Automatically switching to -xkb mode.\n");
			rfbLog("  If this makes the key mapping worse you can\n");
			rfbLog("  disable it with the \"-noxkb\" option.\n");
			rfbLog("  Also, remember \"-remap DEAD\" for accenting characters.\n");
		}

		use_xkb_modtweak = 1;
		return;

	} else if (missing_noxkb == 0) {
		if (! raw_fb_str) {
			rfbLog("\n");
			rfbLog("XKEYBOARD: all %d \"must have\" keysyms accounted for.\n", n);
			rfbLog("  Not automatically switching to -xkb mode.\n");
			rfbLog("  If some keys still cannot be typed, try using -xkb.\n");
			rfbLog("  Also, remember \"-remap DEAD\" for accenting characters.\n");
		}
		return;
	}

	for (k=0; k<n; k++) {
		miss_xkb[k] = 1;
	}

	for (kc = 0; kc < 0x100; kc++) {
	    for (grp = 0; grp < GRP; grp++) {
		for (lvl = 0; lvl < LVL; lvl++) {
			/* look up the Keysym, if any */
			keysym = XkbKeycodeToKeysym(dpy, kc, grp, lvl);
			if (keysym == NoSymbol) {
				continue;
			}
			for (k=0; k<n; k++) {
				if (keysym == must_have[k]) {
					miss_xkb[k] = 0;
				}
			}
		}
	    }
	}

	for (k=0; k<n; k++) {
		if (miss_xkb[k]) {
			missing_xkb++;
		}
	}

	rfbLog("\n");
	if (missing_xkb < missing_noxkb) {
		rfbLog("XKEYBOARD:\n");
		rfbLog("Switching to -xkb mode to recover these keysyms:\n");
	} else {
		rfbLog("XKEYBOARD: \"must have\" keysyms better accounted"
		    " for\n");
		rfbLog("under -noxkb mode: not switching to -xkb mode:\n");
	}

	rfbLog("   xkb  noxkb   Keysym  (\"X\" means present)\n");
	rfbLog("   ---  -----   -----------------------------\n");
	for (k=0; k<n; k++) {
		char *xx, *xn, *name;

		keysym = must_have[k];
		if (keysym == NoSymbol) {
			continue;
		}
		if (!miss_xkb[k] && !miss_noxkb[k]) {
			continue;
		}
		if (miss_xkb[k]) {
			xx = "   ";
		} else {
			xx = " X ";
		}
		if (miss_noxkb[k]) {
			xn = "   ";
		} else {
			xn = " X ";
		}
		name = XKeysymToString(keysym);
		rfbLog("   %s  %s     0x%lx  %s\n", xx, xn, keysym,
		    name ? name : "null");
	}
	rfbLog("\n");

	if (missing_xkb < missing_noxkb) {
		rfbLog("  If this makes the key mapping worse you can\n");
		rfbLog("  disable it with the \"-noxkb\" option.\n");
		rfbLog("\n");

		use_xkb_modtweak = 1;

	} else {
		rfbLog("  If some keys still cannot be typed, try using"
		    " -xkb.\n");
		rfbLog("  Also, remember \"-remap DEAD\" for accenting"
		    " characters.\n");
	}
}

/* sets up all the keymapping info via Xkb API */

static void initialize_xkb_modtweak(void) {
	KeySym ks;
	int kc, grp, lvl, k;
	unsigned int state;

/*
 * Here is a guide:

Workarounds arrays:

multi_key[]     indicates which keycodes have Multi_key (Compose)
                bound to them.
mode_switch[]   indicates which keycodes have Mode_switch (AltGr)
                bound to them.
shift_keys[]    indicates which keycodes have Shift bound to them.
skipkeycode[]   indicates which keycodes are to be skipped
                for any lookups from -skip_keycodes option. 

Groups and Levels, here is an example:
                                                                  
      ^          --------                                      
      |      L2 | A   AE |                                      
    shift       |        |                                      
    level    L1 | a   ae |                                      
                 --------                                      
                  G1  G2                                        
                                                                
                  group ->                                      

Traditionally this it all a key could do.  L1 vs. L2 selected via Shift
and G1 vs. G2 selected via Mode_switch.  Up to 4 Keysyms could be bound
to a key.  See initialize_modtweak() for an example of using that type
of keymap from XGetKeyboardMapping().

Xkb gives us up to 4 groups and 63 shift levels per key, with the
situation being potentially different for each key.  This is complicated,
and I don't claim to understand it all, but in the following we just think
of ranging over the group and level indices as covering all of the cases.
This gives us an accurate view of the keymap.  The main tricky part
is mapping between group+level and modifier state.

On current linux/XFree86 setups (Xkb is enabled by default) the
information from XGetKeyboardMapping() (evidently the compat map)
is incomplete and inaccurate, so we are really forced to use the
Xkb API.

xkbkeysyms[]      For a (keycode,group,level) holds the KeySym (0 for none)
xkbstate[]        For a (keycode,group,level) holds the corresponding
                  modifier state needed to get that KeySym
xkbignore[]       For a (keycode,group,level) which modifiers can be
                  ignored (the 0 bits can be ignored).
xkbmodifiers[]    For the KeySym bound to this (keycode,group,level) store
                  the modifier mask.   
 *
 */

	RAWFB_RET_VOID

	/* initialize all the arrays: */
	for (kc = 0; kc < 0x100; kc++) {
		multi_key[kc] = 0;
		mode_switch[kc] = 0;
		skipkeycode[kc] = 0;
		shift_keys[kc] = 0;

		for (grp = 0; grp < GRP; grp++) {
			for (lvl = 0; lvl < LVL; lvl++) {
				xkbkeysyms[kc][grp][lvl] = NoSymbol;
				xkbmodifiers[kc][grp][lvl] = -1;
				xkbstate[kc][grp][lvl] = -1;
			}
		}
	}

	/*
	 * the array is 256*LVL*GRP, but we can make the searched region
	 * smaller by computing the actual ranges.
	 */
	lvl_max = 0;
	grp_max = 0;
	kc_max = 0;
	kc_min = 0x100;

	/* first keycode for a modifier type (multi_key too) */
	kc1_shift = -1;
	kc1_control = -1;
	kc1_caplock = -1;
	kc1_alt = -1;
	kc1_meta = -1;
	kc1_numlock = -1;
	kc1_super = -1;
	kc1_hyper = -1;
	kc1_mode_switch = -1;
	kc1_iso_level3_shift = -1;
	kc1_multi_key = -1;

	/*
	 * loop over all possible (keycode, group, level) triples
	 * and record what we find for it:
	 */
	if (debug_keyboard > 1) {
		rfbLog("initialize_xkb_modtweak: XKB keycode -> keysyms "
		    "mapping info:\n");
	}
	for (kc = 0; kc < 0x100; kc++) {
	    for (grp = 0; grp < GRP; grp++) {
		for (lvl = 0; lvl < LVL; lvl++) {
			unsigned int ms, mods;
			int state_save = -1, mods_save = -1;
			KeySym ks2;

			/* look up the Keysym, if any */
			ks = XkbKeycodeToKeysym(dpy, kc, grp, lvl);
			xkbkeysyms[kc][grp][lvl] = ks;

			/* if no Keysym, on to next */
			if (ks == NoSymbol) {
				continue;
			}
			/*
			 * for various workarounds, note where these special
			 * keys are bound to.
			 */
			if (ks == XK_Multi_key) {
				multi_key[kc] = lvl+1;
			}
			if (ks == XK_Mode_switch) {
				mode_switch[kc] = lvl+1;
			}
			if (ks == XK_Shift_L || ks == XK_Shift_R) {
				shift_keys[kc] = lvl+1;
			}

			if (ks == XK_Shift_L || ks == XK_Shift_R) {
				if (kc1_shift == -1) {
					kc1_shift = kc;
				}
			}
			if (ks == XK_Control_L || ks == XK_Control_R) {
				if (kc1_control == -1) {
					kc1_control = kc;
				}
			}
			if (ks == XK_Caps_Lock || ks == XK_Caps_Lock) {
				if (kc1_caplock == -1) {
					kc1_caplock = kc;
				}
			}
			if (ks == XK_Alt_L || ks == XK_Alt_R) {
				if (kc1_alt == -1) {
					kc1_alt = kc;
				}
			}
			if (ks == XK_Meta_L || ks == XK_Meta_R) {
				if (kc1_meta == -1) {
					kc1_meta = kc;
				}
			}
			if (ks == XK_Num_Lock) {
				if (kc1_numlock == -1) {
					kc1_numlock = kc;
				}
			}
			if (ks == XK_Super_L || ks == XK_Super_R) {
				if (kc1_super == -1) {
					kc1_super = kc;
				}
			}
			if (ks == XK_Hyper_L || ks == XK_Hyper_R) {
				if (kc1_hyper == -1) {
					kc1_hyper = kc;
				}
			}
			if (ks == XK_Mode_switch) {
				if (kc1_mode_switch == -1) {
					kc1_mode_switch = kc;
				}
			}
			if (ks == XK_ISO_Level3_Shift) {
				if (kc1_iso_level3_shift == -1) {
					kc1_iso_level3_shift = kc;
				}
			}
			if (ks == XK_Multi_key) {	/* not a modifier.. */
				if (kc1_multi_key == -1) {
					kc1_multi_key = kc;
				}
			}

			/*
			 * record maximum extent for group/level indices
			 * and keycode range:
			 */
			if (grp > grp_max) {
				grp_max = grp;
			}
			if (lvl > lvl_max) {
				lvl_max = lvl;
			}
			if (kc > kc_max) {
				kc_max = kc;
			}
			if (kc < kc_min) {
				kc_min = kc;
			}

			/*
			 * lookup on *keysym* (i.e. not kc, grp, lvl)
			 * and get the modifier mask.  this is 0 for
			 * most keysyms, only non zero for modifiers.
			 */
			ms = XkbKeysymToModifiers(dpy, ks);
			xkbmodifiers[kc][grp][lvl] = ms;

			/*
			 * Amusing heuristic (may have bugs).  There are
			 * 8 modifier bits, so 256 possible modifier
			 * states.  We loop over all of them for this
			 * keycode (simulating Key "events") and ask
			 * XkbLookupKeySym to tell us the Keysym.  Once it
			 * matches the Keysym we have for this (keycode,
			 * group, level), gotten via XkbKeycodeToKeysym()
			 * above, we then (hopefully...) know that state
			 * of modifiers needed to generate this keysym.
			 *
			 * Yes... keep your fingers crossed.
			 *
			 * Note that many of the 256 states give the
			 * Keysym, we just need one, and we take the
			 * first one found.
			 */
			state = 0;
			while(state < 256) {
				if (XkbLookupKeySym(dpy, kc, state, &mods,
				    &ks2)) {

					/* save these for workaround below */
					if (state_save == -1) {
						state_save = state;
						mods_save = mods;
					}
					if (ks2 == ks) {
						/*
						 * zero the irrelevant bits
						 * by anding with mods.
						 */
						xkbstate[kc][grp][lvl]
						    = state & mods;
						/*
						 * also remember the irrelevant
						 * bits since it is handy.
						 */
						xkbignore[kc][grp][lvl] = mods;

						break;
					}
				}
				state++;
			}
			if (xkbstate[kc][grp][lvl] == (unsigned int) -1
			    && grp == 1) {
				/*
				 * Hack on Solaris 9 for Mode_switch
				 * for Group2 characters.  We force the 
				 * Mode_switch modifier bit on.
				 * XXX Need to figure out better what is
				 * happening here.  Is compat on somehow??
				 */
				unsigned int ms2;
				ms2 = XkbKeysymToModifiers(dpy, XK_Mode_switch);

				xkbstate[kc][grp][lvl]
				    = (state_save & mods_save) | ms2;

				xkbignore[kc][grp][lvl] = mods_save | ms2;
			}

			if (debug_keyboard > 1) {
				char *str;
				fprintf(stderr, "  %03d  G%d L%d  mod=%s ",
				    kc, grp+1, lvl+1, bitprint(ms, 8));
				fprintf(stderr, "state=%s ",
				    bitprint(xkbstate[kc][grp][lvl], 8));
				fprintf(stderr, "ignore=%s ",
				    bitprint(xkbignore[kc][grp][lvl], 8));
				str = XKeysymToString(ks);
				fprintf(stderr, " ks=0x%08lx \"%s\"\n",
				    ks, str ? str : "null");
			}
		}
	    }
	}

	/*
	 * kc_vec will be used in some places to find modifiers, etc
	 * we apply some permutations to it as workarounds.
	 */
	for (kc = 0; kc < 0x100; kc++) {
		kc_vec[kc] = kc;
	}

	if (kc1_mode_switch != -1 && kc1_iso_level3_shift != -1) {
		if (kc1_mode_switch < kc1_iso_level3_shift) {
			/* we prefer XK_ISO_Level3_Shift: */
			kc_vec[kc1_mode_switch] = kc1_iso_level3_shift;
			kc_vec[kc1_iso_level3_shift] = kc1_mode_switch;
		}
	}
	/* any more? need to watch for undoing the above. */

	/*
	 * process the user supplied -skip_keycodes string.
	 * This is presumably a list if "ghost" keycodes, the X server
	 * thinks they exist, but they do not.  ghosts can lead to
	 * ambiguities in the reverse map: Keysym -> KeyCode + Modstate,
	 * so if we can ignore them so much the better.  Presumably the
	 * user can never generate them from the physical keyboard.
	 * There may be other reasons to deaden some keys.
	 */
	if (skip_keycodes != NULL) {
		char *p, *str = strdup(skip_keycodes);
		p = strtok(str, ", \t\n\r");
		while (p) {
			k = 1;
			if (sscanf(p, "%d", &k) != 1 || k < 0 || k >= 0x100) {
				rfbLogEnable(1);
				rfbLog("invalid skip_keycodes: %s %s\n",
				    skip_keycodes, p);
				clean_up_exit(1);
			}
			skipkeycode[k] = 1;
			p = strtok(NULL, ", \t\n\r");
		}
		free(str);
	}
	if (debug_keyboard > 1) {
		fprintf(stderr, "grp_max=%d lvl_max=%d\n", grp_max, lvl_max);
	}
}

/*
 * Called on user keyboard input.  Try to solve the reverse mapping
 * problem: KeySym (from VNC client) => KeyCode(s) to press to generate
 * it.  The one-to-many KeySym => KeyCode mapping makes it difficult, as
 * does working out what changes to the modifier keypresses are needed.
 */
static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym,
    rfbClientPtr client) {

	int kc, grp, lvl, i, kci;
	int kc_f[0x100], grp_f[0x100], lvl_f[0x100], state_f[0x100], found;
	int ignore_f[0x100];
	unsigned int state = 0;


	/* these are used for finding modifiers, etc */
	XkbStateRec kbstate;
	int got_kbstate = 0;
	int Kc_f, Grp_f = 0, Lvl_f = 0;
	static int Kc_last_down = -1;
	static KeySym Ks_last_down = NoSymbol;

	if (!client || !down || !keysym) {} /* unused vars warning: */

	RAWFB_RET_VOID

	X_LOCK;

	if (debug_keyboard) {
		char *str = XKeysymToString(keysym);

		if (debug_keyboard > 1) {
			rfbLog("----------start-xkb_tweak_keyboard (%s) "
			    "--------\n", down ? "DOWN" : "UP");
		}

		rfbLog("xkb_tweak_keyboard: %s keysym=0x%x \"%s\"\n",
		    down ? "down" : "up", (int) keysym, str ? str : "null");
	}

	/*
	 * set everything to not-yet-found.
	 * these "found" arrays (*_f) let us dynamically consider the
	 * one-to-many Keysym -> Keycode issue.  we set the size at 256,
	 * but of course only very few will be found.
	 */
	for (i = 0; i < 0x100; i++) {
		kc_f[i]    = -1;
		grp_f[i]   = -1;
		lvl_f[i]   = -1;
		state_f[i] = -1;
		ignore_f[i] = -1;
	}
	found = 0;

	/*
	 * loop over all (keycode, group, level) triples looking for
	 * matching keysyms.  Amazingly this isn't slow (but maybe if
	 * you type really fast...).  Hash lookup into a linked list of
	 * (keycode,grp,lvl) triples would be the way to improve this
	 * in the future if needed.
	 */
	for (kc = kc_min; kc <= kc_max; kc++) {
	    for (grp = 0; grp < grp_max+1; grp++) {
		for (lvl = 0; lvl < lvl_max+1; lvl++) {
			if (keysym != xkbkeysyms[kc][grp][lvl]) {
				continue;
			}
			/* got a keysym match */
			state = xkbstate[kc][grp][lvl];

			if (debug_keyboard > 1) {
				char *s1, *s2;
				s1 = XKeysymToString(XKeycodeToKeysym(dpy,
				    kc, 0));
				if (! s1) s1 = "null";
				s2 = XKeysymToString(keysym);
				if (! s2) s2 = "null";
				fprintf(stderr, "  got match kc=%03d=0x%02x G%d"
				    " L%d  ks=0x%x \"%s\"  (basesym: \"%s\")\n",
				    kc, kc, grp+1, lvl+1, keysym, s2, s1);
				fprintf(stderr, "    need state: %s\n",
				    bitprint(state, 8));
				fprintf(stderr, "    ignorable : %s\n",
				    bitprint(xkbignore[kc][grp][lvl], 8));
			}

			/* save it if state is OK and not told to skip */
			if (state == (unsigned int) -1) {
				continue;
			}
			if (skipkeycode[kc] && debug_keyboard) {
				fprintf(stderr, "    xxx skipping keycode: %d "
				   "G%d/L%d\n", kc, grp+1, lvl+1);
			}
			if (skipkeycode[kc]) {
				continue;
			}
			if (found > 0 && kc == kc_f[found-1]) {
				/* ignore repeats for same keycode */
				continue;
			}
			kc_f[found] = kc;
			grp_f[found] = grp;
			lvl_f[found] = lvl;
			state_f[found] = state;
			ignore_f[found] = xkbignore[kc][grp][lvl];
			found++;
		}
	    }
	}

#define PKBSTATE  \
	fprintf(stderr, "    --- current mod state:\n"); \
	fprintf(stderr, "    mods      : %s\n", bitprint(kbstate.mods, 8)); \
	fprintf(stderr, "    base_mods : %s\n", bitprint(kbstate.base_mods, 8)); \
	fprintf(stderr, "    latch_mods: %s\n", bitprint(kbstate.latched_mods, 8)); \
	fprintf(stderr, "    lock_mods : %s\n", bitprint(kbstate.locked_mods, 8)); \
	fprintf(stderr, "    compat    : %s\n", bitprint(kbstate.compat_state, 8));

	/*
	 * Now get the current state of the keyboard from the X server.
	 * This seems to be the safest way to go as opposed to our
	 * keeping track of the modifier state on our own.  Again,
	 * this is fortunately not too slow.
	 */

	if (debug_keyboard > 1) {
		/* get state early for debug output */
		XkbGetState(dpy, XkbUseCoreKbd, &kbstate);
		got_kbstate = 1;
		PKBSTATE
	}

	if (!found && add_keysyms && keysym && ! IsModifierKey(keysym)) {
		int new_kc = add_keysym(keysym);
		if (new_kc != 0) {
			found = 1;
			kc_f[0] = new_kc;
			grp_f[0] = 0; 
			lvl_f[0] = 0; 
			state_f[0] = 0;
		}
	}

	if (!found && debug_keyboard) {
		char *str = XKeysymToString(keysym);
		fprintf(stderr, "    *** NO key found for: 0x%x %s  "
		    "*keystroke ignored*\n", keysym, str ? str : "null");
	}
	if (!found) {
		X_UNLOCK;
		return;
	}

	/* 
	 * we try to optimize here if found > 1
	 * e.g. minimize lvl or grp, or other things to give
	 * "safest" scenario to simulate the keystrokes.
	 */

	if (found > 1) {
		if (down) {
			int l, score[0x100];
			int best = 0, best_score = -1;
			/* need to break the tie... */
			if (! got_kbstate) {
				XkbGetState(dpy, XkbUseCoreKbd, &kbstate);
				got_kbstate = 1;
			}
			for (l=0; l < found; l++) {
				int myscore = 0, b = 0x1, i;
				int curr, curr_state = kbstate.mods;
				int need, need_state = state_f[l];
				int ignore_state = ignore_f[l];

				/* see how many modifiers need to be changed */
				for (i=0; i<8; i++) {
					curr = b & curr_state;
					need = b & need_state;
					if (! (b & ignore_state)) {
						;
					} else if (curr == need) {
						;
					} else {
						myscore++;
					}
					b = b << 1;
				}
				myscore *= 100;

				/* throw in some minimization of lvl too: */
				myscore += 2*lvl_f[l] + grp_f[l];

				/*
				 * XXX since we now internally track
				 * keycode_state[], we could throw that into
				 * the score as well.  I.e. if it is already
				 * down, it is pointless to think we can
				 * press it down further!  E.g.
				 *   myscore += 1000 * keycode_state[kc_f[l]];
				 * Also could watch multiple modifier
				 * problem, e.g. Shift+key -> Alt
				 * keycode = 125 on my keyboard.
				 */

				score[l] = myscore;
				if (debug_keyboard > 1) {
					fprintf(stderr, "    *** score for "
					    "keycode %03d: %4d\n",
					    kc_f[l], myscore);
				}
			}
			for (l=0; l < found; l++) {
				int myscore = score[l];
				if (best_score == -1 || myscore < best_score) {
					best = l;
					best_score = myscore;
				}
			}
			Kc_f = kc_f[best];
			Grp_f = grp_f[best];
			Lvl_f = lvl_f[best];
			state = state_f[best];
			
		} else {
			/* up */
			Kc_f = -1;
			if (keysym == Ks_last_down) {
				int l;
				for (l=0; l < found; l++) {
					if (Kc_last_down == kc_f[l]) {
						Kc_f = Kc_last_down;
						break;
					}
				}
			}
			if (Kc_f == -1) {
				int l;
				/*
				 * If it is already down, that is
				 * a great hint.  Use it.
				 *
				 * note: keycode_state in internal and
				 * ignores someone pressing keys on the
				 * physical display (but is updated
				 * periodically to clean out stale info).
				 */
				for (l=0; l < found; l++) {
					int key = (int) kc_f[l];
					if (keycode_state[key]) {
						Kc_f = kc_f[l];
						break;
					}
				}
			}

			if (Kc_f == -1) {
				/* hope for the best... XXX check mods */
				Kc_f = kc_f[0];
			}
		}
	} else {
		Kc_f = kc_f[0];
		Grp_f = grp_f[0];
		Lvl_f = lvl_f[0];
		state = state_f[0];
	}

	if (debug_keyboard && found > 1) {
		int l;
		char *str;
		fprintf(stderr, "    *** found more than one keycode: ");
		for (l = 0; l < found; l++) {
			fprintf(stderr, "%03d ", kc_f[l]);
		}
		for (l = 0; l < found; l++) {
			str = XKeysymToString(XKeycodeToKeysym(dpy,kc_f[l],0));
			fprintf(stderr, " \"%s\"", str ? str : "null");
		}
		fprintf(stderr, ", picked this one: %03d  (last down: %03d)\n",
		    Kc_f, Kc_last_down);
	}

	if (sloppy_keys) {
		int new_kc;
		if (sloppy_key_check(Kc_f, down, keysym, &new_kc)) {
			Kc_f = new_kc;
		}
	}

	if (down) {
		/*
		 * need to set up the mods for tweaking and other workarounds
		 */
		int needmods[8], sentmods[8], Ilist[8], keystate[256];
		int involves_multi_key, shift_is_down;
		int i, j, b, curr, need;
		unsigned int ms;
		KeySym ks;
		Bool dn;

		/* remember these to aid the subsequent up case: */
		Ks_last_down = keysym;
		Kc_last_down = Kc_f;

		if (! got_kbstate) {
			/* get the current modifier state if we haven't yet */
			XkbGetState(dpy, XkbUseCoreKbd, &kbstate);
			got_kbstate = 1;
		}

		/*
		 * needmods[] whether or not that modifier bit needs
		 *            something done to it. 
		 *            < 0 means no,
		 *            0   means needs to go up.
		 *            1   means needs to go down.
		 *
		 * -1, -2, -3 are used for debugging info to indicate
		 * why nothing needs to be done with the modifier, see below.
		 *
		 * sentmods[] is the corresponding keycode to use
		 * to acheive the needmods[] requirement for the bit.
		 */

		for (i=0; i<8; i++) {
			needmods[i] = -1;
			sentmods[i] = 0;
		}

		/*
		 * Loop over the 8 modifier bits and check if the current
		 * setting is what we need it to be or whether it should
		 * be changed (by us sending some keycode event)
		 *
		 * If nothing needs to be done to it record why:
		 *   -1  the modifier bit is ignored.
		 *   -2  the modifier bit is ignored, but is correct anyway.
		 *   -3  the modifier bit is correct.
		 */

		b = 0x1;
		for (i=0; i<8; i++) {
			curr = b & kbstate.mods;
			need = b & state;

			if (! (b & xkbignore[Kc_f][Grp_f][Lvl_f])) {
				/* irrelevant modifier bit */
				needmods[i] = -1;
				if (curr == need) needmods[i] = -2;
			} else if (curr == need) {
				/* already correct */
				needmods[i] = -3;
			} else if (! curr && need) {
				/* need it down */
				needmods[i] = 1;
			} else if (curr && ! need) {
				/* need it up */
				needmods[i] = 0;
			}

			b = b << 1;
		}

		/*
		 * Again we dynamically probe the X server for information,
		 * this time for the state of all the keycodes.  Useful
		 * info, and evidently is not too slow...
		 */
		get_keystate(keystate);

		/*
		 * We try to determine if Shift is down (since that can
		 * screw up ISO_Level3_Shift manipulations).
		 */
		shift_is_down = 0;

		for (kc = kc_min; kc <= kc_max; kc++) {
			if (skipkeycode[kc] && debug_keyboard) {
				fprintf(stderr, "    xxx skipping keycode: "
				    "%d\n", kc);
			}
			if (skipkeycode[kc]) {
				continue;
			}
			if (shift_keys[kc] && keystate[kc]) {
				shift_is_down = kc;
				break;
			}
		}

		/*
		 * Now loop over the modifier bits and try to deduce the
		 * keycode presses/release require to match the desired
		 * state.
		 */
		for (i=0; i<8; i++) {
			if (needmods[i] < 0 && debug_keyboard > 1) {
				int k = -needmods[i] - 1;
				char *words[] = {"ignorable",
				    "bitset+ignorable", "bitset"};
				fprintf(stderr, "    +++ needmods: mod=%d is "
				    "OK  (%s)\n", i, words[k]);
			}
			if (needmods[i] < 0) {
				continue;
			}

			b = 1 << i;

			if (debug_keyboard > 1) {
				fprintf(stderr, "    +++ needmods: mod=%d %s "
				    "need it to be: %d %s\n", i, bitprint(b, 8),
				    needmods[i], needmods[i] ? "down" : "up");
			}

			/*
			 * Again, an inefficient loop, this time just
			 * looking for modifiers...
			 * 
			 * note the use of kc_vec to prefer XK_ISO_Level3_Shift
			 * over XK_Mode_switch.
			 */
			for (kci = kc_min; kci <= kc_max; kci++) {
			  for (grp = 0; grp < grp_max+1; grp++) {
			    for (lvl = 0; lvl < lvl_max+1; lvl++) {
				int skip = 1, dbmsg = 0;

				kc = kc_vec[kci];

				ms = xkbmodifiers[kc][grp][lvl];
				if (! ms || ms != (unsigned int) b) {
					continue;
				}

				if (skipkeycode[kc] && debug_keyboard) {
				    fprintf(stderr, "    xxx skipping keycode:"
					" %d G%d/L%d\n", kc, grp+1, lvl+1);
				}
				if (skipkeycode[kc]) {
					continue;
				}

				ks = xkbkeysyms[kc][grp][lvl];
				if (! ks) {
					continue;
				}

				if (ks == XK_Shift_L) {
					skip = 0;
				} else if (ks == XK_Shift_R) {
					skip = 0;
				} else if (ks == XK_Mode_switch) {
					skip = 0;
				} else if (ks == XK_ISO_Level3_Shift) {
					skip = 0;
				}

				if (watch_capslock && kbstate.locked_mods & LockMask) {
				    if (keysym >= 'A' && keysym <= 'Z') {
					if (ks == XK_Shift_L || ks == XK_Shift_R) {
						if (debug_keyboard > 1) {
							fprintf(stderr, "    A-Z caplock skip Shift\n");
						}
						skip = 1;
					} else if (ks == XK_Caps_Lock) {
						if (debug_keyboard > 1) {
							fprintf(stderr, "    A-Z caplock noskip CapsLock\n");
						}
						skip = 0;
					}
				    }
				}
				/*
				 * Alt, Meta, Control, Super,
				 * Hyper, Num, Caps are skipped.
				 *
				 * XXX need more work on Locks,
				 * and non-standard modifiers.
				 * (e.g. XF86_Next_VMode using
				 * Ctrl+Alt)
				 */
				if (debug_keyboard > 1) {
					char *str = XKeysymToString(ks);
					int kt = keystate[kc];
					fprintf(stderr, "    === for mod=%s "
					    "found kc=%03d/G%d/L%d it is %d "
					    "%s skip=%d (%s)\n", bitprint(b,8),
					    kc, grp+1, lvl+1, kt, kt ?
					    "down" : "up  ", skip, str ?
					    str : "null");
				}

				if (! skip && needmods[i] !=
				    keystate[kc] && sentmods[i] == 0) {
					sentmods[i] = kc;
					dbmsg = 1;
				}

				if (debug_keyboard > 1 && dbmsg) {
					int nm = needmods[i];
					fprintf(stderr, "    >>> we choose "
					    "kc=%03d=0x%02x to change it to: "
					    "%d %s\n", kc, kc, nm, nm ?
					    "down" : "up");
				}
					
			    }
			  }
			}
		}
		for (i=0; i<8; i++) {
			/*
			 * reverse order is useful for tweaking
			 * ISO_Level3_Shift before Shift, but assumes they
			 * are in that order (i.e. Shift is first bit).
			 */
			int reverse = 1;
			if (reverse) {
				Ilist[i] = 7 - i;
			} else {
				Ilist[i] = i;
			}
		}

		/*
		 * check to see if Multi_key is bound to one of the Mods
		 * we have to tweak
		 */
		involves_multi_key = 0;
		for (j=0; j<8; j++) {
			i = Ilist[j];
			if (sentmods[i] == 0) continue;
			dn = (Bool) needmods[i];
			if (!dn) continue;
			if (multi_key[sentmods[i]]) {
				involves_multi_key = i+1;
			}
		}

		if (involves_multi_key && shift_is_down && needmods[0] < 0) {
			/*
			 * Workaround for Multi_key and shift.
			 * Assumes Shift is bit 1 (needmods[0])
			 */
			if (debug_keyboard) {
				fprintf(stderr, "    ^^^ trying to avoid "
				    "inadvertent Multi_key from Shift "
				    "(doing %03d up now)\n", shift_is_down);
			}
			XTestFakeKeyEvent_wr(dpy, shift_is_down, False,
			    CurrentTime);
		} else {
			involves_multi_key = 0;
		}

		for (j=0; j<8; j++) {
			/* do the Mod ups */
			i = Ilist[j];
			if (sentmods[i] == 0) continue;
			dn = (Bool) needmods[i];
			if (dn) continue;
			XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime);
		}
		for (j=0; j<8; j++) {
			/* next, do the Mod downs */
			i = Ilist[j];
			if (sentmods[i] == 0) continue;
			dn = (Bool) needmods[i];
			if (!dn) continue;
			XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime);
		}

		if (involves_multi_key) {
			/*
			 * Reverse workaround for Multi_key and shift.
			 */
			if (debug_keyboard) {
				fprintf(stderr, "    vvv trying to avoid "
				    "inadvertent Multi_key from Shift "
				    "(doing %03d down now)\n", shift_is_down);
			}
			XTestFakeKeyEvent_wr(dpy, shift_is_down, True,
			    CurrentTime);
		}

		/*
		 * With the above modifier work done, send the actual keycode:
		 */
		XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime);

		/*
		 * Now undo the modifier work:
		 */
		for (j=7; j>=0; j--) {
			/* reverse Mod downs we did */
			i = Ilist[j];
			if (sentmods[i] == 0) continue;
			dn = (Bool) needmods[i];
			if (!dn) continue;
			XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn,
			    CurrentTime);
		}
		for (j=7; j>=0; j--) {
			/* finally reverse the Mod ups we did */
			i = Ilist[j];
			if (sentmods[i] == 0) continue;
			dn = (Bool) needmods[i];
			if (dn) continue;
			XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn,
			    CurrentTime);
		}

	} else { /* for up case, hopefully just need to pop it up: */

		XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime);
	}
	X_UNLOCK;
}
#endif

/*
 * For tweaking modifiers wrt the Alt-Graph key, etc.
 */
#define LEFTSHIFT 1
#define RIGHTSHIFT 2
#define ALTGR 4
static char mod_state = 0;

static char modifiers[0x100];
static KeyCode keycodes[0x100];
static KeyCode left_shift_code, right_shift_code, altgr_code, iso_level3_code;

/* workaround for X11R5, Latin 1 only */
#ifndef XConvertCase
#define XConvertCase(sym, lower, upper) \
*(lower) = sym; \
*(upper) = sym; \
if (sym >> 8 == 0) { \
    if ((sym >= XK_A) && (sym <= XK_Z)) \
        *(lower) += (XK_a - XK_A); \
    else if ((sym >= XK_a) && (sym <= XK_z)) \
        *(upper) -= (XK_a - XK_A); \
    else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis)) \
        *(lower) += (XK_agrave - XK_Agrave); \
    else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis)) \
        *(upper) -= (XK_agrave - XK_Agrave); \
    else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn)) \
        *(lower) += (XK_oslash - XK_Ooblique); \
    else if ((sym >= XK_oslash) && (sym <= XK_thorn)) \
        *(upper) -= (XK_oslash - XK_Ooblique); \
}
#endif

char *short_kmbcf(char *str) {
	int i, saw_k = 0, saw_m = 0, saw_b = 0, saw_c = 0, saw_f = 0, n = 10;
	char *p, tmp[10];
	
	for (i=0; i<n; i++) {
		tmp[i] = '\0';
	}

	p = str;
	i = 0;
	while (*p) {
		if ((*p == 'K' || *p == 'k') && !saw_k) {
			tmp[i++] = 'K';
			saw_k = 1;
		} else if ((*p == 'M' || *p == 'm') && !saw_m) {
			tmp[i++] = 'M';
			saw_m = 1;
		} else if ((*p == 'B' || *p == 'b') && !saw_b) {
			tmp[i++] = 'B';
			saw_b = 1;
		} else if ((*p == 'C' || *p == 'c') && !saw_c) {
			tmp[i++] = 'C';
			saw_c = 1;
		} else if ((*p == 'F' || *p == 'f') && !saw_f) {
			tmp[i++] = 'F';
			saw_f = 1;
		}
		p++;
	}
	return(strdup(tmp));
}

void initialize_allowed_input(void) {
	char *str;

	if (allowed_input_normal) {
		free(allowed_input_normal);
		allowed_input_normal = NULL;
	}
	if (allowed_input_view_only) {
		free(allowed_input_view_only);
		allowed_input_view_only = NULL;
	}

	if (! allowed_input_str) {
		allowed_input_normal = strdup("KMBCF");
		allowed_input_view_only = strdup("");
	} else {
		char *p, *str = strdup(allowed_input_str);
		p = strchr(str, ',');
		if (p) {
			allowed_input_view_only = strdup(p+1);
			*p = '\0';
			allowed_input_normal = strdup(str);
		} else {
			allowed_input_normal = strdup(str);
			allowed_input_view_only = strdup("");
		}
		free(str);
	}

	/* shorten them */
	str = short_kmbcf(allowed_input_normal);
	free(allowed_input_normal);
	allowed_input_normal = str;

	str = short_kmbcf(allowed_input_view_only);
	free(allowed_input_view_only);
	allowed_input_view_only = str;

	if (screen) {
		rfbClientIteratorPtr iter;
		rfbClientPtr cl;

		iter = rfbGetClientIterator(screen);
		while( (cl = rfbClientIteratorNext(iter)) ) {
			ClientData *cd = (ClientData *) cl->clientData;

			if (! cd) {
				continue;
			}
#if 0
rfbLog("cd: %p\n", cd);
rfbLog("cd->input: %s\n", cd->input);
rfbLog("cd->login_viewonly: %d\n", cd->login_viewonly);
rfbLog("allowed_input_view_only: %s\n", allowed_input_view_only);
#endif

			if (cd->input[0] == '=') {
				;	/* custom setting */
			} else if (cd->login_viewonly) {
				if (*allowed_input_view_only != '\0') {
					cl->viewOnly = FALSE;
					cd->input[0] = '\0';
					strncpy(cd->input,
					    allowed_input_view_only, CILEN);
				} else {
					cl->viewOnly = TRUE;
				}
			} else {
				if (allowed_input_normal) {
					cd->input[0] = '\0';
					strncpy(cd->input,
					    allowed_input_normal, CILEN);
				}
			}
		}
		rfbReleaseClientIterator(iter);
	}
}

void initialize_modtweak(void) {
#if NO_X11
	RAWFB_RET_VOID
	return;
#else
	KeySym keysym, *keymap;
	int i, j, minkey, maxkey, syms_per_keycode;
	int use_lowest_index = 0;

	if (use_xkb_modtweak) {
		initialize_xkb_modtweak();
		return;
	}
	memset(modifiers, -1, sizeof(modifiers));
	for (i=0; i<0x100; i++) {
		keycodes[i] = NoSymbol;
	}

	RAWFB_RET_VOID

	if (getenv("MODTWEAK_LOWEST")) {
		use_lowest_index = 1;
	}

	X_LOCK;
	XDisplayKeycodes(dpy, &minkey, &maxkey);

	keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
	    &syms_per_keycode);

	/* handle alphabetic char with only one keysym (no upper + lower) */
	for (i = minkey; i <= maxkey; i++) {
		KeySym lower, upper;
		/* 2nd one */
		keysym = keymap[(i - minkey) * syms_per_keycode + 1];
		if (keysym != NoSymbol) {
			continue;
		}
		/* 1st one */
		keysym = keymap[(i - minkey) * syms_per_keycode + 0];
		if (keysym == NoSymbol) {
			continue;
		}
		XConvertCase(keysym, &lower, &upper);
		if (lower != upper) {
			keymap[(i - minkey) * syms_per_keycode + 0] = lower;
			keymap[(i - minkey) * syms_per_keycode + 1] = upper;
		}
	}
	for (i = minkey; i <= maxkey; i++) {
		if (debug_keyboard) {
			if (i == minkey) {
				rfbLog("initialize_modtweak: keycode -> "
				    "keysyms mapping info:\n");
			}
			fprintf(stderr, "  %03d  ", i);
		}
		for (j = 0; j < syms_per_keycode; j++) {
			if (debug_keyboard) {
				char *sym;
#if 0
				sym =XKeysymToString(XKeycodeToKeysym(dpy,i,j));
#else
				keysym = keymap[(i-minkey)*syms_per_keycode+j];
				sym = XKeysymToString(keysym);
#endif
				fprintf(stderr, "%-18s ", sym ? sym : "null");
				if (j == syms_per_keycode - 1) {
					fprintf(stderr, "\n");
				}
			}
			if (j >= 4) {
				/*
				 * Something wacky in the keymapping.
				 * Ignore these non Shift/AltGr chords
				 * for now... n.b. we try to automatically
				 * switch to -xkb for this case.
				 */
				continue;
			}
			keysym = keymap[ (i - minkey) * syms_per_keycode + j ];
			if ( keysym >= ' ' && keysym < 0x100
			    && i == XKeysymToKeycode(dpy, keysym) ) {
				if (use_lowest_index && keycodes[keysym] != NoSymbol) {
					continue;
				}
				keycodes[keysym] = i;
				modifiers[keysym] = j;
			}
		}
	}

	left_shift_code = XKeysymToKeycode(dpy, XK_Shift_L);
	right_shift_code = XKeysymToKeycode(dpy, XK_Shift_R);
	altgr_code = XKeysymToKeycode(dpy, XK_Mode_switch);
	iso_level3_code = NoSymbol;
#ifdef XK_ISO_Level3_Shift
	iso_level3_code = XKeysymToKeycode(dpy, XK_ISO_Level3_Shift);
#endif

	XFree_wr ((void *) keymap);

	X_UNLOCK;
#endif	/* NO_X11 */
}

/*
 * does the actual tweak:
 */
static void tweak_mod(signed char mod, rfbBool down) {
	rfbBool is_shift = mod_state & (LEFTSHIFT|RIGHTSHIFT);
	Bool dn = (Bool) down;
	KeyCode altgr = altgr_code;

	RAWFB_RET_VOID

	if (mod < 0) {
		if (debug_keyboard) {
			rfbLog("tweak_mod: Skip:  down=%d index=%d\n", down,
			    (int) mod);
		}
		return;
	}
	if (debug_keyboard) {
		rfbLog("tweak_mod: Start:  down=%d index=%d mod_state=0x%x"
		    " is_shift=%d\n", down, (int) mod, (int) mod_state,
		    is_shift);
	}

	if (use_iso_level3 && iso_level3_code) {
		altgr = iso_level3_code;
	}

	X_LOCK;
	if (is_shift && mod != 1) {
	    if (mod_state & LEFTSHIFT) {
		XTestFakeKeyEvent_wr(dpy, left_shift_code, !dn, CurrentTime);
	    }
	    if (mod_state & RIGHTSHIFT) {
		XTestFakeKeyEvent_wr(dpy, right_shift_code, !dn, CurrentTime);
	    }
	}
	if ( ! is_shift && mod == 1 ) {
	    XTestFakeKeyEvent_wr(dpy, left_shift_code, dn, CurrentTime);
	}
	if ( altgr && (mod_state & ALTGR) && mod != 2 ) {
	    XTestFakeKeyEvent_wr(dpy, altgr, !dn, CurrentTime);
	}
	if ( altgr && ! (mod_state & ALTGR) && mod == 2 ) {
	    XTestFakeKeyEvent_wr(dpy, altgr, dn, CurrentTime);
	}
	X_UNLOCK;
	if (debug_keyboard) {
		rfbLog("tweak_mod: Finish: down=%d index=%d mod_state=0x%x"
		    " is_shift=%d\n", down, (int) mod, (int) mod_state,
		    is_shift);
	}
}

/*
 * tweak the modifier under -modtweak
 */
static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym,
    rfbClientPtr client) {
#if NO_X11
	RAWFB_RET_VOID
	if (!down || !keysym || !client) {}
	return;
#else
	KeyCode k;
	int tweak = 0;

	RAWFB_RET_VOID

	if (use_xkb_modtweak) {
		xkb_tweak_keyboard(down, keysym, client);
		return;
	}
	if (debug_keyboard) {
		rfbLog("modifier_tweak_keyboard: %s keysym=0x%x\n",
		    down ? "down" : "up", (int) keysym);
	}

#define ADJUSTMOD(sym, state) \
	if (keysym == sym) { \
		if (down) { \
			mod_state |= state; \
		} else { \
			mod_state &= ~state; \
		} \
	}

	ADJUSTMOD(XK_Shift_L, LEFTSHIFT)
	ADJUSTMOD(XK_Shift_R, RIGHTSHIFT)
	ADJUSTMOD(XK_Mode_switch, ALTGR)

	if ( down && keysym >= ' ' && keysym < 0x100 ) {
		unsigned int state = 0;
		tweak = 1;
		if (watch_capslock && keysym >= 'A' && keysym <= 'Z') {
			X_LOCK;
			state = mask_state();
			X_UNLOCK;
		}
		if (state & LockMask) {
			/* capslock set for A-Z, so no tweak */
			X_LOCK;
			k = XKeysymToKeycode(dpy, (KeySym) keysym);
			X_UNLOCK;
			tweak = 0;
		} else {
			tweak_mod(modifiers[keysym], True);
			k = keycodes[keysym];
		}
	} else {
		X_LOCK;
		k = XKeysymToKeycode(dpy, (KeySym) keysym);
		X_UNLOCK;
	}
	if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) {
		int new_kc = add_keysym(keysym);
		if (new_kc) {
			k = new_kc;
		}
	}

	if (sloppy_keys) {
		int new_kc;
		if (sloppy_key_check((int) k, down, keysym, &new_kc)) {
			k = (KeyCode) new_kc;
		}
	}

	if (debug_keyboard) {
		char *str = XKeysymToString(keysym);
		rfbLog("modifier_tweak_keyboard: KeySym 0x%x \"%s\" -> "
		    "KeyCode 0x%x%s\n", (int) keysym, str ? str : "null",
		    (int) k, k ? "" : " *ignored*");
	}
	if ( k != NoSymbol ) {
		X_LOCK;
		XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime);
		X_UNLOCK;
	} 

	if ( tweak ) {
		tweak_mod(modifiers[keysym], False);
	}
#endif	/* NO_X11 */
}

void initialize_keyboard_and_pointer(void) {

	RAWFB_RET_VOID

	if (use_modifier_tweak) {
		initialize_modtweak();
	}
	if (remap_file != NULL) {
		initialize_remap(remap_file);
	}

	initialize_pointer_map(pointer_remap);

	clear_modifiers(1);
	if (clear_mods == 1) {
		clear_modifiers(0);
	}
}

void get_allowed_input(rfbClientPtr client, allowed_input_t *input) {
	ClientData *cd;
	char *str;

	input->keystroke = 0;
	input->motion    = 0;
	input->button    = 0;
	input->clipboard = 0;
	input->files     = 0;

	if (! client) {
		return;
	}

	cd = (ClientData *) client->clientData;

	if (! cd) {
		return;
	}
	
	if (cd->input[0] != '-') {
		str = cd->input;
	} else if (client->viewOnly) {
		if (allowed_input_view_only) {
			str = allowed_input_view_only;
		} else {
			str = "";
		}
	} else {
		if (allowed_input_normal) {
			str = allowed_input_normal;
		} else {
			str = "KMBCF";
		}
	}
if (0) fprintf(stderr, "GAI: %s - %s\n", str, cd->input);

	while (*str) {
		if (*str == 'K') {
			input->keystroke = 1;
		} else if (*str == 'M') {
			input->motion = 1;
		} else if (*str == 'B') {
			input->button = 1;
		} else if (*str == 'C') {
			input->clipboard = 1;
		} else if (*str == 'F') {
			input->files = 1;
		}
		str++;
	}
}

/* for -pipeinput mode */
static void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) {
	int can_input = 0, uid = 0;
	allowed_input_t input;
	char *name;
	ClientData *cd = (ClientData *) client->clientData;

	if (pipeinput_int == PIPEINPUT_VID) {
		v4l_key_command(down, keysym, client);
	} else if (pipeinput_int == PIPEINPUT_CONSOLE) {
		console_key_command(down, keysym, client);
	} else if (pipeinput_int == PIPEINPUT_UINPUT) {
		uinput_key_command(down, keysym, client);
	} else if (pipeinput_int == PIPEINPUT_MACOSX) {
		macosx_key_command(down, keysym, client);
	} else if (pipeinput_int == PIPEINPUT_VNC) {
		vnc_reflect_send_key((uint32_t) keysym, down);
	}
	if (pipeinput_fh == NULL) {
		return;
	}

	if (! view_only) {
		get_allowed_input(client, &input);
		if (input.keystroke) {
			can_input = 1;	/* XXX distinguish later */
		}
	}
	if (cd) {
		uid = cd->uid;
	}
	if (! can_input) {
		uid = -uid;
	}

	X_LOCK;
	name = XKeysymToString(keysym);
	X_UNLOCK;

	fprintf(pipeinput_fh, "Keysym %d %d %u %s %s\n", uid, down,
	    keysym, name ? name : "null", down ? "KeyPress" : "KeyRelease");

	fflush(pipeinput_fh);
	check_pipeinput();
}

typedef struct keyevent {
	rfbKeySym sym;
	rfbBool down;
	double time;
} keyevent_t;

#define KEY_HIST 256
static int key_history_idx = -1;
static keyevent_t key_history[KEY_HIST];

double typing_rate(double time_window, int *repeating) {
	double dt = 1.0, now = dnow();
	KeySym key = NoSymbol;
	int i, idx, cnt = 0, repeat_keys = 0;

	if (key_history_idx == -1) {
		if (repeating) {
			*repeating = 0;
		}
		return 0.0;
	}
	if (time_window > 0.0) {
		dt = time_window;
	}
	for (i=0; i<KEY_HIST; i++) {
		idx = key_history_idx - i;
		if (idx < 0) {
			idx += KEY_HIST;
		}
		if (! key_history[idx].down) {
			continue;
		}
		if (now > key_history[idx].time + dt) {
			break;
		}
		cnt++;
		if (key == NoSymbol) {
			key = key_history[idx].sym;
			repeat_keys = 1;
		} else if (key == key_history[idx].sym) {
			repeat_keys++;
		}
	}

	if (repeating) {
		if (repeat_keys >= 2) {
			*repeating = repeat_keys;
		} else {
			*repeating = 0;
		}
	}

	/*
	 * n.b. keyrate could seem very high with libvncserver buffering them
	 * so avoid using small dt.
	 */
	return ((double) cnt)/dt;
}

int skip_cr_when_scaling(char *mode) {
	int got = 0;
	
	if (!scaling) {
		return 0;
	}

	if (scaling_copyrect != scaling_copyrect0) {
		/* user override via -scale: */
		if (! scaling_copyrect) {
			return 1;
		} else {
			return 0;
		}
	}
	if (*mode == 's') {
		got = got_scrollcopyrect;
	} else if (*mode == 'w') {
		got = got_wirecopyrect;
	}
	if (scaling_copyrect || got) {
		int lat, rate;
		int link = link_rate(&lat, &rate);
		if (link == LR_DIALUP) {
			return 1;
		} else if (rate < 25) {
			/* the fill-in of the repair may be too slow */
			return 1;
		} else {
			return 0;
		}
	} else {
		return 1;
	}
}

/*
 * key event handler.  See the above functions for contortions for
 * running under -modtweak.
 */
static rfbClientPtr last_keyboard_client = NULL;

void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) {
	KeyCode k;
	int idx, isbutton = 0;
	allowed_input_t input;
	time_t now = time(NULL);
	double tnow;
	static int skipped_last_down;
	static rfbBool last_down;
	static rfbKeySym last_keysym = NoSymbol;
	static rfbKeySym max_keyrepeat_last_keysym = NoSymbol;
	static double max_keyrepeat_last_time = 0.0;
	static double max_keyrepeat_always = -1.0;

	dtime0(&tnow);
	got_keyboard_calls++;

	if (debug_keyboard) {
		char *str;
		X_LOCK;
		str = XKeysymToString((KeySym) keysym);
		X_UNLOCK;
		rfbLog("# keyboard(%s, 0x%x \"%s\") uip=%d  %.4f\n",
		    down ? "down":"up", (int) keysym, str ? str : "null",
		    unixpw_in_progress, tnow - x11vnc_start);
	}

	if (keysym <= 0) {
		rfbLog("keyboard: skipping 0x0 keysym\n");
		return;
	}
	
	if (unixpw_in_progress) {
		if (unixpw_denied) {
			rfbLog("keyboard: ignoring keystroke 0x%x in "
			    "unixpw_denied=1 state\n", (int) keysym);
			return;
		}
		if (client != unixpw_client) {
			rfbLog("keyboard: skipping other client in unixpw\n");
			return;
		}

		unixpw_keystroke(down, keysym, 0);

		return;
	}

	if (skip_duplicate_key_events) {
		if (keysym == last_keysym && down == last_down) {
			if (debug_keyboard) {
				rfbLog("skipping dup key event: %d 0x%x\n",
				    down, keysym);
			}
			return;
		}
	}

	if (skip_lockkeys) {
		/*  we don't handle XK_ISO*_Lock or XK_Kana_Lock ... */
		if (keysym == XK_Scroll_Lock || keysym == XK_Num_Lock ||
		    keysym == XK_Caps_Lock || keysym == XK_Shift_Lock) {
			if (debug_keyboard) {
				rfbLog("skipping lock key event: %d 0x%x\n",
				    down, keysym);
			}
			return;
		} else if (keysym >= XK_KP_0 && keysym <= XK_KP_9) {
			/* ugh this is probably what they meant... assume NumLock. */
			if (debug_keyboard) {
				rfbLog("changed KP digit to regular digit: %d 0x%x\n",
				    down, keysym);
			}
			keysym = (keysym - XK_KP_0) + XK_0;
		} else if (keysym == XK_KP_Decimal) {
			if (debug_keyboard) {
				rfbLog("changed XK_KP_Decimal to XK_period: %d 0x%x\n",
				    down, keysym);
			}
			keysym = XK_period; 
		}
	}

	last_down = down;
	last_keysym = keysym;
	last_keyboard_time = tnow;

	last_rfb_down = down;
	last_rfb_keysym = keysym;
	last_rfb_keytime = tnow;
	last_rfb_key_accepted = FALSE;

	if (key_history_idx == -1) {
		for (idx=0; idx<KEY_HIST; idx++) {
			key_history[idx].sym = NoSymbol;
			key_history[idx].down = FALSE;
			key_history[idx].time = 0.0;
		}
	}
	idx = ++key_history_idx;
	if (key_history_idx >= KEY_HIST) {
		key_history_idx = 0;
		idx = 0;
	}
	key_history[idx].sym = keysym;
	key_history[idx].down = down;
	key_history[idx].time = tnow;

	if (down && (keysym == XK_Alt_L || keysym == XK_Super_L)) {
		int i, k, run = 0, ups = 0;
		double delay = 1.0;
		KeySym ks;
		for (i=0; i<16; i++) {
			k = idx - i;
			if (k < 0) k += KEY_HIST;
			if (!key_history[k].down) {
				ups++;
				continue;
			}
			ks = key_history[k].sym;
			if (key_history[k].time < tnow - delay) {
				break;
			} else if (ks == keysym && ks == XK_Alt_L) {
				run++;
			} else if (ks == keysym && ks == XK_Super_L) {
				run++;
			} else {
				break;
			}
		}
		if (ups < 2) {
			;
		} else if (run == 3 && keysym == XK_Alt_L) {
			rfbLog("3*Alt_L, calling: refresh_screen(0)\n");
			refresh_screen(0);
		} else if (run == 4 && keysym == XK_Alt_L) {
			rfbLog("4*Alt_L, setting: do_copy_screen\n");
			do_copy_screen = 1;
		} else if (run == 5 && keysym == XK_Alt_L) {
			;
		} else if (run == 3 && keysym == XK_Super_L) {
			rfbLog("3*Super_L, calling: set_xdamage_mark()\n");
			set_xdamage_mark(0, 0, dpy_x, dpy_y);
		} else if (run == 4 && keysym == XK_Super_L) {
			rfbLog("4*Super_L, calling: check_xrecord_reset()\n");
			check_xrecord_reset(1);
		} else if (run == 5 && keysym == XK_Super_L) {
			rfbLog("5*Super_L, calling: push_black_screen(0)\n");
			push_black_screen(0);
		}
	}

#ifdef MAX_KEYREPEAT 
	if (max_keyrepeat_always < 0.0) {
		if (getenv("MAX_KEYREPEAT")) {
			max_keyrepeat_always = atof(getenv("MAX_KEYREPEAT"));
		} else {
			max_keyrepeat_always = 0.0;
		}
	}
	if (max_keyrepeat_always > 0.0) {
		max_keyrepeat_time = max_keyrepeat_always;
	}
#else
	if (0) {max_keyrepeat_always=0;}
#endif
	if (!down && skipped_last_down) {
		int db = debug_scroll;
		if (keysym == max_keyrepeat_last_keysym) {
			skipped_last_down = 0;
			if (db) rfbLog("--- scroll keyrate skipping 0x%lx %s "
			    "%.4f  %.4f\n", keysym, down ? "down":"up  ",
			    tnow - x11vnc_start, tnow - max_keyrepeat_last_time); 
			return;
		}
	}
	if (down && max_keyrepeat_time > 0.0) {
		int skip = 0;
		int db = debug_scroll;

		if (max_keyrepeat_last_keysym != NoSymbol &&
		    max_keyrepeat_last_keysym != keysym) {
			;
		} else {
			if (tnow < max_keyrepeat_last_time+max_keyrepeat_time) {
				skip = 1;
			}
		}
		max_keyrepeat_time = 0.0;
		if (skip) {
			if (db) rfbLog("--- scroll keyrate skipping 0x%lx %s "
			    "%.4f  %.4f\n", keysym, down ? "down":"up  ",
			    tnow - x11vnc_start, tnow - max_keyrepeat_last_time); 
			max_keyrepeat_last_keysym = keysym;
			skipped_last_down = 1;
			return;
		} else {
			if (db) rfbLog("--- scroll keyrate KEEPING  0x%lx %s "
			    "%.4f  %.4f\n", keysym, down ? "down":"up  ",
			    tnow - x11vnc_start, tnow - max_keyrepeat_last_time); 
		}
	}
	max_keyrepeat_last_keysym = keysym;
	max_keyrepeat_last_time = tnow;
	skipped_last_down = 0;
	last_rfb_key_accepted = TRUE;

	if (pipeinput_fh != NULL || pipeinput_int) {
		pipe_keyboard(down, keysym, client);	/* MACOSX here. */
		if (! pipeinput_tee) {
			if (! view_only || raw_fb) {	/* raw_fb hack */
				last_keyboard_client = client;
				last_event = last_input = now;
				last_keyboard_input = now;
		
				last_keysym = keysym;

				last_rfb_down = down;
				last_rfb_keysym = keysym;
				last_rfb_keytime = tnow;

				got_user_input++;
				got_keyboard_input++;
			}
			return;
		}
	}

	if (view_only) {
		return;
	}
	get_allowed_input(client, &input);
	if (! input.keystroke) {
		return;
	}

	track_mod_state(keysym, down, TRUE);	/* ignores remaps */

	last_keyboard_client = client;
	last_event = last_input = now;
	last_keyboard_input = now;

	last_keysym = keysym;

	last_rfb_down = down;
	last_rfb_keysym = keysym;
	last_rfb_keytime = tnow;

	got_user_input++;
	got_keyboard_input++;
	
	RAWFB_RET_VOID

	if (keyremaps) {
		keyremap_t *remap = keyremaps;
		while (remap != NULL) {
			if (remap->before == keysym) {
				keysym = remap->after;
				isbutton = remap->isbutton;
				if (debug_keyboard) {
					char *str1, *str2;
					X_LOCK;
					str1 = XKeysymToString(remap->before);
					str2 = XKeysymToString(remap->after);
					rfbLog("keyboard(): remapping keysym: "
					    "0x%x \"%s\" -> 0x%x \"%s\"\n",
					    (int) remap->before,
					    str1 ? str1 : "null",
					    (int) remap->after,
					    remap->isbutton ? "button" :
					    str2 ? str2 : "null");
					X_UNLOCK;
				}
				break;
			}
			remap = remap->next;
		}
	}

	if (use_xrecord && ! xrecording && down) {

		if (!strcmp(scroll_copyrect, "never")) {
			;
		} else if (!strcmp(scroll_copyrect, "mouse")) {
			;
		} else if (skip_cr_when_scaling("scroll")) {
			;
		} else if (! xrecord_skip_keysym(keysym)) {
			snapshot_stack_list(0, 0.25);
			xrecord_watch(1, SCR_KEY);
			xrecord_set_by_keys = 1;
			xrecord_keysym = keysym;
		} else {
			if (debug_scroll) {
				char *str = XKeysymToString(keysym);
				rfbLog("xrecord_skip_keysym: %s\n",
				    str ? str : "NoSymbol");
			}
		}
	}

	if (isbutton) {
		int mask, button = (int) keysym;
		if (! down) {
			return;	/* nothing to send */
		}
		if (debug_keyboard) {
			rfbLog("keyboard(): remapping keystroke to button %d"
			    " click\n", button);
		}
		dtime0(&last_key_to_button_remap_time);

		X_LOCK;
		/*
		 * This in principle can be a little dicey... i.e. even
		 * remap the button click to keystroke sequences!
		 * Usually just will simulate the button click.
		 */
		mask = 1<<(button-1);
		do_button_mask_change(mask, button);	/* down */
		mask = 0;
		do_button_mask_change(mask, button);	/* up */
		XFlush_wr(dpy);
		X_UNLOCK;
		return;
	}

	if (use_modifier_tweak) {
		modifier_tweak_keyboard(down, keysym, client);
		X_LOCK;
		XFlush_wr(dpy);
		X_UNLOCK;
		return;
	}

	X_LOCK;

	k = XKeysymToKeycode(dpy, (KeySym) keysym);

	if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) {
		int new_kc = add_keysym(keysym);
		if (new_kc) {
			k = new_kc;
		}
	}
	if (debug_keyboard) {
		char *str = XKeysymToString(keysym);
		rfbLog("keyboard(): KeySym 0x%x \"%s\" -> KeyCode 0x%x%s\n",
		    (int) keysym, str ? str : "null", (int) k,
		    k ? "" : " *ignored*");
	}

	if ( k != NoSymbol ) {
		XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime);
		XFlush_wr(dpy);
	}

	X_UNLOCK;
}


