/* -*- c-basic-offset: 8 -*-
   rdesktop: A Remote Desktop Protocol client.
   User interface services - X keyboard mapping

   Copyright (C) Matthew Chapman <matthewc.unsw.edu.au> 1999-2008
   Copyright 2003-2008 Peter Astrand <astrand@cendio.se> for Cendio AB
   Copyright 2014-2017 Henrik Andersson <hean01@cendio.se> for Cendio AB

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <X11/Xlib.h>
#include <X11/keysym.h>

#include <ctype.h>
#include <limits.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#include "rdesktop.h"
#include "scancodes.h"

#define KEYMAP_SIZE 0x7f+1
#define KEYMAP_MASK 0x7f
#define KEYMAP_MAX_LINE_LENGTH 80

extern Display *g_display;
extern Window g_wnd;
extern char g_keymapname[16];
extern unsigned int g_keylayout;
extern int g_keyboard_type;
extern int g_keyboard_subtype;
extern int g_keyboard_functionkeys;
extern int g_win_button_size;
extern RD_BOOL g_enable_compose;
extern RD_BOOL g_seamless_rdp;
extern RD_BOOL g_seamless_active;
extern RDP_VERSION g_rdp_version;
extern RD_BOOL g_numlock_sync;

static RD_BOOL keymap_loaded;
static key_translation_entry *keymap[KEYMAP_SIZE];
static KeySym keypress_keysyms[256];
static int min_keycode;
static uint16 remote_modifier_state = 0;
static uint16 saved_remote_modifier_state = 0;

static void update_modifier_state(uint8 scancode, RD_BOOL pressed);

/* Free key_translation structure, including linked list */
static void
free_key_translation(key_translation * ptr)
{
	key_translation *next;

	while (ptr)
	{
		next = ptr->next;
		xfree(ptr);
		ptr = next;
	}
}

/* Free the key_translation_entry for a given keysym and remove from the table */
static void
delete_key_translation_entry(KeySym keysym)
{
	uint32 hash;
	key_translation_entry *ptr;
	key_translation_entry *next;
	key_translation_entry *prev;
	key_translation_entry tmp;

	/* Faking a prev node allows us to keep the algorithm simple */
	hash = keysym & KEYMAP_MASK;
	ptr = keymap[hash];
	tmp.next = ptr;
	prev = &tmp;

	while (ptr)
	{
		next = ptr->next;
		if (ptr->keysym == keysym)
		{
			free_key_translation(ptr->tr);
			prev->next = next;
			xfree(ptr);
		}
		else
		{
			prev = ptr;
		}

		ptr = next;
	}

	/* Copy pointer back from our fake node */
	keymap[hash] = tmp.next;
}

/* Allocate and return a new entry in the translation table */
static key_translation_entry *
new_key_translation_entry(KeySym keysym)
{
	uint32 hash;
	key_translation_entry *entry;

	/* Clear out any existing entry */
	delete_key_translation_entry(keysym);

	/* Allocate the new one */
	entry = (key_translation_entry *) xmalloc(sizeof(key_translation_entry));
	memset(entry, 0, sizeof(key_translation_entry));
	entry->keysym = keysym;

	/* And insert it at head of list */
	hash = keysym & KEYMAP_MASK;
	entry->next = keymap[hash];
	keymap[hash] = entry;

	return entry;
}

/* Retrieve the key_translation_entry for a given keysym */
static key_translation_entry *
get_key_translation_entry(uint32 keysym)
{
	key_translation_entry *ptr;
	key_translation_entry *next;

	ptr = keymap[keysym & KEYMAP_MASK];

	while (ptr)
	{
		next = ptr->next;
		if (ptr->keysym == keysym)
			return ptr;

		ptr = next;
	}

	/* Not found */
	return NULL;
}

static void
add_to_keymap(char *keyname, uint8 scancode, uint16 modifiers, char *mapname)
{
	KeySym keysym;
	key_translation_entry *entry;

	keysym = XStringToKeysym(keyname);
	if (keysym == NoSymbol)
	{
		logger(Keyboard, Error, "add_to_keymap(), ignoring bad keysym '%s' in keymap %s",
		       keyname, mapname);
		return;
	}

	logger(Keyboard, Debug, "add_to_keymap(), adding translation, keysym=0x%x, scancode=0x%x, "
	       "modifiers=0x%x", (unsigned int) keysym, scancode, modifiers);

	/* Make a new entry in the table */
	entry = new_key_translation_entry(keysym);

	/* And add the new translation to it */
	entry->tr = (key_translation *) xmalloc(sizeof(key_translation));
	memset(entry->tr, 0, sizeof(key_translation));
	entry->tr->scancode = scancode;
	entry->tr->modifiers = modifiers;

	return;
}

