Index: emu/all.h
===================================================================
--- emu/all.h	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
+++ emu/all.h	(revision f51359cad4cdb056e52aae47198e502a43439d3a)
@@ -66,4 +66,7 @@
 
 extern SDL_atomic_t run;
+
+extern uint32_t vid_win;
+extern uint32_t ser_win;
 
 extern void sdl_init(void);
@@ -160,2 +163,4 @@
 extern uint32_t kbd_read(uint32_t off, int32_t sz);
 extern void kbd_write(uint32_t off, int32_t sz, uint32_t val);
+
+extern void kbd_key(SDL_KeyboardEvent *ev, bool dn);
Index: emu/cpu.c
===================================================================
--- emu/cpu.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
+++ emu/cpu.c	(revision f51359cad4cdb056e52aae47198e502a43439d3a)
@@ -82,5 +82,5 @@
 	{ 0x3b4001, 0x3b8001, 0, snd_init, snd_quit, snd_exec, snd_read, snd_write },
 	{ 0x3b8001, 0x3bc001, 0, led_init, led_quit, led_exec, led_read, led_write },
-	{ 0x3bc001, 0x3c0001, 0, kbd_init, kbd_quit, kbd_exec, kbd_read, kbd_write }
+	{ 0x3bc001, 0x3c0001, 3, kbd_init, kbd_quit, kbd_exec, kbd_read, kbd_write }
 };
 
Index: emu/kbd.c
===================================================================
--- emu/kbd.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
+++ emu/kbd.c	(revision f51359cad4cdb056e52aae47198e502a43439d3a)
@@ -24,4 +24,153 @@
 int32_t kbd_verbose = 0;
 
+#define BUF_SZ 16
+
+static int32_t buf_hd = 0;
+static int32_t buf_tl = 0;
+static uint8_t buf[BUF_SZ];
+
+static uint8_t reg = 0;
+static bool irq = false;
+
+static void xmit(void)
+{
+	ver2("kbd xmit %d %d", buf_tl, buf_hd);
+
+	if (buf_tl >= buf_hd) {
+		return;
+	}
+
+	reg = buf[buf_tl % BUF_SZ];
+	irq = true;
+	ver2("kbd xmit 0x%02x", reg);
+
+	++buf_tl;
+
+	if (buf_tl >= BUF_SZ) {
+		buf_hd -= BUF_SZ;
+		buf_tl -= BUF_SZ;
+		ver2("kbd adj %d %d", buf_tl, buf_hd);
+	}
+}
+
+static void out(uint8_t c)
+{
+	ver2("kbd out %d %d 0x%02x", buf_tl, buf_hd, c);
+
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	if (buf_hd >= buf_tl + BUF_SZ) {
+		err("keyboard port losing data");
+	}
+	else {
+		buf[buf_hd % BUF_SZ] = c;
+		++buf_hd;
+
+		if (!irq) {
+			xmit();
+		}
+	}
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+}
+
+static void but_on(int32_t sig)
+{
+	out((uint8_t)(0x80 | sig));
+	out(0x01);
+}
+
+static void but_off(int32_t sig)
+{
+	out((uint8_t)(0x80 | sig));
+	out(0x00);
+}
+
+static void key_touch(int32_t sig, int32_t val)
+{
+	out((uint8_t)(0x80 | sig));
+	out(0x01);
+	out((uint8_t)val);
+}
+
+static void key_off(int32_t sig)
+{
+	out((uint8_t)(0x80 | sig));
+	out(0x00);
+}
+
+#if defined NOT_YET
+static void pot_128(int32_t sig, int32_t val)
+{
+	out((uint8_t)(0x80 | sig));
+	out((uint8_t)val);
+}
+
+static void pot_256(int32_t sig, int32_t val)
+{
+	out((uint8_t)(0x80 | sig));
+	out((uint8_t)((val >> 7) & 0x01));
+	out((uint8_t)(val & 0x7f));
+}
+#endif
+
+void kbd_key(SDL_KeyboardEvent *ev, bool dn)
+{
+	if ((ev->keysym.mod & KMOD_SHIFT) != 0 &&
+			ev->keysym.sym >= SDLK_a && ev->keysym.sym <= SDLK_x) {
+		int32_t i = ev->keysym.sym - SDLK_a;
+		ver2("kbd key %d %s", i, dn ? "dn" : "up");
+
+		if (dn) {
+			key_touch(1 + i, 0x7f);
+		}
+		else {
+			key_off(1 + i);
+		}
+
+		return;
+	}
+
+	int32_t sig;
+
+	if (ev->keysym.sym >= SDLK_0 && ev->keysym.sym <= '9') {
+		int32_t i = ev->keysym.sym - SDLK_0;
+		ver2("kbd dat %d %s", i, dn ? "dn" : "up");
+		sig = 60 + i;
+	}
+	else {
+		switch (ev->keysym.sym) {
+		case SDLK_x:
+			ver2("kbd x %s", dn ? "dn" : "up");
+			sig = 70;
+			break;
+
+		case SDLK_e:
+			ver2("kbd e %s", dn ? "dn" : "up");
+			sig = 71;
+			break;
+
+		case SDLK_m:
+			ver2("kbd m %s", dn ? "dn" : "up");
+			sig = 72;
+			break;
+
+		default:
+			return;
+		}
+	}
+
+	if (dn) {
+		but_on(sig);
+	}
+	else {
+		but_off(sig);
+	}
+}
+
 void kbd_init(void)
 {
@@ -37,5 +186,5 @@
 {
 	ver3("kbd exec");
-	return false;
+	return irq;
 }
 
@@ -43,5 +192,15 @@
 {
 	ver2("kbd rd %u:%d", off, sz * 8);
-	return 0;
+
+	if (sz != 1 || off > 0) {
+		fail("invalid kbd rd %u:%d", off, sz * 8);
+	}
+
+	irq = false;
+	uint32_t res = reg;
+
+	xmit();
+
+	return res;
 }
 
@@ -49,3 +208,4 @@
 {
 	ver2("kbd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val);
-}
+	fail("invalid kbd wr %u:%d", off, sz * 8);
+}
Index: emu/sdl.c
===================================================================
--- emu/sdl.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
+++ emu/sdl.c	(revision f51359cad4cdb056e52aae47198e502a43439d3a)
@@ -67,4 +67,5 @@
 
 	bool rel_mod = false;
+	uint32_t win = 0;
 
 	while (SDL_AtomicGet(&run) != 0) {
@@ -101,4 +102,11 @@
 				SDL_AtomicSet(&run, 0);
 				continue;
+			}
+
+			if (ev.type == SDL_WINDOWEVENT) {
+				if (ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
+					ver("sdl ev win %u", ev.window.windowID);
+					win = ev.window.windowID;
+				}
 			}
 
@@ -138,5 +146,9 @@
 			if (ev.type == SDL_TEXTINPUT) {
 				ver("sdl ev text input %d", ev.text.text[0]);
-				ser_text(&ev.text);
+
+				if (win == ser_win) {
+					ser_text(&ev.text);
+				}
+
 				continue;
 			}
@@ -144,5 +156,22 @@
 			if (ev.type == SDL_KEYDOWN) {
 				ver("sdl ev key down %d", (int32_t)ev.key.keysym.sym);
-				ser_key(&ev.key);
+
+				if (win == ser_win) {
+					ser_key(&ev.key);
+				}
+				else if (win == vid_win) {
+					kbd_key(&ev.key, true);
+				}
+
+				continue;
+			}
+
+			if (ev.type == SDL_KEYUP) {
+				ver("sdl ev key up %d", (int32_t)ev.key.keysym.sym);
+
+				if (win == vid_win) {
+					kbd_key(&ev.key, false);
+				}
+
 				continue;
 			}
Index: emu/ser.c
===================================================================
--- emu/ser.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
+++ emu/ser.c	(revision f51359cad4cdb056e52aae47198e502a43439d3a)
@@ -63,4 +63,6 @@
 
 static SDL_Window *win;
+uint32_t ser_win;
+
 static SDL_Renderer *ren;
 static SDL_atomic_t frame;
@@ -477,4 +479,10 @@
 	}
 
