Index: emu/all.h
===================================================================
--- emu/all.h	(revision a9861f36c86186b07a88f8f7e9bc65a55f90303a)
+++ emu/all.h	(revision 657abdf2383c581e5829ac55cf8ebc768bdff0fe)
@@ -121,4 +121,9 @@
 extern void ser_key(SDL_KeyboardEvent *ev);
 
+extern void ser_mou_res(void);
+extern void ser_mou_mov(SDL_MouseMotionEvent *ev);
+extern void ser_mou_dn(SDL_MouseButtonEvent *ev);
+extern void ser_mou_up(SDL_MouseButtonEvent *ev);
+
 extern void mid_init(void);
 extern void mid_quit(void);
Index: emu/sdl.c
===================================================================
--- emu/sdl.c	(revision a9861f36c86186b07a88f8f7e9bc65a55f90303a)
+++ emu/sdl.c	(revision 657abdf2383c581e5829ac55cf8ebc768bdff0fe)
@@ -106,4 +106,5 @@
 				ver("sdl ev down-arrow");
 				rel_mod = true;
+				ser_mou_res();
 				continue;
 			}
@@ -113,4 +114,24 @@
 				rel_mod = false;
 				continue;
+			}
+
+			if (rel_mod) {
+				if (ev.type == SDL_MOUSEMOTION) {
+					ver("sdl ev mousemotion (%d, %d)", ev.motion.xrel, ev.motion.yrel);
+					ser_mou_mov(&ev.motion);
+					continue;
+				}
+
+				if (ev.type == SDL_MOUSEBUTTONDOWN) {
+					ver("sdl ev mousebuttondown %d", ev.button.button);
+					ser_mou_dn(&ev.button);
+					continue;
+				}
+
+				if (ev.type == SDL_MOUSEBUTTONUP) {
+					ver("sdl ev mousebuttonup %d", ev.button.button);
+					ser_mou_up(&ev.button);
+					continue;
+				}
 			}
 
Index: emu/ser.c
===================================================================
--- emu/ser.c	(revision a9861f36c86186b07a88f8f7e9bc65a55f90303a)
+++ emu/ser.c	(revision 657abdf2383c581e5829ac55cf8ebc768bdff0fe)
@@ -28,4 +28,5 @@
 
 #define BEL_CYC 10000
+#define MOU_CYC 10000
 
 #define CON_W 80
@@ -37,4 +38,6 @@
 #define CON_FGR ((SDL_Color){ .r = 255, .b = 255, .g = 255, .a = 255 })
 
+#define BUF_SZ 16
+
 #define REG_IER_ISR 0
 #define REG_CFR_SR  1
@@ -43,4 +46,7 @@
 
 typedef struct {
+	int32_t buf_hd;
+	int32_t buf_tl;
+	uint8_t buf[BUF_SZ];
 	bool irq_r;
 	bool irq_t;
@@ -50,6 +56,6 @@
 
 static state_t state[] = {
-	{ .irq_r = false, .irq_t = false, .rdr_ok = false, .rdr = 0x00 },
-	{ .irq_r = false, .irq_t = false, .rdr_ok = false, .rdr = 0x00 }
+	{ .buf_hd = 0, .buf_tl = 0, .irq_r = false, .irq_t = false, .rdr_ok = false, .rdr = 0x00 },
+	{ .buf_hd = 0, .buf_tl = 0, .irq_r = false, .irq_t = false, .rdr_ok = false, .rdr = 0x00 }
 };
 
@@ -68,4 +74,8 @@
 static int32_t cur_x = 0, cur_y = 0;
 static int32_t bel = 0;
+
+static int32_t mou;
+static int32_t mou_dx, mou_dy;
+static bool mou_l, mou_r;
 
 static void scroll(void)
@@ -151,4 +161,47 @@
 }
 
+static void xmit(int32_t un)
+{
+	int32_t i = state[un].buf_tl;
+	ver2("ser xmit %d %d", i, state[un].buf_hd);
+
+	if (i >= state[un].buf_hd) {
+		return;
+	}
+
+	uint8_t byte = state[un].buf[i % BUF_SZ];
+	ver2("ser xmit 0x%02x", byte);
+
+	state[un].rdr = byte;
+	state[un].rdr_ok = true;
+	state[un].irq_r = true;
+
+	state[un].buf_tl = i + 1;
+
+	if (state[un].buf_tl >= BUF_SZ) {
+		state[un].buf_hd -= BUF_SZ;
+		state[un].buf_tl -= BUF_SZ;
+		ver2("ser adj %d %d", state[un].buf_tl, state[un].buf_hd);
+	}
+}
+
+static void out_lk(int32_t un, uint8_t c)
+{
+	int32_t i = state[un].buf_hd;
+	ver2("ser out %d %d 0x%02x", state[un].buf_tl, i, c);
+
+	if (i >= state[un].buf_tl + BUF_SZ) {
+		err("serial port %d losing data", un);
+		return;
+	}
+
+	state[un].buf[i % BUF_SZ] = c;
+	state[un].buf_hd = i + 1;
+
+	if (!state[un].irq_r && !state[un].rdr_ok) {
+		xmit(un);
+	}
+}
+
 static void out(int32_t un, uint8_t c)
 {
@@ -157,11 +210,19 @@
 	}
 