static void
add_sequence(char *rest, char *mapname)
{
	KeySym keysym;
	KeySym seq_keysym;
	key_translation_entry *entry;
	key_translation *tr, **prev_next;
	size_t chars;
	char keyname[KEYMAP_MAX_LINE_LENGTH];

	/* Skip over whitespace after the sequence keyword */
	chars = strspn(rest, " \t");
	rest += chars;

	/* Fetch the keysym name */
	chars = strcspn(rest, " \t\0");
	STRNCPY(keyname, rest, chars + 1);
	rest += chars;

	keysym = XStringToKeysym(keyname);
	if (keysym == NoSymbol)
	{
		logger(Keyboard, Error, "add_sequence(), ignoring bad keysym '%s' in keymap %s",
		       keyname, mapname);
		return;
	}

	logger(Keyboard, Debug, "add_sequence(), adding sequence for keysym '%s' (0x%lx)", keyname,
	       keysym);

	entry = new_key_translation_entry(keysym);
	prev_next = &(entry->tr);

	while (*rest)
	{
		/* Skip whitespace */
		chars = strspn(rest, " \t");
		rest += chars;

		/* Fetch the keysym name */
		chars = strcspn(rest, " \t\0");
		STRNCPY(keyname, rest, chars + 1);
		rest += chars;

		/* Handle trailing whitespace */
		if (*keyname == 0)
			break;

		seq_keysym = XStringToKeysym(keyname);
		if (seq_keysym == NoSymbol)
		{
			logger(Keyboard, Error,
			       "add_sequence(), ignoring line with bad keysym '%s' in keymap %s",
			       keyname, mapname);
			delete_key_translation_entry(keysym);
			return;
		}

		/* Allocate space for key_translation structure */
		tr = (key_translation *) xmalloc(sizeof(key_translation));
		memset(tr, 0, sizeof(key_translation));

		/* Do this straight away so the key_translation won't get orphaned on error */
		if (!entry->tr)
			entry->tr = tr;

		*prev_next = tr;
		prev_next = &tr->next;
		tr->seq_keysym = seq_keysym;
	}
}

RD_BOOL
xkeymap_from_locale(const char *locale)
{
	char *str, *ptr;
	FILE *fp;

	/* Create a working copy */
	str = xstrdup(locale);

	/* Truncate at dot and at */
	ptr = strrchr(str, '.');
	if (ptr)
		*ptr = '\0';
	ptr = strrchr(str, '@');
	if (ptr)
		*ptr = '\0';

	/* Replace _ with - */
	ptr = strrchr(str, '_');
	if (ptr)
		*ptr = '-';

	/* Convert to lowercase */
	ptr = str;
	while (*ptr)
	{
		*ptr = tolower((int) *ptr);
		ptr++;
	}

	/* Try to open this keymap (da-dk) */
	fp = xkeymap_open(str);
	if (fp == NULL)
	{
		/* Truncate at dash */
		ptr = strrchr(str, '-');
		if (ptr)
			*ptr = '\0';

		/* Try the short name (da) */
		fp = xkeymap_open(str);
	}

	if (fp)
	{
		fclose(fp);
		STRNCPY(g_keymapname, str, sizeof(g_keymapname));
		xfree(str);
		return True;
	}

	xfree(str);
	return False;
}


/* Joins two path components. The result should be freed with
   xfree(). */
static char *
pathjoin(const char *a, const char *b)
{
	char *result;
	result = xmalloc(PATH_MAX * 2 + 1);

	if (b[0] == '/')
	{
		strncpy(result, b, PATH_MAX);
	}
	else
	{
		strncpy(result, a, PATH_MAX);
		strcat(result, "/");
		strncat(result, b, PATH_MAX);
	}
	return result;
}