+	ser_win = SDL_GetWindowID(win);
+
+	if (ser_win == 0) {
+		fail("SDL_GetWindowID() failed: %s", SDL_GetError());
+	}
+
 	ren = SDL_CreateRenderer(win, -1, 0);
 
Index: emu/vid.c
===================================================================
--- emu/vid.c	(revision 5475ecffc7e57c236384c2d970e1fea0538c9460)
+++ emu/vid.c	(revision f51359cad4cdb056e52aae47198e502a43439d3a)
@@ -50,4 +50,5 @@
 #define OD0_CB 0x0800
 
+#define AT_UND 0x0100
 #define AT_CG 0x8000
 
@@ -80,4 +81,6 @@
 
 static SDL_Window *win;
+uint32_t vid_win;
+
 static SDL_Renderer *ren;
 static SDL_Texture *tex;
@@ -123,6 +126,14 @@
 				int32_t fg = (at & 0x00f0) >> 4;
 
-				uint16_t *cg = (at & AT_CG) != 0 ? cg0 : cg1;
-				int32_t bits = cg[256 * line + ch];
+				int32_t bits;
+
+				if ((at & AT_UND) != 0 && line == 1) {
+					bits = 0xff;
+				}
+				else {
+					uint16_t *cg = (at & AT_CG) != 0 ? cg0 : cg1;
+					bits = cg[256 * line + ch];
+				}
+
 				int32_t mask = 0x01;
 
@@ -225,4 +236,10 @@
 	if (win == NULL) {
 		fail("SDL_CreateWindow() failed: %s", SDL_GetError());
+	}
+
+	vid_win = SDL_GetWindowID(win);
+
+	if (vid_win == 0) {
+		fail("SDL_GetWindowID() failed: %s", SDL_GetError());
 	}
 
