Index: uspace/srv/kbd/Makefile
===================================================================
--- uspace/srv/kbd/Makefile	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/Makefile	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -45,4 +45,6 @@
 GENERIC_SOURCES = \
 	generic/kbd.c \
+	genarch/gsp.c \
+	genarch/stroke.c \
 	generic/key_buffer.c
 
Index: uspace/srv/kbd/ctl/gxe_fb.c
===================================================================
--- uspace/srv/kbd/ctl/gxe_fb.c	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/ctl/gxe_fb.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -40,279 +40,186 @@
 #include <kbd/keycode.h>
 #include <kbd_ctl.h>
-
-static void parse_ds_start(int scancode);
-static void parse_ds_e(int scancode);
-static void parse_ds_e1(int scancode);
-static void parse_ds_e1a(int scancode);
-static void parse_ds_e1b(int scancode);
-static void parse_ds_e1c(int scancode);
-
-static void parse_leaf(int scancode, int (*map)[2], size_t map_length);
-
-enum dec_state {
-	ds_start,
-	ds_e,
-	ds_e1,
-	ds_e1a,
-	ds_e1b,
-	ds_e1c
+#include <gsp.h>
+#include <stroke.h>
+
+/** Scancode parser */
+static gsp_t sp;
+
+/** Current parser state */
+static int ds;
+
+#include <stdio.h>
+
+int seq_defs[] = {
+	/* Not shifted */
+
+	0,	KC_BACKTICK,	0x60, GSP_END,
+
+	0,	KC_1,		0x31, GSP_END,
+	0,	KC_2,		0x32, GSP_END,
+	0,	KC_3,		0x33, GSP_END,
+	0,	KC_4,		0x34, GSP_END,
+	0,	KC_5,		0x35, GSP_END,
+	0,	KC_6,		0x36, GSP_END,
+	0,	KC_7,		0x37, GSP_END,
+	0,	KC_8,		0x38, GSP_END,
+	0,	KC_9,		0x39, GSP_END,
+	0,	KC_0,		0x30, GSP_END,
+
+	0,	KC_MINUS,	0x2d, GSP_END,
+	0,	KC_EQUALS,	0x3d, GSP_END,
+	0,	KC_BACKSPACE,	0x08, GSP_END,
+
+	0,	KC_TAB,		0x09, GSP_END,
+
+	0,	KC_Q,		0x71, GSP_END,
+	0,	KC_W,		0x77, GSP_END,
+	0,	KC_E,		0x65, GSP_END,
+	0,	KC_R,		0x72, GSP_END,
+	0,	KC_T,		0x74, GSP_END,
+	0,	KC_Y,		0x79, GSP_END,
+	0,	KC_U,		0x75, GSP_END,
+	0,	KC_I,		0x69, GSP_END,
+	0,	KC_O,		0x6f, GSP_END,
+	0,	KC_P,		0x70, GSP_END,
+
+	0,	KC_LBRACKET,	0x5b, GSP_END,
+	0,	KC_RBRACKET,	0x5d, GSP_END,
+
+	0,	KC_A,		0x61, GSP_END,
+	0,	KC_S,		0x73, GSP_END,
+	0,	KC_D,		0x64, GSP_END,
+	0,	KC_F,		0x66, GSP_END,
+	0,	KC_G,		0x67, GSP_END,
+	0,	KC_H,		0x68, GSP_END,
+	0,	KC_J,		0x6a, GSP_END,
+	0,	KC_K,		0x6b, GSP_END,
+	0,	KC_L,		0x6c, GSP_END,
+
+	0,	KC_SEMICOLON,	0x3b, GSP_END,
+	0,	KC_QUOTE,	0x27, GSP_END,
+	0,	KC_BACKSLASH,	0x5c, GSP_END,
+
+	0,	KC_Z,		0x7a, GSP_END,
+	0,	KC_X,		0x78, GSP_END,
+	0,	KC_C,		0x63, GSP_END,
+	0,	KC_V,		0x76, GSP_END,
+	0,	KC_B,		0x62, GSP_END,
+	0,	KC_N,		0x6e, GSP_END,
+	0,	KC_M,		0x6d, GSP_END,
+
+	0,	KC_COMMA,	0x2c, GSP_END,
+	0,	KC_PERIOD,	0x2e, GSP_END,
+	0,	KC_SLASH,	0x2f, GSP_END,
+
+	/* Shifted */
+
+	KM_SHIFT,	KC_BACKTICK,	0x7e, GSP_END,
+
+	KM_SHIFT,	KC_1,		0x21, GSP_END,
+	KM_SHIFT,	KC_2,		0x40, GSP_END,
+	KM_SHIFT,	KC_3,		0x23, GSP_END,
+	KM_SHIFT,	KC_4,		0x24, GSP_END,
+	KM_SHIFT,	KC_5,		0x25, GSP_END,
+	KM_SHIFT,	KC_6,		0x5e, GSP_END,
+	KM_SHIFT,	KC_7,		0x26, GSP_END,
+	KM_SHIFT,	KC_8,		0x2a, GSP_END,
+	KM_SHIFT,	KC_9,		0x28, GSP_END,
+	KM_SHIFT,	KC_0,		0x29, GSP_END,
+
+	KM_SHIFT,	KC_MINUS,	0x5f, GSP_END,
+	KM_SHIFT,	KC_EQUALS,	0x2b, GSP_END,
+
+	KM_SHIFT,	KC_Q,		0x51, GSP_END,
+	KM_SHIFT,	KC_W,		0x57, GSP_END,
+	KM_SHIFT,	KC_E,		0x45, GSP_END,
+	KM_SHIFT,	KC_R,		0x52, GSP_END,
+	KM_SHIFT,	KC_T,		0x54, GSP_END,
+	KM_SHIFT,	KC_Y,		0x59, GSP_END,
+	KM_SHIFT,	KC_U,		0x55, GSP_END,
+	KM_SHIFT,	KC_I,		0x49, GSP_END,
+	KM_SHIFT,	KC_O,		0x4f, GSP_END,
+	KM_SHIFT,	KC_P,		0x50, GSP_END,
+
+	KM_SHIFT,	KC_LBRACKET,	0x7b, GSP_END,
+	KM_SHIFT,	KC_RBRACKET,	0x7d, GSP_END,
+
+	KM_SHIFT,	KC_A,		0x41, GSP_END,
+	KM_SHIFT,	KC_S,		0x53, GSP_END,
+	KM_SHIFT,	KC_D,		0x44, GSP_END,
+	KM_SHIFT,	KC_F,		0x46, GSP_END,
+	KM_SHIFT,	KC_G,		0x47, GSP_END,
+	KM_SHIFT,	KC_H,		0x48, GSP_END,
+	KM_SHIFT,	KC_J,		0x4a, GSP_END,
+	KM_SHIFT,	KC_K,		0x4b, GSP_END,
+	KM_SHIFT,	KC_L,		0x4c, GSP_END,
+
+	KM_SHIFT,	KC_SEMICOLON,	0x3a, GSP_END,
+	KM_SHIFT,	KC_QUOTE,	0x22, GSP_END,
+	KM_SHIFT,	KC_BACKSLASH,	0x7c, GSP_END,
+
+	KM_SHIFT,	KC_Z,		0x5a, GSP_END,
+	KM_SHIFT,	KC_X,		0x58, GSP_END,
+	KM_SHIFT,	KC_C,		0x43, GSP_END,
+	KM_SHIFT,	KC_V,		0x56, GSP_END,
+	KM_SHIFT,	KC_B,		0x42, GSP_END,
+	KM_SHIFT,	KC_N,		0x4e, GSP_END,
+	KM_SHIFT,	KC_M,		0x4d, GSP_END,
+
+	KM_SHIFT,	KC_COMMA,	0x3c, GSP_END,
+	KM_SHIFT,	KC_PERIOD,	0x3e, GSP_END,
+	KM_SHIFT,	KC_SLASH,	0x3f, GSP_END,
+
+	/* ... */
+
+	0,	KC_SPACE,	0x20, GSP_END,
+	0,	KC_ENTER,	0x0a, GSP_END,
+	0,	KC_ENTER,	0x0d, GSP_END,
+
+	0,	KC_ESCAPE,	0x1b, 0x1b, GSP_END,
+
+	0,	KC_F1,		0x1b, 0x5b, 0x4f, 0x50, GSP_END,
+	0,	KC_F2,		0x1b, 0x5b, 0x4f, 0x51, GSP_END,
+	0,	KC_F3,		0x1b, 0x5b, 0x4f, 0x52, GSP_END,
+	0,	KC_F4,		0x1b, 0x5b, 0x4f, 0x53, GSP_END,
+	0,	KC_F5,		0x1b, 0x5b, 0x31, 0x35, GSP_END,
+	0,	KC_F6,		0x1b, 0x5b, 0x31, 0x37, GSP_END,
+	0,	KC_F7,		0x1b, 0x5b, 0x31, 0x38, GSP_END,
+	0,	KC_F8,		0x1b, 0x5b, 0x31, 0x39, GSP_END,
+	0,	KC_F9,		0x1b, 0x5b, 0x32, 0x38, GSP_END,
+	0,	KC_F10,		0x1b, 0x5b, 0x32, 0x39, GSP_END,
+	0,	KC_F11,		0x1b, 0x5b, 0x32, 0x33, GSP_END,
+	0,	KC_F12,		0x1b, 0x5b, 0x32, 0x34, GSP_END,
+
+	0,	KC_INSERT,	0x1b, 0x5b, 0x32, 0x7e, GSP_END,
+	0,	KC_HOME,	0x1b, 0x5b, 0x48, GSP_END,
+	0,	KC_PAGE_UP,	0x1b, 0x5b, 0x35, 0x7e, GSP_END,
+	0,	KC_DELETE,	0x1b, 0x5b, 0x33, 0x7e, GSP_END,
+	0,	KC_END,		0x1b, 0x5b, 0x46, GSP_END,
+	0,	KC_PAGE_DOWN,	0x1b, 0x5b, 0x36, 0x7e, GSP_END,
+
+	0,	KC_UP,		0x1b, 0x5b, 0x41, GSP_END,
+	0,	KC_LEFT,	0x1b, 0x5b, 0x44, GSP_END,
+	0,	KC_DOWN,	0x1b, 0x5b, 0x42, GSP_END,
+	0,	KC_RIGHT,	0x1b, 0x5b, 0x43, GSP_END,
+
+	0,	0
 };
 
-static int map_start[][2] = {
-
-	[0x60] = { 0, KC_BACKTICK },
-
-	[0x31] = { 0, KC_1 },
-	[0x32] = { 0, KC_2 },
-	[0x33] = { 0, KC_3 },
-	[0x34] = { 0, KC_4 },
-	[0x35] = { 0, KC_5 },
-	[0x36] = { 0, KC_6 },
-	[0x37] = { 0, KC_7 },
-	[0x38] = { 0, KC_8 },
-	[0x39] = { 0, KC_9 },
-	[0x30] = { 0, KC_0 },
-
-	[0x2d] = { 0, KC_MINUS },
-	[0x3d] = { 0, KC_EQUALS },
-	[0x08] = { 0, KC_BACKSPACE },
-
-	[0x0f] = { 0, KC_TAB },
-
-	[0x71] = { 0, KC_Q },
-	[0x77] = { 0, KC_W },
-	[0x65] = { 0, KC_E },
-	[0x72] = { 0, KC_R },
-	[0x74] = { 0, KC_T },
-	[0x79] = { 0, KC_Y },
-	[0x75] = { 0, KC_U },
-	[0x69] = { 0, KC_I },
-	[0x6f] = { 0, KC_O },
-	[0x70] = { 0, KC_P },
-
-	[0x5b] = { 0, KC_LBRACKET },
-	[0x5d] = { 0, KC_RBRACKET },
-
-	[0x61] = { 0, KC_A },
-	[0x73] = { 0, KC_S },
-	[0x64] = { 0, KC_D },
-	[0x66] = { 0, KC_F },
-	[0x67] = { 0, KC_G },
-	[0x68] = { 0, KC_H },
-	[0x6a] = { 0, KC_J },
-	[0x6b] = { 0, KC_K },
-	[0x6c] = { 0, KC_L },
-
-	[0x3b] = { 0, KC_SEMICOLON },
-	[0x27] = { 0, KC_QUOTE },
-	[0x5c] = { 0, KC_BACKSLASH },
-
-	[0x7a] = { 0, KC_Z },
-	[0x78] = { 0, KC_X },
-	[0x63] = { 0, KC_C },
-	[0x76] = { 0, KC_V },
-	[0x62] = { 0, KC_B },
-	[0x6e] = { 0, KC_N },
-	[0x6d] = { 0, KC_M },
-
-	[0x2c] = { 0, KC_COMMA },
-	[0x2e] = { 0, KC_PERIOD },
-	[0x2f] = { 0, KC_SLASH },
-
-	[0x20] = { 0, KC_SPACE },
-
-	[0x1b] = { 0, KC_ESCAPE },
-
-	[0x0a] = { 0, KC_ENTER },
-	[0x0d] = { 0, KC_ENTER },
-
-	/* with Shift pressed */
-
-	[0x7e] = { KM_LSHIFT, KC_BACKTICK },
-
-	[0x21] = { KM_LSHIFT, KC_1 },
-	[0x40] = { KM_LSHIFT, KC_2 },
-	[0x23] = { KM_LSHIFT, KC_3 },
-	[0x24] = { KM_LSHIFT, KC_4 },
-	[0x25] = { KM_LSHIFT, KC_5 },
-	[0x5e] = { KM_LSHIFT, KC_6 },
-	[0x26] = { KM_LSHIFT, KC_7 },
-	[0x2a] = { KM_LSHIFT, KC_8 },
-	[0x28] = { KM_LSHIFT, KC_9 },
-	[0x29] = { KM_LSHIFT, KC_0 },
-
-	[0x5f] = { KM_LSHIFT, KC_MINUS },
-	[0x2b] = { KM_LSHIFT, KC_EQUALS },
-
-	[0x51] = { KM_LSHIFT, KC_Q },
-	[0x57] = { KM_LSHIFT, KC_W },
-	[0x45] = { KM_LSHIFT, KC_E },
-	[0x52] = { KM_LSHIFT, KC_R },
-	[0x54] = { KM_LSHIFT, KC_T },
-	[0x59] = { KM_LSHIFT, KC_Y },
-	[0x55] = { KM_LSHIFT, KC_U },
-	[0x49] = { KM_LSHIFT, KC_I },
-	[0x4f] = { KM_LSHIFT, KC_O },
-	[0x50] = { KM_LSHIFT, KC_P },
-
-	[0x7b] = { KM_LSHIFT, KC_LBRACKET },
-	[0x7d] = { KM_LSHIFT, KC_RBRACKET },
-
-	[0x41] = { KM_LSHIFT, KC_A },
-	[0x53] = { KM_LSHIFT, KC_S },
-	[0x44] = { KM_LSHIFT, KC_D },
-	[0x46] = { KM_LSHIFT, KC_F },
-	[0x47] = { KM_LSHIFT, KC_G },
-	[0x48] = { KM_LSHIFT, KC_H },
-	[0x4a] = { KM_LSHIFT, KC_J },
-	[0x4b] = { KM_LSHIFT, KC_K },
-	[0x4c] = { KM_LSHIFT, KC_L },
-
-	[0x3a] = { KM_LSHIFT, KC_SEMICOLON },
-	[0x22] = { KM_LSHIFT, KC_QUOTE },
-	[0x7c] = { KM_LSHIFT, KC_BACKSLASH },
-
-	[0x5a] = { KM_LSHIFT, KC_Z },
-	[0x58] = { KM_LSHIFT, KC_X },
-	[0x43] = { KM_LSHIFT, KC_C },
-	[0x56] = { KM_LSHIFT, KC_V },
-	[0x42] = { KM_LSHIFT, KC_B },
-	[0x4e] = { KM_LSHIFT, KC_N },
-	[0x4d] = { KM_LSHIFT, KC_M },
-
-	[0x3c] = { KM_LSHIFT, KC_COMMA },
-	[0x3e] = { KM_LSHIFT, KC_PERIOD },
-	[0x3f] = { KM_LSHIFT, KC_SLASH }
-};
-
-static int map_e1[][2] =
+int kbd_ctl_init(void)
 {
-};
-
-static int map_e1a[][2] =
-{
-	[0x50] = { 0, KC_F1 },
-	[0x51] = { 0, KC_F2 },
-	[0x52] = { 0, KC_F3 },
-	[0x53] = { 0, KC_F4 },
-};
-
-static int map_e1b[][2] =
-{
-	[0x33] = { 0, KC_F5 },
-	[0x37] = { 0, KC_F6 },
-	[0x38] = { 0, KC_F7 },
-	[0x39] = { 0, KC_F8 },
-};
-
-static int map_e1c[][2] =
-{
-	[0x38] = { 0, KC_F9 },
-	[0x39] = { 0, KC_F10 },
-	[0x33] = { 0, KC_F11 },
-	[0x34] = { 0, KC_F12 },
-};
-
-static unsigned int mods_keys[][2] = {
-	{ KM_LSHIFT, KC_LSHIFT },
-	{ 0, 0 }
-};
-
-static enum dec_state ds = ds_start;
+	ds = 0;
+
+	gsp_init(&sp);
+	return gsp_insert_defs(&sp, seq_defs);
+}
 
 void kbd_ctl_parse_scancode(int scancode)
 {
-	switch (ds) {
-	case ds_start:	parse_ds_start(scancode); break;
-	case ds_e: 	parse_ds_e(scancode); break;
-	case ds_e1:	parse_ds_e1(scancode); break;
-	case ds_e1a:	parse_ds_e1a(scancode); break;
-	case ds_e1b:	parse_ds_e1b(scancode); break;
-	case ds_e1c:	parse_ds_e1c(scancode); break;
-	}
-}
-
-static void parse_ds_start(int scancode)
-{
-	if (scancode == 0x1b) {
-		ds = ds_e;
-		return;
-	}
-
-	parse_leaf(scancode, map_start, sizeof(map_start) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e(int scancode)
-{
-	switch (scancode) {
-	case 0x5b: ds = ds_e1; return;
-	case 0x1b: ds = ds_start; break;
-	default: ds = ds_start; return;
-	}
-
-	kbd_push_ev(KE_PRESS, KC_ESCAPE);
-}
-
-static void parse_ds_e1(int scancode)
-{
-	switch (scancode) {
-	case 0x4f: ds = ds_e1a; return;
-	case 0x31: ds = ds_e1b; return;
-	case 0x32: ds = ds_e1c; return;
-	default: ds = ds_start; break;
-	}
-
-	parse_leaf(scancode, map_e1, sizeof(map_e1) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e1a(int scancode)
-{
-	parse_leaf(scancode, map_e1a, sizeof(map_e1a) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e1b(int scancode)
-{
-	parse_leaf(scancode, map_e1b, sizeof(map_e1b) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e1c(int scancode)
-{
-	parse_leaf(scancode, map_e1c, sizeof(map_e1c) / (2 * sizeof(int)));
-}
-
-static void parse_leaf(int scancode, int (*map)[2], size_t map_length)
-{
-	unsigned int key, mod;
-	int i;
-
-	ds = ds_start;
-
-	if (scancode < 0 || scancode >= map_length)
-		return;
-
-	mod = map[scancode][0];
-	key = map[scancode][1];
-
-	/* Simulate modifier pressing. */
-	i = 0;
-	while (mods_keys[i][0] != 0) {
-		if (mod & mods_keys[i][0]) {
-			kbd_push_ev(KE_PRESS, mods_keys[i][1]);
-		}
-		++i;
-	}
-
+	unsigned mods, key;
+
+	ds = gsp_step(&sp, ds, scancode, &mods, &key);
 	if (key != 0) {
-		kbd_push_ev(KE_PRESS, key);
-		kbd_push_ev(KE_RELEASE, key);
-	}
-
-	/* Simulate modifier releasing. */
-	i = 0;
-	while (mods_keys[i][0] != 0) {
-		if (mod & mods_keys[i][0]) {
-			kbd_push_ev(KE_RELEASE, mods_keys[i][1]);
-		}
-		++i;
+		stroke_sim(mods, key);
 	}
 }
Index: uspace/srv/kbd/ctl/pc.c
===================================================================
--- uspace/srv/kbd/ctl/pc.c	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/ctl/pc.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -40,4 +40,5 @@
 #include <kbd/keycode.h>
 #include <kbd_ctl.h>
+#include <gsp.h>
 
 enum dec_state {
@@ -46,5 +47,5 @@
 };
 
-static enum dec_state ds = ds_s;
+static enum dec_state ds;
 
 static int scanmap_simple[] = {
@@ -180,4 +181,9 @@
 };
 
+int kbd_ctl_init(void)
+{
+	ds = ds_s;
+	return 0;
+}
 
 void kbd_ctl_parse_scancode(int scancode)
Index: uspace/srv/kbd/ctl/stty.c
===================================================================
--- uspace/srv/kbd/ctl/stty.c	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/ctl/stty.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -40,288 +40,188 @@
 #include <kbd/keycode.h>
 #include <kbd_ctl.h>
-
-static void parse_ds_start(int scancode);
-static void parse_ds_e(int scancode);
-static void parse_ds_e1(int scancode);
-static void parse_ds_e2(int scancode);
-static void parse_ds_e2a(int scancode);
-static void parse_ds_e2b(int scancode);
-
-static void parse_leaf(int scancode, int (*map)[2], size_t map_length);
-
-enum dec_state {
-	ds_start,
-	ds_e,
-	ds_e1,
-	ds_e2,
-	ds_e2a,
-	ds_e2b
+#include <gsp.h>
+#include <stroke.h>
+
+/** Scancode parser */
+static gsp_t sp;
+
+/** Current parser state */
+static int ds;
+
+#include <stdio.h>
+
+int seq_defs[] = {
+	/* Not shifted */
+
+	0,	KC_BACKTICK,	0x60, GSP_END,
+
+	0,	KC_1,		0x31, GSP_END,
+	0,	KC_2,		0x32, GSP_END,
+	0,	KC_3,		0x33, GSP_END,
+	0,	KC_4,		0x34, GSP_END,
+	0,	KC_5,		0x35, GSP_END,
+	0,	KC_6,		0x36, GSP_END,
+	0,	KC_7,		0x37, GSP_END,
+	0,	KC_8,		0x38, GSP_END,
+	0,	KC_9,		0x39, GSP_END,
+	0,	KC_0,		0x30, GSP_END,
+
+	0,	KC_MINUS,	0x2d, GSP_END,
+	0,	KC_EQUALS,	0x3d, GSP_END,
+	0,	KC_BACKSPACE,	0x08, GSP_END,
+
+	0,	KC_TAB,		0x09, GSP_END,
+
+	0,	KC_Q,		0x71, GSP_END,
+	0,	KC_W,		0x77, GSP_END,
+	0,	KC_E,		0x65, GSP_END,
+	0,	KC_R,		0x72, GSP_END,
+	0,	KC_T,		0x74, GSP_END,
+	0,	KC_Y,		0x79, GSP_END,
+	0,	KC_U,		0x75, GSP_END,
+	0,	KC_I,		0x69, GSP_END,
+	0,	KC_O,		0x6f, GSP_END,
+	0,	KC_P,		0x70, GSP_END,
+
+	0,	KC_LBRACKET,	0x5b, GSP_END,
+	0,	KC_RBRACKET,	0x5d, GSP_END,
+
+	0,	KC_A,		0x61, GSP_END,
+	0,	KC_S,		0x73, GSP_END,
+	0,	KC_D,		0x64, GSP_END,
+	0,	KC_F,		0x66, GSP_END,
+	0,	KC_G,		0x67, GSP_END,
+	0,	KC_H,		0x68, GSP_END,
+	0,	KC_J,		0x6a, GSP_END,
+	0,	KC_K,		0x6b, GSP_END,
+	0,	KC_L,		0x6c, GSP_END,
+
+	0,	KC_SEMICOLON,	0x3b, GSP_END,
+	0,	KC_QUOTE,	0x27, GSP_END,
+	0,	KC_BACKSLASH,	0x5c, GSP_END,
+
+	0,	KC_Z,		0x7a, GSP_END,
+	0,	KC_X,		0x78, GSP_END,
+	0,	KC_C,		0x63, GSP_END,
+	0,	KC_V,		0x76, GSP_END,
+	0,	KC_B,		0x62, GSP_END,
+	0,	KC_N,		0x6e, GSP_END,
+	0,	KC_M,		0x6d, GSP_END,
+
+	0,	KC_COMMA,	0x2c, GSP_END,
+	0,	KC_PERIOD,	0x2e, GSP_END,
+	0,	KC_SLASH,	0x2f, GSP_END,
+
+	/* Shifted */
+
+	KM_SHIFT,	KC_BACKTICK,	0x7e, GSP_END,
+
+	KM_SHIFT,	KC_1,		0x21, GSP_END,
+	KM_SHIFT,	KC_2,		0x40, GSP_END,
+	KM_SHIFT,	KC_3,		0x23, GSP_END,
+	KM_SHIFT,	KC_4,		0x24, GSP_END,
+	KM_SHIFT,	KC_5,		0x25, GSP_END,
+	KM_SHIFT,	KC_6,		0x5e, GSP_END,
+	KM_SHIFT,	KC_7,		0x26, GSP_END,
+	KM_SHIFT,	KC_8,		0x2a, GSP_END,
+	KM_SHIFT,	KC_9,		0x28, GSP_END,
+	KM_SHIFT,	KC_0,		0x29, GSP_END,
+
+	KM_SHIFT,	KC_MINUS,	0x5f, GSP_END,
+	KM_SHIFT,	KC_EQUALS,	0x2b, GSP_END,
+
+	KM_SHIFT,	KC_Q,		0x51, GSP_END,
+	KM_SHIFT,	KC_W,		0x57, GSP_END,
+	KM_SHIFT,	KC_E,		0x45, GSP_END,
+	KM_SHIFT,	KC_R,		0x52, GSP_END,
+	KM_SHIFT,	KC_T,		0x54, GSP_END,
+	KM_SHIFT,	KC_Y,		0x59, GSP_END,
+	KM_SHIFT,	KC_U,		0x55, GSP_END,
+	KM_SHIFT,	KC_I,		0x49, GSP_END,
+	KM_SHIFT,	KC_O,		0x4f, GSP_END,
+	KM_SHIFT,	KC_P,		0x50, GSP_END,
+
+	KM_SHIFT,	KC_LBRACKET,	0x7b, GSP_END,
+	KM_SHIFT,	KC_RBRACKET,	0x7d, GSP_END,
+
+	KM_SHIFT,	KC_A,		0x41, GSP_END,
+	KM_SHIFT,	KC_S,		0x53, GSP_END,
+	KM_SHIFT,	KC_D,		0x44, GSP_END,
+	KM_SHIFT,	KC_F,		0x46, GSP_END,
+	KM_SHIFT,	KC_G,		0x47, GSP_END,
+	KM_SHIFT,	KC_H,		0x48, GSP_END,
+	KM_SHIFT,	KC_J,		0x4a, GSP_END,
+	KM_SHIFT,	KC_K,		0x4b, GSP_END,
+	KM_SHIFT,	KC_L,		0x4c, GSP_END,
+
+	KM_SHIFT,	KC_SEMICOLON,	0x3a, GSP_END,
+	KM_SHIFT,	KC_QUOTE,	0x22, GSP_END,
+	KM_SHIFT,	KC_BACKSLASH,	0x7c, GSP_END,
+
+	KM_SHIFT,	KC_Z,		0x5a, GSP_END,
+	KM_SHIFT,	KC_X,		0x58, GSP_END,
+	KM_SHIFT,	KC_C,		0x43, GSP_END,
+	KM_SHIFT,	KC_V,		0x56, GSP_END,
+	KM_SHIFT,	KC_B,		0x42, GSP_END,
+	KM_SHIFT,	KC_N,		0x4e, GSP_END,
+	KM_SHIFT,	KC_M,		0x4d, GSP_END,
+
+	KM_SHIFT,	KC_COMMA,	0x3c, GSP_END,
+	KM_SHIFT,	KC_PERIOD,	0x3e, GSP_END,
+	KM_SHIFT,	KC_SLASH,	0x3f, GSP_END,
+
+	/* ... */
+
+	0,	KC_SPACE,	0x20, GSP_END,
+	0,	KC_ENTER,	0x0a, GSP_END,
+	0,	KC_ENTER,	0x0d, GSP_END,
+
+	0,	KC_ESCAPE,	0x1b, 0x1b, GSP_END,
+
+	0,	KC_F1,		0x1b, 0x4f, 0x50, GSP_END,
+	0,	KC_F2,		0x1b, 0x4f, 0x51, GSP_END,
+	0,	KC_F3,		0x1b, 0x4f, 0x52, GSP_END,
+	0,	KC_F4,		0x1b, 0x4f, 0x53, GSP_END,
+	0,	KC_F5,		0x1b, 0x5b, 0x31, 0x35, 0x7e, GSP_END,
+	0,	KC_F6,		0x1b, 0x5b, 0x31, 0x37, 0x7e, GSP_END,
+	0,	KC_F7,		0x1b, 0x5b, 0x31, 0x38, 0x7e, GSP_END,
+	0,	KC_F8,		0x1b, 0x5b, 0x31, 0x39, 0x7e, GSP_END,
+	0,	KC_F9,		0x1b, 0x5b, 0x32, 0x30, 0x7e, GSP_END,
+	0,	KC_F10,		0x1b, 0x5b, 0x32, 0x31, 0x7e, GSP_END,
+	0,	KC_F11,		0x1b, 0x5b, 0x32, 0x33, 0x7e, GSP_END,
+	0,	KC_F12,		0x1b, 0x5b, 0x32, 0x34, 0x7e, GSP_END,
+
+	0,	KC_INSERT,	0x1b, 0x5b, 0x32, 0x7e, GSP_END,
+	0,	KC_HOME,	0x1b, 0x5b, 0x48, GSP_END,
+	0,	KC_PAGE_UP,	0x1b, 0x5b, 0x35, 0x7e, GSP_END,
+	0,	KC_DELETE,	0x1b, 0x5b, 0x33, 0x7e, GSP_END,
+	0,	KC_END,		0x1b, 0x5b, 0x46, GSP_END,
+	0,	KC_PAGE_DOWN,	0x1b, 0x5b, 0x36, 0x7e, GSP_END,
+
+	0,	KC_UP,		0x1b, 0x5b, 0x41, GSP_END,
+	0,	KC_LEFT,	0x1b, 0x5b, 0x44, GSP_END,
+	0,	KC_DOWN,	0x1b, 0x5b, 0x42, GSP_END,
+	0,	KC_RIGHT,	0x1b, 0x5b, 0x43, GSP_END,
+
+	0,	0
 };
 
-static int map_start[][2] = {
-
-	[0x60] = { 0, KC_BACKTICK },
-
-	[0x31] = { 0, KC_1 },
-	[0x32] = { 0, KC_2 },
-	[0x33] = { 0, KC_3 },
-	[0x34] = { 0, KC_4 },
-	[0x35] = { 0, KC_5 },
-	[0x36] = { 0, KC_6 },
-	[0x37] = { 0, KC_7 },
-	[0x38] = { 0, KC_8 },
-	[0x39] = { 0, KC_9 },
-	[0x30] = { 0, KC_0 },
-
-	[0x2d] = { 0, KC_MINUS },
-	[0x3d] = { 0, KC_EQUALS },
-	[0x08] = { 0, KC_BACKSPACE },
-
-	[0x0f] = { 0, KC_TAB },
-
-	[0x71] = { 0, KC_Q },
-	[0x77] = { 0, KC_W },
-	[0x65] = { 0, KC_E },
-	[0x72] = { 0, KC_R },
-	[0x74] = { 0, KC_T },
-	[0x79] = { 0, KC_Y },
-	[0x75] = { 0, KC_U },
-	[0x69] = { 0, KC_I },
-	[0x6f] = { 0, KC_O },
-	[0x70] = { 0, KC_P },
-
-	[0x5b] = { 0, KC_LBRACKET },
-	[0x5d] = { 0, KC_RBRACKET },
-
-	[0x61] = { 0, KC_A },
-	[0x73] = { 0, KC_S },
-	[0x64] = { 0, KC_D },
-	[0x66] = { 0, KC_F },
-	[0x67] = { 0, KC_G },
-	[0x68] = { 0, KC_H },
-	[0x6a] = { 0, KC_J },
-	[0x6b] = { 0, KC_K },
-	[0x6c] = { 0, KC_L },
-
-	[0x3b] = { 0, KC_SEMICOLON },
-	[0x27] = { 0, KC_QUOTE },
-	[0x5c] = { 0, KC_BACKSLASH },
-
-	[0x7a] = { 0, KC_Z },
-	[0x78] = { 0, KC_X },
-	[0x63] = { 0, KC_C },
-	[0x76] = { 0, KC_V },
-	[0x62] = { 0, KC_B },
-	[0x6e] = { 0, KC_N },
-	[0x6d] = { 0, KC_M },
-
-	[0x2c] = { 0, KC_COMMA },
-	[0x2e] = { 0, KC_PERIOD },
-	[0x2f] = { 0, KC_SLASH },
-
-	[0x20] = { 0, KC_SPACE },
-
-	[0x1b] = { 0, KC_ESCAPE },
-
-	[0x0a] = { 0, KC_ENTER },
-	[0x0d] = { 0, KC_ENTER },
-
-	/* with Shift pressed */
-
-	[0x7e] = { KM_LSHIFT, KC_BACKTICK },
-
-	[0x21] = { KM_LSHIFT, KC_1 },
-	[0x40] = { KM_LSHIFT, KC_2 },
-	[0x23] = { KM_LSHIFT, KC_3 },
-	[0x24] = { KM_LSHIFT, KC_4 },
-	[0x25] = { KM_LSHIFT, KC_5 },
-	[0x5e] = { KM_LSHIFT, KC_6 },
-	[0x26] = { KM_LSHIFT, KC_7 },
-	[0x2a] = { KM_LSHIFT, KC_8 },
-	[0x28] = { KM_LSHIFT, KC_9 },
-	[0x29] = { KM_LSHIFT, KC_0 },
-
-	[0x5f] = { KM_LSHIFT, KC_MINUS },
-	[0x2b] = { KM_LSHIFT, KC_EQUALS },
-
-	[0x51] = { KM_LSHIFT, KC_Q },
-	[0x57] = { KM_LSHIFT, KC_W },
-	[0x45] = { KM_LSHIFT, KC_E },
-	[0x52] = { KM_LSHIFT, KC_R },
-	[0x54] = { KM_LSHIFT, KC_T },
-	[0x59] = { KM_LSHIFT, KC_Y },
-	[0x55] = { KM_LSHIFT, KC_U },
-	[0x49] = { KM_LSHIFT, KC_I },
-	[0x4f] = { KM_LSHIFT, KC_O },
-	[0x50] = { KM_LSHIFT, KC_P },
-
-	[0x7b] = { KM_LSHIFT, KC_LBRACKET },
-	[0x7d] = { KM_LSHIFT, KC_RBRACKET },
-
-	[0x41] = { KM_LSHIFT, KC_A },
-	[0x53] = { KM_LSHIFT, KC_S },
-	[0x44] = { KM_LSHIFT, KC_D },
-	[0x46] = { KM_LSHIFT, KC_F },
-	[0x47] = { KM_LSHIFT, KC_G },
-	[0x48] = { KM_LSHIFT, KC_H },
-	[0x4a] = { KM_LSHIFT, KC_J },
-	[0x4b] = { KM_LSHIFT, KC_K },
-	[0x4c] = { KM_LSHIFT, KC_L },
-
-	[0x3a] = { KM_LSHIFT, KC_SEMICOLON },
-	[0x22] = { KM_LSHIFT, KC_QUOTE },
-	[0x7c] = { KM_LSHIFT, KC_BACKSLASH },
-
-	[0x5a] = { KM_LSHIFT, KC_Z },
-	[0x58] = { KM_LSHIFT, KC_X },
-	[0x43] = { KM_LSHIFT, KC_C },
-	[0x56] = { KM_LSHIFT, KC_V },
-	[0x42] = { KM_LSHIFT, KC_B },
-	[0x4e] = { KM_LSHIFT, KC_N },
-	[0x4d] = { KM_LSHIFT, KC_M },
-
-	[0x3c] = { KM_LSHIFT, KC_COMMA },
-	[0x3e] = { KM_LSHIFT, KC_PERIOD },
-	[0x3f] = { KM_LSHIFT, KC_SLASH }
-};
-
-static int map_e1[][2] =
+int kbd_ctl_init(void)
 {
-	[0x50] = { 0, KC_F1 },
-	[0x51] = { 0, KC_F2 },
-	[0x52] = { 0, KC_F3 },
-	[0x53] = { 0, KC_F4 },
-};
-
-static int map_e2[][2] =
-{
-	[0x41] = { 0, KC_UP },
-	[0x42] = { 0, KC_DOWN },
-	[0x44] = { 0, KC_LEFT },
-	[0x43] = { 0, KC_RIGHT },
-};
-
-static int map_e2a[][2] =
-{
-	[0x35] = { 0, KC_F5 },
-	[0x37] = { 0, KC_F6 },
-	[0x38] = { 0, KC_F7 },
-	[0x39] = { 0, KC_F8 },
-};
-
-static int map_e2b[][2] =
-{
-	[0x30] = { 0, KC_F9 },
-	[0x31] = { 0, KC_F10 },
-	[0x32] = { 0, KC_F11 },
-	[0x33] = { 0, KC_F12 },
-};
-
-static unsigned int mods_keys[][2] = {
-	{ KM_LSHIFT, KC_LSHIFT },
-	{ 0, 0 }
-};
-
-static enum dec_state ds;
+	ds = 0;
+
+	gsp_init(&sp);
+	return gsp_insert_defs(&sp, seq_defs);
+}
 
 void kbd_ctl_parse_scancode(int scancode)
 {
-	switch (ds) {
-	case ds_start:	parse_ds_start(scancode); break;
-	case ds_e: 	parse_ds_e(scancode); break;
-	case ds_e1:	parse_ds_e1(scancode); break;
-	case ds_e2:	parse_ds_e2(scancode); break;
-	case ds_e2a:	parse_ds_e2a(scancode); break;
-	case ds_e2b:	parse_ds_e2b(scancode); break;
+	unsigned mods, key;
+
+	ds = gsp_step(&sp, ds, scancode, &mods, &key);
+	if (key != 0) {
+		stroke_sim(mods, key);
 	}
 }
-
-static void parse_ds_start(int scancode)
-{
-	if (scancode == 0x1b) {
-		ds = ds_e;
-		return;
-	}
-
-	parse_leaf(scancode, map_start, sizeof(map_start) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e(int scancode)
-{
-	if (scancode < 0 || scancode >= 0x80) return;
-
-	switch (scancode) {
-	case 0x4f: ds = ds_e1; return;
-	case 0x5b: ds = ds_e2; return;
-	case 0x1b: ds = ds_start; break;
-	default: ds = ds_start; return;
-	}
-
-	kbd_push_ev(KE_PRESS, KC_ESCAPE);
-}
-
-static void parse_ds_e1(int scancode)
-{
-	parse_leaf(scancode, map_e1, sizeof(map_e1) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e2(int scancode)
-{
-	switch (scancode) {
-	case 0x31: ds = ds_e2a; break;
-	case 0x32: ds = ds_e2b; break;
-	default: ds = ds_start; break;
-	}
-
-	parse_leaf(scancode, map_e2, sizeof(map_e2) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e2a(int scancode)
-{
-	parse_leaf(scancode, map_e2a, sizeof(map_e2a) / (2 * sizeof(int)));
-}
-
-static void parse_ds_e2b(int scancode)
-{
-	parse_leaf(scancode, map_e2b, sizeof(map_e2b) / (2 * sizeof(int)));
-}
-
-static void parse_leaf(int scancode, int (*map)[2], size_t map_length)
-{
-	unsigned int key, mod;
-	int i;
-
-	ds = ds_start;
-
-	if (scancode < 0 || scancode >= map_length)
-		return;
-
-	mod = map[scancode][0];
-	key = map[scancode][1];
-
-	/* Simulate modifier pressing. */
-	i = 0;
-	while (mods_keys[i][0] != 0) {
-		if (mod & mods_keys[i][0]) {
-			kbd_push_ev(KE_PRESS, mods_keys[i][1]);
-		}
-		++i;
-	}
-
-	if (key != 0) {
-		kbd_push_ev(KE_PRESS, key);
-		kbd_push_ev(KE_RELEASE, key);
-	}
-
-	/* Simulate modifier releasing. */
-	i = 0;
-	while (mods_keys[i][0] != 0) {
-		if (mod & mods_keys[i][0]) {
-			kbd_push_ev(KE_RELEASE, mods_keys[i][1]);
-		}
-		++i;
-	}
-}
-
 
 /**
Index: uspace/srv/kbd/ctl/sun.c
===================================================================
--- uspace/srv/kbd/ctl/sun.c	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/ctl/sun.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -45,4 +45,9 @@
 
 static int scanmap_simple[];
+
+int kbd_ctl_init(void)
+{
+	return 0;
+}
 
 void kbd_ctl_parse_scancode(int scancode)
Index: uspace/srv/kbd/genarch/gsp.c
===================================================================
--- uspace/srv/kbd/genarch/gsp.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
+++ uspace/srv/kbd/genarch/gsp.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup kbdgen generic
+ * @ingroup  kbd
+ * @{
+ */ 
+/** @file
+ * @brief	Generic scancode parser.
+ *
+ * The scancode parser is a simple finite state machine. It is described
+ * using sequences of input symbols (scancodes) and the corresponding output
+ * value (mods, key pair). When the parser recognizes a sequence,
+ * it outputs the value and restarts. If a transition is undefined,
+ * the parser restarts, too.
+ *
+ * Apart from precise values, GSP_DEFAULT allows to catch general cases.
+ * I.e. if we knew that after 0x1b 0x4f there always follow two more
+ * scancodes, we can define (0x1b, 0x4f, GSP_DEFAULT, GSP_DEFAULT, GSP_END)
+ * with null output. This will force the parser to read the entire sequence,
+ * not leaving garbage on the input if it does not recognize the specific
+ * sequence.
+ */
+
+#include <gsp.h>
+#include <libadt/hash_table.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define TRANS_TABLE_CHAINS 256
+
+/*
+ * Hash table operations for the transition function.
+ */
+
+static hash_index_t trans_op_hash(unsigned long key[]);
+static int trans_op_compare(unsigned long key[], hash_count_t keys,
+    link_t *item);
+static void trans_op_remove_callback(link_t *item);
+
+static hash_table_operations_t trans_ops = {
+	.hash = trans_op_hash,
+	.compare = trans_op_compare,
+	.remove_callback = trans_op_remove_callback
+};
+
+static gsp_trans_t *trans_lookup(gsp_t *p, int state, int input);
+static void trans_insert(gsp_t *p, gsp_trans_t *t);
+static gsp_trans_t *trans_new(void);
+
+/** Initialise scancode parser. */
+void gsp_init(gsp_t *p)
+{
+	p->states = 1;
+	hash_table_create(&p->trans, TRANS_TABLE_CHAINS, 2, &trans_ops);
+}
+
+/** Insert a series of definitions into the parser.
+ *
+ * @param p	The parser.
+ * @param defs	Definition list. Each definition starts with two output values
+ *		(mods, key) and continues with a sequence of input values
+ *		terminated with GSP_END. The definition list is terminated
+ *		with two zeroes (0, 0) for output values.
+ */
+int gsp_insert_defs(gsp_t *p, const int *defs)
+{
+	unsigned mods, key;
+	const int *dp;
+	int rc;
+
+	dp = defs;
+
+	while (1) {
+		/* Read the output values. */
+		mods = *dp++;
+		key = *dp++;
+		if (key == 0) break;
+
+		/* Insert one sequence. */		
+		rc = gsp_insert_seq(p, dp, mods, key);
+		if (rc != 0)
+			return rc;
+
+		/* Skip to the next definition. */
+		while (*dp != GSP_END)
+			++dp;
+		++dp;
+	}
+
+	return 0;
+}
+
+/** Insert one sequence into the parser.
+ *
+ * @param p	The parser.
+ * @param seq	Sequence of input values terminated with GSP_END.
+ * @param mods	Corresponsing output value.
+ * @param key	Corresponsing output value.
+ */
+int gsp_insert_seq(gsp_t *p, const int *seq, unsigned mods, unsigned key)
+{
+	int state;
+	gsp_trans_t *t;
+
+	state = 0;
+	t = NULL;
+
+	/* Input sequence must be non-empty. */
+	if (*seq == GSP_END)
+		return -1;
+
+	while (*(seq + 1) != GSP_END) {
+		t = trans_lookup(p, state, *seq);
+		if (t == NULL) {
+			/* Create new state. */
+			t = trans_new();
+			t->old_state = state;
+			t->input = *seq;
+			t->new_state = p->states++;
+
+			t->out_mods = 0;
+			t->out_key = 0;
+
+			trans_insert(p, t);
+		}
+		state = t->new_state;
+		++seq;
+	}
+
+	/* Process the last transition. */
+	t = trans_lookup(p, state, *seq);
+	if (t != NULL) {
+		exit(1);
+		return -1;	/* Conflicting definition. */
+	}
+
+	t = trans_new();
+	t->old_state = state;
+	t->input = *seq;
+	t->new_state = 0;
+
+	t->out_mods = mods;
+	t->out_key = key;
+
+	trans_insert(p, t);
+
+	return 0;
+}
+
+/** Compute one parser step.
+ *
+ * Computes the next state and output values for a given state and input.
+ * This handles everything including restarts and default branches.
+ *
+ * @param p		The parser.
+ * @param state		Old state.
+ * @param input		Input symbol (scancode).
+ * @param mods		Output value (modifier).
+ * @param key		Output value (key).
+ * @return		New state.
+ */
+int gsp_step(gsp_t *p, int state, int input, unsigned *mods, unsigned *key)
+{
+	gsp_trans_t *t;
+
+	t = trans_lookup(p, state, input);
+	if (t == NULL) {
+		t = trans_lookup(p, state, GSP_DEFAULT);
+	}
+
+	if (t == NULL) {
+		printf("gsp_step: not found\n");
+		*mods = NULL;
+		*key = NULL;
+		return 0;
+	}
+
+	*mods = t->out_mods;
+	*key = t->out_key;
+	return t->new_state;
+}
+
+/** Transition function lookup.
+ *
+ * Returns the value of the transition function for the given state
+ * and input. Note that the transition must be specified precisely,
+ * to obtain the default branch use input = GSP_DEFAULT.
+ *
+ * @param p		Parser.
+ * @param state		Current state.
+ * @param input		Input value.
+ * @return		The transition or @c NULL if not defined.
+ */
+static gsp_trans_t *trans_lookup(gsp_t *p, int state, int input)
+{
+	link_t *item;
+	unsigned long key[2];
+
+	key[0] = state;
+	key[1] = input;
+
+	item = hash_table_find(&p->trans, key);
+	if (item == NULL) return NULL;
+
+	return hash_table_get_instance(item, gsp_trans_t, link);
+}
+
+/** Define a new transition.
+ *
+ * @param p	The parser.
+ * @param t	Transition with all fields defined.
+ */
+static void trans_insert(gsp_t *p, gsp_trans_t *t)
+{
+	unsigned long key[2];
+
+	key[0] = t->old_state;
+	key[1] = t->input;
+
+	hash_table_insert(&p->trans, &key, &t->link);
+}
+
+/** Allocate transition structure. */
+static gsp_trans_t *trans_new(void)
+{
+	gsp_trans_t *t;
+
+	t = malloc(sizeof(gsp_trans_t));
+	if (t == NULL) {
+		printf("Memory allocation failed.\n");
+		exit(1);
+	}
+
+	return t;
+}
+
+/*
+ * Transition function hash table operations.
+ */
+
+static hash_index_t trans_op_hash(unsigned long key[])
+{
+	return (key[0] * 17 + key[1]) % TRANS_TABLE_CHAINS;
+}
+
+static int trans_op_compare(unsigned long key[], hash_count_t keys,
+    link_t *item)
+{
+	gsp_trans_t *t;
+
+	t = hash_table_get_instance(item, gsp_trans_t, link);
+	return (key[0] == t->old_state && key[1] == t->input);
+}
+
+static void trans_op_remove_callback(link_t *item)
+{
+}
+
+/**
+ * @}
+ */ 
Index: uspace/srv/kbd/genarch/stroke.c
===================================================================
--- uspace/srv/kbd/genarch/stroke.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
+++ uspace/srv/kbd/genarch/stroke.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup kbd
+ * @{
+ */
+/**
+ * @file
+ * @brief	Stroke simulator.
+ *
+ * When simulating a keyboard using a serial TTY we need to convert the
+ * recognized strokes (such as Shift-A) to sequences of key presses and
+ * releases (such as 'press Shift, press A, release A, release Shift').
+ */
+
+#include <stroke.h>
+#include <kbd.h>
+#include <kbd/kbd.h>
+#include <kbd/keycode.h>
+
+/** Correspondence between modifers and the modifier keycodes. */
+static unsigned int mods_keys[][2] = {
+	{ KM_LSHIFT, KC_LSHIFT },
+	{ 0, 0 }
+};
+
+/** Simulate keystroke using sequences of key presses and releases. */
+void stroke_sim(unsigned mod, unsigned key)
+{
+	int i;
+
+	/* Simulate modifier presses. */
+	i = 0;
+	while (mods_keys[i][0] != 0) {
+		if (mod & mods_keys[i][0]) {
+			kbd_push_ev(KE_PRESS, mods_keys[i][1]);
+		}
+		++i;
+	}
+
+	/* Simulate key press and release. */
+	if (key != 0) {
+		kbd_push_ev(KE_PRESS, key);
+		kbd_push_ev(KE_RELEASE, key);
+	}
+
+	/* Simulate modifier releases. */
+	i = 0;
+	while (mods_keys[i][0] != 0) {
+		if (mod & mods_keys[i][0]) {
+			kbd_push_ev(KE_RELEASE, mods_keys[i][1]);
+		}
+		++i;
+	}
+}
+
+/**
+ * @}
+ */ 
Index: uspace/srv/kbd/generic/kbd.c
===================================================================
--- uspace/srv/kbd/generic/kbd.c	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/generic/kbd.c	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -190,5 +190,9 @@
 	
 	/* Initialize port driver. */
-	if (kbd_port_init())
+	if (kbd_port_init() != 0)
+		return -1;
+
+	/* Initialize controller driver. */
+	if (kbd_ctl_init() != 0)
 		return -1;
 	
Index: uspace/srv/kbd/include/gsp.h
===================================================================
--- uspace/srv/kbd/include/gsp.h	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
+++ uspace/srv/kbd/include/gsp.h	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup kbdgen generic
+ * @brief	Generic scancode parser.
+ * @ingroup  kbd
+ * @{
+ */ 
+/** @file
+ */
+
+#ifndef KBD_GSP_H_
+#define KBD_GSP_H_
+
+#include <libadt/hash_table.h>
+
+enum {
+	GSP_END		= -1,
+	GSP_DEFAULT	= -2
+};
+
+typedef struct {
+	/** Transition table, (state, input) -> (state, output). */
+	hash_table_t trans;
+
+	/** Current number of states. */
+	int states;
+} gsp_t;
+
+typedef struct {
+	link_t link;
+
+	int old_state;
+	int input;
+
+	int new_state;
+
+	unsigned out_mods;
+	unsigned out_key;
+} gsp_trans_t;
+
+extern void gsp_init(gsp_t *);
+extern int gsp_insert_defs(gsp_t *, const int *);
+extern int gsp_insert_seq(gsp_t *, const int *, unsigned, unsigned);
+extern int gsp_step(gsp_t *, int, int, unsigned *, unsigned *);
+
+
+#endif
+
+/**
+ * @}
+ */ 
+
Index: uspace/srv/kbd/include/kbd_ctl.h
===================================================================
--- uspace/srv/kbd/include/kbd_ctl.h	(revision c247262e870426717fb30c3998c3a9eeed2d71b1)
+++ uspace/srv/kbd/include/kbd_ctl.h	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -39,4 +39,6 @@
 
 extern void kbd_ctl_parse_scancode(int);
+extern int kbd_ctl_init(void);
+
 
 #endif
Index: uspace/srv/kbd/include/stroke.h
===================================================================
--- uspace/srv/kbd/include/stroke.h	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
+++ uspace/srv/kbd/include/stroke.h	(revision db4ce845185d0afbdb84c938b2e579a7b5fb8bf2)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup kbdgen generic
+ * @brief	Generic scancode parser.
+ * @ingroup  kbd
+ * @{
+ */ 
+/** @file
+ */
+
+#ifndef KBD_STROKE_H_
+#define KBD_STROKE_H_
+
+extern void stroke_sim(unsigned, unsigned);
+
+#endif
+
+/**
+ * @}
+ */ 
+