/* Try to open a keymap with fopen() */
FILE *
xkeymap_open(const char *filename)
{
	char *path1, *path2;
	char *home;
	FILE *fp;

	/* Try ~/.rdesktop/keymaps */
	home = getenv("HOME");
	if (home)
	{
		path1 = pathjoin(home, ".rdesktop/keymaps");
		path2 = pathjoin(path1, filename);
		xfree(path1);
		fp = fopen(path2, "r");
		xfree(path2);
		if (fp)
			return fp;
	}

	/* Try KEYMAP_PATH */
	path1 = pathjoin(KEYMAP_PATH, filename);
	fp = fopen(path1, "r");
	xfree(path1);
	if (fp)
		return fp;

	/* Try current directory, in case we are running from the source
	   tree */
	path1 = pathjoin("keymaps", filename);
	fp = fopen(path1, "r");
	xfree(path1);
	if (fp)
		return fp;

	return NULL;
}

static RD_BOOL
xkeymap_read(char *mapname)
{
	FILE *fp;
	char line[KEYMAP_MAX_LINE_LENGTH];
	unsigned int line_num = 0;
	unsigned int line_length = 0;
	char *keyname, *p;
	char *line_rest;
	uint8 scancode;
	uint16 modifiers;

	fp = xkeymap_open(mapname);
	if (fp == NULL)
	{
		logger(Keyboard, Error, "xkeymap_read(), failed to open keymap %s", mapname);
		return False;
	}

	/* FIXME: More tolerant on white space */
	while (fgets(line, sizeof(line), fp) != NULL)
	{
		line_num++;

		/* Replace the \n with \0 */
		p = strchr(line, '\n');
		if (p != NULL)
			*p = 0;

		line_length = strlen(line);

		/* Completely empty line */
		if (strspn(line, " \t\n\r\f\v") == line_length)
		{
			continue;
		}

		/* Include */
		if (str_startswith(line, "include "))
		{
			if (!xkeymap_read(line + sizeof("include ") - 1))
				return False;
			continue;
		}

		/* map */
		if (str_startswith(line, "map "))
		{
			g_keylayout = strtoul(line + sizeof("map ") - 1, NULL, 16);
			logger(Keyboard, Debug, "xkeymap_read(), Keylayout 0x%x", g_keylayout);
			continue;
		}

		/* compose */
		if (str_startswith(line, "enable_compose"))
		{
			logger(Keyboard, Debug, "xkeymap_read(), enabling compose handling");
			g_enable_compose = True;
			continue;
		}

		/* sequence */
		if (str_startswith(line, "sequence"))
		{
			add_sequence(line + sizeof("sequence") - 1, mapname);
			continue;
		}

		/* keyboard_type */
		if (str_startswith(line, "keyboard_type "))
		{
			g_keyboard_type = strtol(line + sizeof("keyboard_type ") - 1, NULL, 16);
			logger(Keyboard, Debug, "xkeymap_read(), keyboard_type 0x%x",
			       g_keyboard_type);
			continue;
		}

		/* keyboard_subtype */
		if (str_startswith(line, "keyboard_subtype "))
		{
			g_keyboard_subtype =
				strtol(line + sizeof("keyboard_subtype ") - 1, NULL, 16);
			logger(Keyboard, Debug, "xkeymap_read(), keyboard_subtype 0x%x",
			       g_keyboard_subtype);
			continue;
		}

		/* keyboard_functionkeys */
		if (str_startswith(line, "keyboard_functionkeys "))
		{
			g_keyboard_functionkeys =
				strtol(line + sizeof("keyboard_functionkeys ") - 1, NULL, 16);
			logger(Keyboard, Debug, "xkeymap_read(), keyboard_functionkeys 0x%x",
			       g_keyboard_functionkeys);
			continue;
		}

		/* Comment */
		if (line[0] == '#')
		{
			continue;
		}

		/* Normal line */
		keyname = line;
		p = strchr(line, ' ');
		if (p == NULL)
		{
			logger(Keyboard, Error, "xkeymap_read(), bad line %d in keymap %s",
			       line_num, mapname);
			continue;
		}
		else
		{
			*p = 0;
		}

		/* scancode */
		p++;
		scancode = strtol(p, &line_rest, 16);

		/* flags */
		/* FIXME: Should allow case-insensitive flag names. 
		   Fix by using lex+yacc... */
		modifiers = 0;
		if (strstr(line_rest, "altgr"))
		{
			MASK_ADD_BITS(modifiers, MapAltGrMask);
		}

		if (strstr(line_rest, "shift"))
		{
			MASK_ADD_BITS(modifiers, MapLeftShiftMask);
		}

		if (strstr(line_rest, "numlock"))
		{
			MASK_ADD_BITS(modifiers, MapNumLockMask);
		}

		if (strstr(line_rest, "localstate"))
		{
			MASK_ADD_BITS(modifiers, MapLocalStateMask);
		}

		if (strstr(line_rest, "inhibit"))
		{
			MASK_ADD_BITS(modifiers, MapInhibitMask);
		}

		add_to_keymap(keyname, scancode, modifiers, mapname);

		if (strstr(line_rest, "addupper"))
		{
			/* Automatically add uppercase key, with same modifiers 
			   plus shift */
			for (p = keyname; *p; p++)
				*p = toupper((int) *p);
			MASK_ADD_BITS(modifiers, MapLeftShiftMask);
			add_to_keymap(keyname, scancode, modifiers, mapname);
		}
	}

	fclose(fp);
	return True;
}


