diff --git a/src/clock.c b/src/clock.c
index e83e0f3..e44e112 100644
--- a/src/clock.c
+++ b/src/clock.c
@@ -295,6 +295,7 @@ clock_update(void)
     floppy_tick();
     usb_check_event();
     ps2_check_event();
+    sercon_check_event();
 }
 
 // INT 08h System Timer ISR Entry Point
diff --git a/src/serial.c b/src/serial.c
index 74b91bb..d72dd01 100644
--- a/src/serial.c
+++ b/src/serial.c
@@ -655,3 +655,258 @@ void sercon_enable(void)
     outb(0x01, addr + 0x02);       // enable fifo
     enable_vga_console();
 }
+
+/****************************************************************
+ * serial input
+ ****************************************************************/
+
+VARLOW u8 rx_buf[16];
+VARLOW u8 rx_bytes;
+
+VARLOW struct {
+    char seq[4];
+    u8 len;
+    u8 scancode;
+} termseq[] = {
+    { .seq = "OP", .len = 2, .scancode = 0x3b },    // F1
+    { .seq = "OQ", .len = 2, .scancode = 0x3c },    // F2
+    { .seq = "OR", .len = 2, .scancode = 0x3d },    // F3
+    { .seq = "OS", .len = 2, .scancode = 0x3e },    // F4
+    { .seq = "[A", .len = 2, .scancode = 0xc8 },    // up
+    { .seq = "[B", .len = 2, .scancode = 0xd0 },    // down
+    { .seq = "[C", .len = 2, .scancode = 0xcd },    // right
+    { .seq = "[D", .len = 2, .scancode = 0xcb },    // left
+};
+
+#define FLAG_CTRL  (1<<0)
+#define FLAG_SHIFT (1<<1)
+
+VARLOW struct {
+    u8 flags;
+    u8 scancode;
+} termchr[256] = {
+    [ '1'        ] = { .scancode = 0x02,                      },
+    [ '!'        ] = { .scancode = 0x02, .flags = FLAG_SHIFT  },
+    [ '2'        ] = { .scancode = 0x03,                      },
+    [ '@'        ] = { .scancode = 0x03, .flags = FLAG_SHIFT  },
+    [ '3'        ] = { .scancode = 0x04,                      },
+    [ '#'        ] = { .scancode = 0x04, .flags = FLAG_SHIFT  },
+    [ '4'        ] = { .scancode = 0x05,                      },
+    [ '$'        ] = { .scancode = 0x05, .flags = FLAG_SHIFT  },
+    [ '5'        ] = { .scancode = 0x06,                      },
+    [ '%'        ] = { .scancode = 0x06, .flags = FLAG_SHIFT  },
+    [ '6'        ] = { .scancode = 0x07,                      },
+    [ '^'        ] = { .scancode = 0x07, .flags = FLAG_SHIFT  },
+    [ '7'        ] = { .scancode = 0x08,                      },
+    [ '&'        ] = { .scancode = 0x08, .flags = FLAG_SHIFT  },
+    [ '8'        ] = { .scancode = 0x09,                      },
+    [ '*'        ] = { .scancode = 0x09, .flags = FLAG_SHIFT  },
+    [ '9'        ] = { .scancode = 0x0a,                      },
+    [ '('        ] = { .scancode = 0x0a, .flags = FLAG_SHIFT  },
+    [ '0'        ] = { .scancode = 0x0b,                      },
+    [ ')'        ] = { .scancode = 0x0b, .flags = FLAG_SHIFT  },
+    [ '-'        ] = { .scancode = 0x0c,                      },
+    [ '='        ] = { .scancode = 0x0d,                      },
+    [ '+'        ] = { .scancode = 0x0d, .flags = FLAG_SHIFT  },
+
+    [ 'q'        ] = { .scancode = 0x10,                      },
+    [ 'Q'        ] = { .scancode = 0x10, .flags = FLAG_SHIFT  },
+    [ 'Q' & 0x1f ] = { .scancode = 0x10, .flags = FLAG_CTRL   },
+    [ 'w'        ] = { .scancode = 0x11,                      },
+    [ 'W'        ] = { .scancode = 0x11, .flags = FLAG_SHIFT  },
+    [ 'W' & 0x1f ] = { .scancode = 0x11, .flags = FLAG_CTRL   },
+    [ 'e'        ] = { .scancode = 0x12,                      },
+    [ 'E'        ] = { .scancode = 0x12, .flags = FLAG_SHIFT  },
+    [ 'E' & 0x1f ] = { .scancode = 0x12, .flags = FLAG_CTRL   },
+    [ 'r'        ] = { .scancode = 0x13,                      },
+    [ 'R'        ] = { .scancode = 0x13, .flags = FLAG_SHIFT  },
+    [ 'R' & 0x1f ] = { .scancode = 0x13, .flags = FLAG_CTRL   },
+    [ 't'        ] = { .scancode = 0x14,                      },
+    [ 'T'        ] = { .scancode = 0x14, .flags = FLAG_SHIFT  },
+    [ 'T' & 0x1f ] = { .scancode = 0x14, .flags = FLAG_CTRL   },
+    [ 'y'        ] = { .scancode = 0x15,                      },
+    [ 'Y'        ] = { .scancode = 0x15, .flags = FLAG_SHIFT  },
+    [ 'Y' & 0x1f ] = { .scancode = 0x15, .flags = FLAG_CTRL   },
+    [ 'u'        ] = { .scancode = 0x16,                      },
+    [ 'U'        ] = { .scancode = 0x16, .flags = FLAG_SHIFT  },
+    [ 'U' & 0x1f ] = { .scancode = 0x16, .flags = FLAG_CTRL   },
+    [ 'i'        ] = { .scancode = 0x17,                      },
+    [ 'I'        ] = { .scancode = 0x17, .flags = FLAG_SHIFT  },
+    [ 'I' & 0x1f ] = { .scancode = 0x17, .flags = FLAG_CTRL   },
+    [ 'o'        ] = { .scancode = 0x18,                      },
+    [ 'O'        ] = { .scancode = 0x18, .flags = FLAG_SHIFT  },
+    [ 'O' & 0x1f ] = { .scancode = 0x18, .flags = FLAG_CTRL   },
+    [ 'p'        ] = { .scancode = 0x19,                      },
+    [ 'P'        ] = { .scancode = 0x19, .flags = FLAG_SHIFT  },
+    [ 'P' & 0x1f ] = { .scancode = 0x19, .flags = FLAG_CTRL   },
+    [ '['        ] = { .scancode = 0x1a,                      },
+    [ '{'        ] = { .scancode = 0x1a, .flags = FLAG_SHIFT  },
+    [ ']'        ] = { .scancode = 0x1b,                      },
+    [ '}'        ] = { .scancode = 0x1b, .flags = FLAG_SHIFT  },
+
+    [ 'a'        ] = { .scancode = 0x1e,                      },
+    [ 'A'        ] = { .scancode = 0x1e, .flags = FLAG_SHIFT  },
+    [ 'A' & 0x1f ] = { .scancode = 0x1e, .flags = FLAG_CTRL   },
+    [ 's'        ] = { .scancode = 0x1f,                      },
+    [ 'S'        ] = { .scancode = 0x1f, .flags = FLAG_SHIFT  },
+    [ 'S' & 0x1f ] = { .scancode = 0x1f, .flags = FLAG_CTRL   },
+    [ 'd'        ] = { .scancode = 0x20,                      },
+    [ 'D'        ] = { .scancode = 0x20, .flags = FLAG_SHIFT  },
+    [ 'D' & 0x1f ] = { .scancode = 0x20, .flags = FLAG_CTRL   },
+    [ 'f'        ] = { .scancode = 0x21,                      },
+    [ 'F'        ] = { .scancode = 0x21, .flags = FLAG_SHIFT  },
+    [ 'F' & 0x1f ] = { .scancode = 0x21, .flags = FLAG_CTRL   },
+    [ 'g'        ] = { .scancode = 0x22,                      },
+    [ 'G'        ] = { .scancode = 0x22, .flags = FLAG_SHIFT  },
+    [ 'G' & 0x1f ] = { .scancode = 0x22, .flags = FLAG_CTRL   },
+    [ 'h'        ] = { .scancode = 0x23,                      },
+    [ 'H'        ] = { .scancode = 0x23, .flags = FLAG_SHIFT  },
+    [ 'H' & 0x1f ] = { .scancode = 0x23, .flags = FLAG_CTRL   },
+    [ 'j'        ] = { .scancode = 0x24,                      },
+    [ 'J'        ] = { .scancode = 0x24, .flags = FLAG_SHIFT  },
+    [ 'J' & 0x1f ] = { .scancode = 0x24, .flags = FLAG_CTRL   },
+    [ 'k'        ] = { .scancode = 0x25,                      },
+    [ 'K'        ] = { .scancode = 0x25, .flags = FLAG_SHIFT  },
+    [ 'K' & 0x1f ] = { .scancode = 0x25, .flags = FLAG_CTRL   },
+    [ 'l'        ] = { .scancode = 0x26,                      },
+    [ 'L'        ] = { .scancode = 0x26, .flags = FLAG_SHIFT  },
+    [ 'L' & 0x1f ] = { .scancode = 0x26, .flags = FLAG_CTRL   },
+    [ ';'        ] = { .scancode = 0x27,                      },
+    [ ':'        ] = { .scancode = 0x27, .flags = FLAG_SHIFT  },
+    [ '\''       ] = { .scancode = 0x28,                      },
+    [ '"'        ] = { .scancode = 0x28, .flags = FLAG_SHIFT  },
+
+    [ '\\'       ] = { .scancode = 0x2b,                      },
+    [ '|'        ] = { .scancode = 0x2b, .flags = FLAG_SHIFT  },
+    [ 'z'        ] = { .scancode = 0x2c,                      },
+    [ 'Z'        ] = { .scancode = 0x2c, .flags = FLAG_SHIFT  },
+    [ 'Z' & 0x1f ] = { .scancode = 0x2c, .flags = FLAG_CTRL   },
+    [ 'x'        ] = { .scancode = 0x2d,                      },
+    [ 'X'        ] = { .scancode = 0x2d, .flags = FLAG_SHIFT  },
+    [ 'X' & 0x1f ] = { .scancode = 0x2d, .flags = FLAG_CTRL   },
+    [ 'c'        ] = { .scancode = 0x2e,                      },
+    [ 'C'        ] = { .scancode = 0x2e, .flags = FLAG_SHIFT  },
+    [ 'C' & 0x1f ] = { .scancode = 0x2e, .flags = FLAG_CTRL   },
+    [ 'v'        ] = { .scancode = 0x2f,                      },
+    [ 'V'        ] = { .scancode = 0x2f, .flags = FLAG_SHIFT  },
+    [ 'V' & 0x1f ] = { .scancode = 0x2f, .flags = FLAG_CTRL   },
+    [ 'b'        ] = { .scancode = 0x30,                      },
+    [ 'B'        ] = { .scancode = 0x30, .flags = FLAG_SHIFT  },
+    [ 'B' & 0x1f ] = { .scancode = 0x30, .flags = FLAG_CTRL   },
+    [ 'n'        ] = { .scancode = 0x31,                      },
+    [ 'N'        ] = { .scancode = 0x31, .flags = FLAG_SHIFT  },
+    [ 'N' & 0x1f ] = { .scancode = 0x31, .flags = FLAG_CTRL   },
+    [ 'm'        ] = { .scancode = 0x32,                      },
+    [ 'M'        ] = { .scancode = 0x32, .flags = FLAG_SHIFT  },
+    [ 'M' & 0x1f ] = { .scancode = 0x32, .flags = FLAG_CTRL   },
+    [ ','        ] = { .scancode = 0x33,                      },
+    [ '<'        ] = { .scancode = 0x33, .flags = FLAG_SHIFT  },
+    [ '.'        ] = { .scancode = 0x34,                      },
+    [ '>'        ] = { .scancode = 0x34, .flags = FLAG_SHIFT  },
+    [ '?'        ] = { .scancode = 0x35,                      },
+    [ '|'        ] = { .scancode = 0x35, .flags = FLAG_SHIFT  },
+
+    [ '\x1b'     ] = { .scancode = 0x01,                      },
+    [ '\t'       ] = { .scancode = 0x0f,                      },
+    [ '\r'       ] = { .scancode = 0x1c,                      },
+    [ '\n'       ] = { .scancode = 0x1c,                      },
+    [ ' '        ] = { .scancode = 0x39,                      },
+};
+
+static void shiftbuf(int remove)
+{
+    int i, remaining;
+
+    remaining = GET_LOW(rx_bytes) - remove;
+    SET_LOW(rx_bytes, remaining);
+    for (i = 0; i < remaining; i++)
+        SET_LOW(rx_buf[i], GET_LOW(rx_buf[i + remove]));
+}
+
+static void sercon_sendkey(u8 scancode, u8 flags)
+{
+    if (flags & FLAG_CTRL)
+        process_key(0x1d);
+    if (flags & FLAG_SHIFT)
+        process_key(0x2a);
+
+    if (scancode & 0x80) {
+        process_key(0xe0);
+        process_key(scancode & ~0x80);
+        process_key(0xe0);
+        process_key(scancode);
+    } else {
+        process_key(scancode);
+        process_key(scancode | 0x80);
+    }
+
+    if (flags & FLAG_SHIFT)
+        process_key(0x2a | 0x80);
+    if (flags & FLAG_CTRL)
+        process_key(0x1d | 0x80);
+}
+
+void VISIBLE16
+sercon_check_event(void)
+{
+    u16 addr = GET_LOW(sercon_port);
+    u8 byte, scancode, flags, count = 0;
+    int seq, chr, len;
+
+    // check to see if there is a active serial port
+    if (!addr)
+        return;
+    if (inb(addr + SEROFF_LSR) == 0xFF)
+        return;
+
+    // flush pending output
+    sercon_flush_lazy();
+
+    // read all available data
+    while (inb(addr + SEROFF_LSR) & 0x01) {
+        byte = inb(addr + SEROFF_DATA);
+        if (GET_LOW(rx_bytes) < sizeof(rx_buf)) {
+            SET_LOW(rx_buf[rx_bytes], byte);
+            SET_LOW(rx_bytes, GET_LOW(rx_bytes) + 1);
+            count++;
+        }
+    }
+
+next_char:
+    // no (more) input data
+    if (!GET_LOW(rx_bytes))
+        return;
+
+    // lookup escape sequences
+    if (GET_LOW(rx_bytes) > 1 && GET_LOW(rx_buf[0]) == 0x1b) {
+        for (seq = 0; seq < ARRAY_SIZE(termseq); seq++) {
+            len = GET_LOW(termseq[seq].len);
+            if (GET_LOW(rx_bytes) < len + 1)
+                continue;
+            for (chr = 0; chr < len; chr++) {
+                if (GET_LOW(termseq[seq].seq[chr]) != GET_LOW(rx_buf[chr + 1]))
+                    break;
+            }
+            if (chr == len) {
+                scancode = GET_LOW(termseq[seq].scancode);
+                sercon_sendkey(scancode, 0);
+                shiftbuf(len + 1);
+                goto next_char;
+            }
+        }
+    }
+
+    // Seems we got a escape sequence we didn't recognise.
+    //  -> If we received data wait for more, maybe it is just incomplete.
+    if (GET_LOW(rx_buf[0]) == 0x1b && count)
+        return;
+
+    // Handle input as individual chars.
+    chr = GET_LOW(rx_buf[0]);
+    scancode = GET_LOW(termchr[chr].scancode);
+    flags = GET_LOW(termchr[chr].flags);
+    if (scancode)
+        sercon_sendkey(scancode, flags);
+    shiftbuf(1);
+    goto next_char;
+}
diff --git a/src/util.h b/src/util.h
index 29f17be..3e7366b 100644
--- a/src/util.h
+++ b/src/util.h
@@ -232,6 +232,7 @@ void serial_setup(void);
 void lpt_setup(void);
 void sercon_10(struct bregs *regs);
 void sercon_enable(void);
+void sercon_check_event(void);
 
 // vgahooks.c
 void handle_155f(struct bregs *regs);
