Index: emu/all.h
===================================================================
--- emu/all.h	(revision 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/all.h	(revision 4ed9bfed715c4db1a0f6cf2779711f3c0ab33613)
@@ -66,4 +66,7 @@
 
 extern SDL_atomic_t run;
+
+extern uint32_t vid_win;
+extern uint32_t ser_win;
 
 extern void sdl_init(void);
@@ -158,2 +161,5 @@
 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_text(SDL_TextInputEvent *ev);
+extern void kbd_key(SDL_KeyboardEvent *ev);
Index: emu/cpu.c
===================================================================
--- emu/cpu.c	(revision 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/cpu.c	(revision 4ed9bfed715c4db1a0f6cf2779711f3c0ab33613)
@@ -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 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/kbd.c	(revision 4ed9bfed715c4db1a0f6cf2779711f3c0ab33613)
@@ -24,4 +24,133 @@
 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);
+}
+
+#if defined NOT_YET
+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);
+}
+
+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)
+{
+	(void)ev;
+}
+
+void kbd_text(SDL_TextInputEvent *ev)
+{
+	for (int32_t i = 0; ev->text[i] != 0; ++i) {
+		if (ev->text[i] >= '0' && ev->text[i] <= '9') {
+			int32_t sig = 60 + ev->text[i] - '0';
+			but_on(sig);
+			but_off(sig);
+			continue;
+		}
+
+		switch (ev->text[i]) {
+		case 'x':
+			but_on(70);
+			but_off(70);
+			break;
+
+		case 'e':
+			but_on(71);
+			but_off(71);
+			break;
+
+		case 'm':
+			but_on(72);
+			but_off(72);
+			break;
+		}
+	}
+}
+
 void kbd_init(void)
 {
@@ -37,5 +166,5 @@
 {
 	ver3("kbd exec");
-	return false;
+	return irq;
 }
 
@@ -43,5 +172,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 +188,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 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/sdl.c	(revision 4ed9bfed715c4db1a0f6cf2779711f3c0ab33613)
@@ -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,12 @@
 			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);
+				}
+				else if (win == vid_win) {
+					kbd_text(&ev.text);
+				}
+
 				continue;
 			}
@@ -144,5 +159,12 @@
 			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);
+				}
+
 				continue;
 			}
Index: emu/ser.c
===================================================================
--- emu/ser.c	(revision 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/ser.c	(revision 4ed9bfed715c4db1a0f6cf2779711f3c0ab33613)
@@ -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 4f967e8eb19a1addd39ec92272705cffc64c374d)
+++ emu/vid.c	(revision 4ed9bfed715c4db1a0f6cf2779711f3c0ab33613)
@@ -80,4 +80,6 @@
 
 static SDL_Window *win;
+uint32_t vid_win;
+
 static SDL_Renderer *ren;
 static SDL_Texture *tex;
@@ -225,4 +227,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());
 	}
 