/* Before connecting and creating UI */
void
xkeymap_init(void)
{
	unsigned int max_keycode;

	if (strcmp(g_keymapname, "none"))
	{
		if (xkeymap_read(g_keymapname))
			keymap_loaded = True;
	}

	XDisplayKeycodes(g_display, &min_keycode, (int *) &max_keycode);
}

static void
send_winkey(uint32 ev_time, RD_BOOL pressed, RD_BOOL leftkey)
{
	uint8 winkey;

	if (leftkey)
		winkey = SCANCODE_CHAR_LWIN;
	else
		winkey = SCANCODE_CHAR_RWIN;

	if (pressed)
	{
		if (g_rdp_version >= RDP_V5)
		{
			rdp_send_scancode(ev_time, RDP_KEYPRESS, winkey);
		}
		else
		{
			/* RDP4 doesn't support winkey. Fake with Ctrl-Esc */
			rdp_send_scancode(ev_time, RDP_KEYPRESS, SCANCODE_CHAR_LCTRL);
			rdp_send_scancode(ev_time, RDP_KEYPRESS, SCANCODE_CHAR_ESC);
		}
	}
	else
	{
		/* key released */
		if (g_rdp_version >= RDP_V5)
		{
			rdp_send_scancode(ev_time, RDP_KEYRELEASE, winkey);
		}
		else
		{
			rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_ESC);
			rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_LCTRL);
		}
	}
}

static void
reset_winkey(uint32 ev_time)
{
	if (g_rdp_version >= RDP_V5)
	{
		/* For some reason, it seems to suffice to release
		 *either* the left or right winkey. */
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_LWIN);
	}
}


void
set_keypress_keysym(unsigned int keycode, KeySym keysym)
{
	if (keycode < 8 || keycode > 255)
		return;
	keypress_keysyms[keycode] = keysym;
}


KeySym
reset_keypress_keysym(unsigned int keycode, KeySym keysym)
{
	KeySym ks;
	if (keycode < 8 || keycode > 255)
		return keysym;
	ks = keypress_keysyms[keycode];
	if (ks != 0)
	{
		keypress_keysyms[keycode] = 0;
	}
	else
	{
		ks = keysym;
	}

	return ks;
}