-	state[un].rdr = c;
-	state[un].rdr_ok = true;
-	state[un].irq_r = true;
+	out_lk(un, c);
 
 	if (SDL_UnlockMutex(cpu_mutex) < 0) {
 		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
 	}
+}
+
+static void mouse(uint8_t c)
+{
+	if (c == 't') {
+		ver2("ser mou init");
+	}
+
+	out_lk(0, 'V');
+	out_lk(0, 'O');
 }
 
@@ -266,4 +327,142 @@
 }
 
+static void mou_ev(void)
+{
+	ver2("ser mou ev (%d, %d) %c %c",
+		mou_dx, mou_dy, mou_l ? 'l' : '-', mou_r ? 'r' : '-');
+
+	int32_t dx = mou_dx;
+	int32_t dy = mou_dy;
+
+	if (dx < -128) {
+		dx = -128;
+	}
+	else if (dx > 127) {
+		dx = 127;
+	}
+
+	if (dy < -128) {
+		dy = -128;
+	}
+	else if (dy > 127) {
+		dy = 127;
+	}
+
+	dx = dx & 0xff;
+	dy = dy & 0xff;
+
+	int32_t b1 = 0x40;
+
+	if (mou_l) {
+		b1 |= 0x20;
+	}
+
+	if (mou_r) {
+		b1 |= 0x10;
+	}
+
+	b1 |= (dy & 0xc0) >> 4;
+	b1 |= (dx & 0xc0) >> 6;
+
+	int32_t b2 = dx & 0x3f;
+	int32_t b3 = dy & 0x3f;
+
+	out_lk(0, (uint8_t)b1);
+	out_lk(0, (uint8_t)b2);
+	out_lk(0, (uint8_t)b3);
+
+	mou_dx = mou_dy = 0;
+}
+
+static void mou_ev_chk(void)
+{
+	if (--mou > 0) {
+		return;
+	}
+
+	mou = MOU_CYC;
+
+	if (mou_dx == 0 && mou_dy == 0) {
+		return;
+	}
+
+	mou_ev();
+}
+
+void ser_mou_res(void)
+{
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	mou = MOU_CYC;
+	mou_dx = mou_dy = 0;
+	mou_l = mou_r = false;
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+}
+
+void ser_mou_mov(SDL_MouseMotionEvent *ev)
+{
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	mou_dx += ev->xrel;
+	mou_dy += ev->yrel;
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+}
+
+void ser_mou_dn(SDL_MouseButtonEvent *ev)
+{
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	if (ev->button == SDL_BUTTON_LEFT) {
+		mou_l = true;
+	}
+	else if (ev->button == SDL_BUTTON_RIGHT) {
+		mou_r = true;
+	}
+	else {
+		return;
+	}
+
+	mou_ev();
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+}
+
+void ser_mou_up(SDL_MouseButtonEvent *ev)
+{
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	if (ev->button == SDL_BUTTON_LEFT) {
+		mou_l = false;
+	}
+	else if (ev->button == SDL_BUTTON_RIGHT) {
+		mou_r = false;
+	}
+	else {
+		return;
+	}
+
+	mou_ev();
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+}
+
 void ser_init(void)
 {
@@ -343,4 +542,6 @@
 		}
 	}
+
+	mou_ev_chk();
 
 	return state[0].irq_r || state[0].irq_t || state[1].irq_r || state[1].irq_t;
@@ -379,4 +580,8 @@
 	}
 
+	if (!state[un].irq_r && !state[un].rdr_ok) {
+		xmit(un);
+	}
+
 	return rv;
 }
@@ -396,5 +601,12 @@
 	case REG_TDR_RDR:
 		ver2("TDR[%d] 0x%02x", un, val);
-		echo((uint8_t)val);
+
+		if (un == 1) {
+			echo((uint8_t)val);
+		}
+		else {
+			mouse((uint8_t)val);
+		}
+
 		state[un].irq_t = true;
 		break;