/* Handle special key combinations */
RD_BOOL
handle_special_keys(uint32 keysym, unsigned int state, uint32 ev_time, RD_BOOL pressed)
{
	switch (keysym)
	{
		case XK_Return:
			if ((get_key_state(state, XK_Alt_L) || get_key_state(state, XK_Alt_R))
			    && (get_key_state(state, XK_Control_L)
				|| get_key_state(state, XK_Control_R)))
			{
				/* Ctrl-Alt-Enter: toggle full screen */
				if (pressed)
				{
					if (!g_seamless_rdp)
					{
						/* only allow toggle fullscreen when not running
						   rdesktop in seamless mode */
						xwin_toggle_fullscreen();
					}
					else
					{
						/* deactivate seamless mode for debug purpose, one can
						   not activate seamless mode again  */
						if (g_seamless_active)
							ui_seamless_toggle();
					}
				}
				return True;
			}
			break;

		case XK_Break:
			/* Send Break sequence E0 46 E0 C6 */
			if (pressed)
			{
				rdp_send_scancode(ev_time, RDP_KEYPRESS,
						  (SCANCODE_EXTENDED | 0x46));
				rdp_send_scancode(ev_time, RDP_KEYPRESS,
						  (SCANCODE_EXTENDED | 0xc6));
			}
			/* No release sequence */
			return True;
			break;

		case XK_Pause:
			/* According to the RDP documentation (MS-RDPBCGR, page 164),
			   pressing Pause must result in:
			   CTRL    (0x1D) DOWN  with the KBDFLAGS_EXTENDED1 flag
			   NUMLOCK (0x45) DOWN
			   CTRL    (0x1D) UP    with the KBDFLAGS_EXTENDED1 flag
			   NUMLOCK (0x45) UP
			 */
			if (pressed)
			{
				rdp_send_input(ev_time, RDP_INPUT_SCANCODE,
					       RDP_KEYPRESS | KBD_FLAG_EXT1, 0x1d, 0);
				rdp_send_input(ev_time, RDP_INPUT_SCANCODE, RDP_KEYPRESS, 0x45, 0);
				rdp_send_input(ev_time, RDP_INPUT_SCANCODE,
					       RDP_KEYRELEASE | KBD_FLAG_EXT1, 0x1d, 0);
				rdp_send_input(ev_time, RDP_INPUT_SCANCODE, RDP_KEYRELEASE, 0x45,
					       0);
			}
			return True;
			break;

		case XK_Meta_L:	/* Windows keys */
		case XK_Super_L:
		case XK_Hyper_L:
			send_winkey(ev_time, pressed, True);
			return True;
			break;

		case XK_Meta_R:
		case XK_Super_R:
		case XK_Hyper_R:
			send_winkey(ev_time, pressed, False);
			return True;
			break;

		case XK_space:
			/* Prevent access to the Windows system menu in single app mode */
			if (g_win_button_size
			    && (get_key_state(state, XK_Alt_L) || get_key_state(state, XK_Alt_R)))
				return True;
			break;

		case XK_Num_Lock:
			/* Synchronize on key release */
			if (g_numlock_sync && !pressed)
				rdp_send_input(0, RDP_INPUT_SYNCHRONIZE, 0,
					       ui_get_numlock_state(read_keyboard_state()), 0);

			/* Inhibit */
			return True;
			break;
		case XK_Overlay1_Enable:
			/* Toggle SeamlessRDP */
			if (pressed)
				ui_seamless_toggle();
			break;

	}
	return False;
}


key_translation
xkeymap_translate_key(uint32 keysym, unsigned int keycode, unsigned int state)
{
	key_translation tr = { 0, 0, 0, 0 };
	key_translation *ptr;
	key_translation_entry *entry;

	entry = get_key_translation_entry(keysym);
	ptr = entry ? entry->tr : NULL;

	if (ptr)
	{
		tr = *ptr;
		if (tr.seq_keysym == 0)	/* Normal scancode translation */
		{
			if (MASK_HAS_BITS(tr.modifiers, MapInhibitMask))
			{
				logger(Keyboard, Debug, "xkeymap_translate_key(), inhibiting key");
				tr.scancode = 0;
				return tr;
			}

			if (MASK_HAS_BITS(tr.modifiers, MapLocalStateMask))
			{
				/* The modifiers to send for this key should be obtained
				   from the local state. Currently, only shift is implemented. */
				if (MASK_HAS_BITS(state, ShiftMask))
				{
					tr.modifiers = MapLeftShiftMask;
				}
			}

			/* Windows interprets CapsLock+Ctrl+key
			   differently from Shift+Ctrl+key. Since we
			   are simulating CapsLock with Shifts, things
			   like Ctrl+f with CapsLock on breaks. To
			   solve this, we are releasing Shift if Ctrl
			   is on, but only if Shift isn't physically pressed. */
			if (MASK_HAS_BITS(tr.modifiers, MapShiftMask)
			    && MASK_HAS_BITS(remote_modifier_state, MapCtrlMask)
			    && !MASK_HAS_BITS(state, ShiftMask))
			{
				logger(Keyboard, Debug,
				       "xkeymap_translate_key(), non-physical Shift + Ctrl pressed, releasing Shift");
				MASK_REMOVE_BITS(tr.modifiers, MapShiftMask);
			}

			logger(Keyboard, Debug,
			       "xkeymap_translate_key(), found scancode translation, scancode=0x%x, modifiers=0x%x",
			       tr.scancode, tr.modifiers);
		}
	}
	else
	{
		if (keymap_loaded)
			logger(Keyboard, Warning, "No translation for (keysym 0x%lx, %s)", keysym,
			       get_ksname(keysym));

		/* not in keymap, try to interpret the raw scancode */
		if (((int) keycode >= min_keycode) && (keycode <= 0x60))
		{
			tr.scancode = keycode - min_keycode;

			/* The modifiers to send for this key should be
			   obtained from the local state. Currently, only
			   shift is implemented. */
			if (MASK_HAS_BITS(state, ShiftMask))
			{
				tr.modifiers = MapLeftShiftMask;
			}

			logger(Keyboard, Debug, "Sending guessed scancode 0x%x", tr.scancode);
		}
		else
		{
			logger(Keyboard, Debug, "No good guess for keycode 0x%x found", keycode);
		}
	}

	return tr;
}

static RD_BOOL
is_modifier(uint8 scancode)
{
	switch (scancode)
	{
		case SCANCODE_CHAR_LSHIFT:
		case SCANCODE_CHAR_RSHIFT:
		case SCANCODE_CHAR_LCTRL:
		case SCANCODE_CHAR_RCTRL:
		case SCANCODE_CHAR_LALT:
		case SCANCODE_CHAR_RALT:
		case SCANCODE_CHAR_LWIN:
		case SCANCODE_CHAR_RWIN:
		case SCANCODE_CHAR_NUMLOCK:
			return True;
		default:
			break;
	}
	return False;
}


void
xkeymap_send_keys(uint32 keysym, unsigned int keycode, unsigned int state, uint32 ev_time,
		  RD_BOOL pressed, uint8 nesting)
{
	key_translation tr, *ptr;
	tr = xkeymap_translate_key(keysym, keycode, state);

	if (tr.seq_keysym == 0)
	{
		/* Scancode translation */
		if (tr.scancode == 0)
			return;

		save_remote_modifiers(tr.scancode);
		ensure_remote_modifiers(ev_time, tr);
		rdp_send_scancode(ev_time, pressed ? RDP_KEYPRESS : RDP_KEYRELEASE, tr.scancode);
		restore_remote_modifiers(ev_time, tr.scancode);
		return;
	}

	/* Sequence, only on key down */
	if (pressed)
	{
		ptr = &tr;
		do
		{
			logger(Keyboard, Debug,
			       "xkeymap_send_keys(), handling sequence element, keysym=0x%x",
			       (unsigned int) ptr->seq_keysym);

			if (nesting++ > 32)
			{
				logger(Keyboard, Error, "Sequence nesting too deep");
				return;
			}

			xkeymap_send_keys(ptr->seq_keysym, keycode, state, ev_time, True, nesting);
			xkeymap_send_keys(ptr->seq_keysym, keycode, state, ev_time, False, nesting);
			ptr = ptr->next;
		}
		while (ptr);
	}
}

uint16
xkeymap_translate_button(unsigned int button, uint16 * input_type)
{
	*input_type = RDP_INPUT_MOUSE;

	switch (button)
	{
		case Button1:	/* left */
			return MOUSE_FLAG_BUTTON1;
		case Button2:	/* middle */
			return MOUSE_FLAG_BUTTON3;
		case Button3:	/* right */
			return MOUSE_FLAG_BUTTON2;
		case Button4:	/* wheel up */
			return MOUSE_FLAG_BUTTON4;
		case Button5:	/* wheel down */
			return MOUSE_FLAG_BUTTON5;
		case 8:	/* button 4 */
			*input_type = RDP_INPUT_MOUSEX;
			return MOUSEX_FLAG_BUTTON1;
		case 9:	/* button 5 */
			*input_type = RDP_INPUT_MOUSEX;
			return MOUSEX_FLAG_BUTTON2;
	}

	return 0;
}

char *
get_ksname(uint32 keysym)
{
	char *ksname = NULL;

	if (keysym == NoSymbol)
		ksname = "NoSymbol";
	else if (!(ksname = XKeysymToString(keysym)))
		ksname = "(no name)";

	return ksname;
}

void
save_remote_modifiers(uint8 scancode)
{
	if (is_modifier(scancode))
		return;

	saved_remote_modifier_state = remote_modifier_state;
}

void
restore_remote_modifiers(uint32 ev_time, uint8 scancode)
{
	key_translation dummy = { 0 };

	if (is_modifier(scancode))
		return;

	dummy.scancode = 0;
	dummy.modifiers = saved_remote_modifier_state;
	ensure_remote_modifiers(ev_time, dummy);
}

void
ensure_remote_modifiers(uint32 ev_time, key_translation tr)
{
	/* If this key is a modifier, do nothing */
	if (is_modifier(tr.scancode))
		return;

	if (!g_numlock_sync)
	{
		/* NumLock */
		if (MASK_HAS_BITS(tr.modifiers, MapNumLockMask)
		    != MASK_HAS_BITS(remote_modifier_state, MapNumLockMask))
		{
			/* The remote modifier state is not correct */
			uint16 new_remote_state;

			if (MASK_HAS_BITS(tr.modifiers, MapNumLockMask))
			{
				logger(Keyboard, Debug,
				       "ensure_remote_modifiers(), remote NumLock state is incorrect, activating NumLock");
				new_remote_state = KBD_FLAG_NUMLOCK;
				remote_modifier_state = MapNumLockMask;
			}
			else
			{
				logger(Keyboard, Debug,
				       "ensure_remote_modifiers(), remote NumLock state is incorrect, deactivating NumLock.");
				new_remote_state = 0;
				remote_modifier_state = 0;
			}

			rdp_send_input(0, RDP_INPUT_SYNCHRONIZE, 0, new_remote_state, 0);
		}
	}


	/* Shift. Left shift and right shift are treated as equal; either is fine. */
	if (MASK_HAS_BITS(tr.modifiers, MapShiftMask)
	    != MASK_HAS_BITS(remote_modifier_state, MapShiftMask))
	{
		/* The remote modifier state is not correct */
		if (MASK_HAS_BITS(tr.modifiers, MapLeftShiftMask))
		{
			/* Needs left shift. Send down. */
			rdp_send_scancode(ev_time, RDP_KEYPRESS, SCANCODE_CHAR_LSHIFT);
		}
		else if (MASK_HAS_BITS(tr.modifiers, MapRightShiftMask))
		{
			/* Needs right shift. Send down. */
			rdp_send_scancode(ev_time, RDP_KEYPRESS, SCANCODE_CHAR_RSHIFT);
		}
		else
		{
			/* Should not use this modifier. Send up for shift currently pressed. */
			if (MASK_HAS_BITS(remote_modifier_state, MapLeftShiftMask))
				/* Left shift is down */
				rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_LSHIFT);
			else
				/* Right shift is down */
				rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_RSHIFT);
		}
	}

	/* AltGr */
	if (MASK_HAS_BITS(tr.modifiers, MapAltGrMask)
	    != MASK_HAS_BITS(remote_modifier_state, MapAltGrMask))
	{
		/* The remote modifier state is not correct */
		if (MASK_HAS_BITS(tr.modifiers, MapAltGrMask))
		{
			/* Needs this modifier. Send down. */
			rdp_send_scancode(ev_time, RDP_KEYPRESS, SCANCODE_CHAR_RALT);
		}
		else
		{
			/* Should not use this modifier. Send up. */
			rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_RALT);
		}
	}


}


unsigned int
read_keyboard_state()
{
	unsigned int state;
	Window wdummy;
	int dummy;

	XQueryPointer(g_display, g_wnd, &wdummy, &wdummy, &dummy, &dummy, &dummy, &dummy, &state);
	return state;
}


uint16
ui_get_numlock_state(unsigned int state)
{
	uint16 numlock_state = 0;

	if (get_key_state(state, XK_Num_Lock))
		numlock_state = KBD_FLAG_NUMLOCK;

	return numlock_state;
}


void
reset_modifier_keys()
{
	unsigned int state = read_keyboard_state();

	/* reset keys */
	uint32 ev_time;
	ev_time = time(NULL);

	if (MASK_HAS_BITS(remote_modifier_state, MapLeftShiftMask)
	    && !get_key_state(state, XK_Shift_L))
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_LSHIFT);

	if (MASK_HAS_BITS(remote_modifier_state, MapRightShiftMask)
	    && !get_key_state(state, XK_Shift_R))
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_RSHIFT);

	if (MASK_HAS_BITS(remote_modifier_state, MapLeftCtrlMask)
	    && !get_key_state(state, XK_Control_L))
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_LCTRL);

	if (MASK_HAS_BITS(remote_modifier_state, MapRightCtrlMask)
	    && !get_key_state(state, XK_Control_R))
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_RCTRL);

	if (MASK_HAS_BITS(remote_modifier_state, MapLeftAltMask) && !get_key_state(state, XK_Alt_L))
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_LALT);

	if (MASK_HAS_BITS(remote_modifier_state, MapRightAltMask) &&
	    !get_key_state(state, XK_Alt_R) && !get_key_state(state, XK_Mode_switch)
	    && !get_key_state(state, XK_ISO_Level3_Shift))
		rdp_send_scancode(ev_time, RDP_KEYRELEASE, SCANCODE_CHAR_RALT);

	reset_winkey(ev_time);

	if (g_numlock_sync)
		rdp_send_input(ev_time, RDP_INPUT_SYNCHRONIZE, 0, ui_get_numlock_state(state), 0);
}


static void
update_modifier_state(uint8 scancode, RD_BOOL pressed)
{
	uint16 old_modifier_state;

	old_modifier_state = remote_modifier_state;

	switch (scancode)
	{
		case SCANCODE_CHAR_LSHIFT:
			MASK_CHANGE_BIT(remote_modifier_state, MapLeftShiftMask, pressed);
			break;
		case SCANCODE_CHAR_RSHIFT:
			MASK_CHANGE_BIT(remote_modifier_state, MapRightShiftMask, pressed);
			break;
		case SCANCODE_CHAR_LCTRL:
			MASK_CHANGE_BIT(remote_modifier_state, MapLeftCtrlMask, pressed);
			break;
		case SCANCODE_CHAR_RCTRL:
			MASK_CHANGE_BIT(remote_modifier_state, MapRightCtrlMask, pressed);
			break;
		case SCANCODE_CHAR_LALT:
			MASK_CHANGE_BIT(remote_modifier_state, MapLeftAltMask, pressed);
			break;
		case SCANCODE_CHAR_RALT:
			MASK_CHANGE_BIT(remote_modifier_state, MapRightAltMask, pressed);
			break;
		case SCANCODE_CHAR_LWIN:
			MASK_CHANGE_BIT(remote_modifier_state, MapLeftWinMask, pressed);
			break;
		case SCANCODE_CHAR_RWIN:
			MASK_CHANGE_BIT(remote_modifier_state, MapRightWinMask, pressed);
			break;
		case SCANCODE_CHAR_NUMLOCK:
			/* KeyReleases for NumLocks are sent immediately. Toggle the
			   modifier state only on Keypress */
			if (pressed && !g_numlock_sync)
			{
				RD_BOOL newNumLockState;
				newNumLockState =
					(MASK_HAS_BITS
					 (remote_modifier_state, MapNumLockMask) == False);
				MASK_CHANGE_BIT(remote_modifier_state,
						MapNumLockMask, newNumLockState);
			}
	}

	if (old_modifier_state != remote_modifier_state)
	{
		logger(Keyboard, Debug,
		       "update_modifier_state(), before modifier_state:0x%x, pressed=0x%x",
		       old_modifier_state, pressed);
		logger(Keyboard, Debug, "update_modifier_state(), after modifier_state:0x%x",
		       remote_modifier_state);
	}
}

/* Send keyboard input */
void
rdp_send_scancode(uint32 time, uint16 flags, uint8 scancode)
{
	update_modifier_state(scancode, !(flags & RDP_KEYRELEASE));

	if (scancode & SCANCODE_EXTENDED)
	{
		logger(Keyboard, Debug,
		       "rdp_send_scancode(), sending extended scancode=0x%x, flags=0x%x",
		       scancode & ~SCANCODE_EXTENDED, flags);
		rdp_send_input(time, RDP_INPUT_SCANCODE, flags | KBD_FLAG_EXT,
			       scancode & ~SCANCODE_EXTENDED, 0);
	}
	else
	{
		logger(Keyboard, Debug, "rdp_send_scancode(), sending scancode=0x%x, flags=0x%x",
		       scancode, flags);
		rdp_send_input(time, RDP_INPUT_SCANCODE, flags, scancode, 0);
	}
}
