diff --git a/Makefile b/Makefile
index 35e3688..ff90bb0 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,12 @@ default: run
 
 .FORCE:
 
-include/shade/version.h: .FORCE
+bin/shade.iso: bin/shade.bin
+	cp bin/shade.bin src/iso/boot/shade.bin
+	grub-mkrescue src/iso/ -o $@
+	rm src/iso/boot/shade.bin
+
+bin/shade.bin: $(sboot_object_files) $(kernel_object_files) $(libc_object_files)
 	echo "#ifndef VERSION_H" > include/shade/version.h
 	echo "#define VERSION_H" >> include/shade/version.h
 	echo "// This file was autogenerated by the shadeOS build system. It should not be modified." >> include/shade/version.h
@@ -53,13 +58,6 @@ include/shade/version.h: .FORCE
 	echo "#define SHADE_OS_COMPILE_DATE \"$(date)\"" >> include/shade/version.h
 	echo "#define SHADE_OS_CODENAME \"$(codename)\"" >> include/shade/version.h
 	echo "#endif" >> include/shade/version.h
-
-bin/shade.iso: bin/shade.bin
-	cp bin/shade.bin src/iso/boot/shade.bin
-	grub-mkrescue src/iso/ -o $@
-	rm src/iso/boot/shade.bin
-
-bin/shade.bin: $(sboot_object_files) $(kernel_object_files) $(libc_object_files)
 	mkdir -p "$(@D)"
 	$(LD) $(LDFLAGS) -n -T $(shade_bin_ldfile) $^ -o $@
 	grub-file --is-x86-multiboot2 $@
diff --git a/bin/shade.bin b/bin/shade.bin
index 67dff2a..612328a 100755
Binary files a/bin/shade.bin and b/bin/shade.bin differ
diff --git a/bin/shade.iso b/bin/shade.iso
index 5874439..a7b2e85 100644
Binary files a/bin/shade.iso and b/bin/shade.iso differ
diff --git a/include/shade/platform/drivers/keyboard.h b/include/shade/platform/drivers/keyboard.h
new file mode 100644
index 0000000..1b7cdec
--- /dev/null
+++ b/include/shade/platform/drivers/keyboard.h
@@ -0,0 +1,6 @@
+#ifndef KEYBOARD_H
+#define KEYBOARD_H
+
+void init_keyboard();
+
+#endif
\ No newline at end of file
diff --git a/include/shade/platform/interrupts/isr.h b/include/shade/platform/interrupts/isr.h
index 26c1fc8..eb6ec96 100644
--- a/include/shade/platform/interrupts/isr.h
+++ b/include/shade/platform/interrupts/isr.h
@@ -5,61 +5,38 @@
 
 #define ERR_MAX 5
 
+#define IRQ_MIN 0x20
+#define IRQ_MAX 0x2f
+
+#define IRQ0 0x20
+#define IRQ1 0x21
+#define IRQ2 0x22
+#define IRQ3 0x23
+#define IRQ4 0x24
+#define IRQ5 0x25
+#define IRQ6 0x26
+#define IRQ7 0x27
+#define IRQ8 0x28
+#define IRQ9 0x29
+#define IRQ10 0x2a
+#define IRQ11 0x2b
+#define IRQ12 0x2c
+#define IRQ13 0x2d
+#define IRQ14 0x2e
+#define IRQ15 0x2f
+
 static int err_count = 0;
 
-typedef struct {
-    struct {
-        uint64_t    resv0;
-        uint64_t    resv1;
-        uint64_t    resv2;
-        uint64_t    resv3;
-    } broken;
-
-    struct {
-        uint64_t    resv0;
-        uint64_t    resv1;
-        uint64_t    resv2;
-        uint64_t    resv3;
-        uint64_t    resv4;
-        uint64_t    resv5;
-    } broken_2;
-	
-    struct {
-        uint64_t    resv0;
-        uint64_t    error_code;
-        uint64_t    resv1;
-        uint64_t    vector;
-        uint64_t    resv2;
-        uint64_t    resv3;
-        uint64_t    resv4;
-        uint64_t    resv5;
-    } base_frame;
-} isr_xframe_t;
-
-/*
-List of pushed things in order:
-error_code
-vector
-rbp
-rax
-rbx
-rcx
-rdx
-rsi
-rdi
-cr0
-cr2
-cr3
-cr4
-ds
-0
-
-*/
+static void (*interrupt_handlers[256]) (uint8_t vector, uint16_t err);
 
 __attribute__((noreturn))
 void exception_handler(uint8_t vector, uint16_t err);
 
-__attribute__((noreturn))
-void interrupt_handler(isr_xframe_t frame);
+int register_interrupt_handler(int vector, void (*handler)(uint8_t, uint16_t));
+int unregister_interrupt_handler(int vector);
+
+void default_handler(uint8_t vector, uint16_t err);
+
+void fill_isr();
 
 #endif
\ No newline at end of file
diff --git a/include/shade/platform/interrupts/pic.h b/include/shade/platform/interrupts/pic.h
index 568e15a..d83689e 100644
--- a/include/shade/platform/interrupts/pic.h
+++ b/include/shade/platform/interrupts/pic.h
@@ -27,6 +27,22 @@
 #define PIC_READ_IRR                0x0a    /* OCW3 irq ready next CMD read */
 #define PIC_READ_ISR                0x0b    /* OCW3 irq service next CMD read */
 
+#define IRQ_PIT 0
+#define IRQ_KEYBOARD 1
+#define IRQ_INTERNAL 2
+#define IRQ_COM2 3
+#define IRQ_COM1 4
+#define IRQ_LPT2 5
+#define IRQ_FLOPPY 6
+#define IRQ_LPT1 7
+#define IRQ_CMOS 8
+#define IRQ_UNUSED 9
+#define IRQ_UNUSED 10
+#define IRQ_UNUSED 11
+#define IRQ_PS2_MOUSE 12
+#define IRQ_FPU 13
+#define IRQ_ATA_PRIMARY 14
+#define IRQ_ATA_SECONDARY 15
 
 void pic_send_eoi(uint8_t irq);
 void pic_remap(int offset1, int offset2);
diff --git a/include/shade/version.h b/include/shade/version.h
index b2db027..d1f2ec2 100644
--- a/include/shade/version.h
+++ b/include/shade/version.h
@@ -3,7 +3,7 @@
 // This file was autogenerated by the shadeOS build system. It should not be modified.
 #define SHADE_OS_KERNEL_VERSION "0.1.1-alpha"
 #define SHADE_OS_KERNEL "shade-development"
-#define SHADE_OS_BUILD "b4e6b2e"
-#define SHADE_OS_COMPILE_DATE "Sun May 15 05:41:17 PM EDT 2022"
+#define SHADE_OS_BUILD "20b96b2"
+#define SHADE_OS_COMPILE_DATE "Sun May 15 07:01:51 PM EDT 2022"
 #define SHADE_OS_CODENAME "willow"
 #endif
diff --git a/obj/kernel/kernel.o b/obj/kernel/kernel.o
index feca931..300f5fc 100644
Binary files a/obj/kernel/kernel.o and b/obj/kernel/kernel.o differ
diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c
index aa7f52f..b8a19fc 100644
--- a/src/kernel/kernel.c
+++ b/src/kernel/kernel.c
@@ -1,7 +1,10 @@
 #include <shade/platform/ports.h>
 #include <shade/platform/drivers/vga_text_mode.h>
 #include <shade/platform/interrupts/idt.h>
+#include <shade/platform/interrupts/isr.h>
 #include <shade/platform/interrupts/pic.h>
+#include <shade/platform/drivers/keyboard.h>
+#include <shade/platform/ports.h>
 #include <shade/version.h>
 #include <printf.h>
 #include <shade/kmsg.h>
@@ -37,8 +40,20 @@ void kmain() {
     kprintf("The full license can be found at /sys/LICENCE on this system or ./LICENCE in the source tree.\n");
 
     pic_remap(0x20, 0x28);
+    fill_isr(); // ISR must be filled before interrupts are enabled
     idt_assemble();
-    kmsg_ok("Enabled interrupts\n");
+    kmsg_ok("Enabled interrupts");
+
+    init_keyboard();
+
+    idt_disable_interrupts();
+    outb(0x21,0xfd);
+    outb(0xa1,0xff);
+    idt_enable_interrupts();
+
+    kmsg_ok("Enabled keyboard");
+
+    __asm__ __volatile__ ("int $2");
 
     for (;;) {}
 }
\ No newline at end of file
diff --git a/src/kernel/platform/drivers/keyboard.c b/src/kernel/platform/drivers/keyboard.c
new file mode 100644
index 0000000..31a4569
--- /dev/null
+++ b/src/kernel/platform/drivers/keyboard.c
@@ -0,0 +1,209 @@
+#include <shade/platform/drivers/keyboard.h>
+#include <shade/platform/ports.h>
+#include <shade/platform/interrupts/isr.h>
+#include <shade/platform/interrupts/pic.h>
+#include <stdint.h>
+#include <printf.h>
+
+void keyboard_callback(uint8_t vector, uint16_t err) {
+    // PIC leaves scancode in port 0x60
+    uint8_t scancode = inb(0x60);
+    print_letter(scancode);
+}
+
+void init_keyboard() {
+    register_interrupt_handler(IRQ1, keyboard_callback);
+    pic_unmask_irq(IRQ_KEYBOARD);
+}
+
+void print_letter(uint8_t scancode) {
+    switch (scancode) {
+        case 0x0:
+            kprintf("ERROR");
+            break;
+        case 0x1:
+            kprintf("ESC");
+            break;
+        case 0x2:
+            kprintf("1");
+            break;
+        case 0x3:
+            kprintf("2");
+            break;
+        case 0x4:
+            kprintf("3");
+            break;
+        case 0x5:
+            kprintf("4");
+            break;
+        case 0x6:
+            kprintf("5");
+            break;
+        case 0x7:
+            kprintf("6");
+            break;
+        case 0x8:
+            kprintf("7");
+            break;
+        case 0x9:
+            kprintf("8");
+            break;
+        case 0x0A:
+            kprintf("9");
+            break;
+        case 0x0B:
+            kprintf("0");
+            break;
+        case 0x0C:
+            kprintf("-");
+            break;
+        case 0x0D:
+            kprintf("+");
+            break;
+        case 0x0E:
+            kprintf("Backspace");
+            break;
+        case 0x0F:
+            kprintf("Tab");
+            break;
+        case 0x10:
+            kprintf("Q");
+            break;
+        case 0x11:
+            kprintf("W");
+            break;
+        case 0x12:
+            kprintf("E");
+            break;
+        case 0x13:
+            kprintf("R");
+            break;
+        case 0x14:
+            kprintf("T");
+            break;
+        case 0x15:
+            kprintf("Y");
+            break;
+        case 0x16:
+            kprintf("U");
+            break;
+        case 0x17:
+            kprintf("I");
+            break;
+        case 0x18:
+            kprintf("O");
+            break;
+        case 0x19:
+            kprintf("P");
+            break;
+		case 0x1A:
+			kprintf("[");
+			break;
+		case 0x1B:
+			kprintf("]");
+			break;
+		case 0x1C:
+			kprintf("ENTER");
+			break;
+		case 0x1D:
+			kprintf("LCtrl");
+			break;
+		case 0x1E:
+			kprintf("A");
+			break;
+		case 0x1F:
+			kprintf("S");
+			break;
+        case 0x20:
+            kprintf("D");
+            break;
+        case 0x21:
+            kprintf("F");
+            break;
+        case 0x22:
+            kprintf("G");
+            break;
+        case 0x23:
+            kprintf("H");
+            break;
+        case 0x24:
+            kprintf("J");
+            break;
+        case 0x25:
+            kprintf("K");
+            break;
+        case 0x26:
+            kprintf("L");
+            break;
+        case 0x27:
+            kprintf(";");
+            break;
+        case 0x28:
+            kprintf("'");
+            break;
+        case 0x29:
+            kprintf("`");
+            break;
+		case 0x2A:
+			kprintf("LShift");
+			break;
+		case 0x2B:
+			kprintf("\\");
+			break;
+		case 0x2C:
+			kprintf("Z");
+			break;
+		case 0x2D:
+			kprintf("X");
+			break;
+		case 0x2E:
+			kprintf("C");
+			break;
+		case 0x2F:
+			kprintf("V");
+			break;
+        case 0x30:
+            kprintf("B");
+            break;
+        case 0x31:
+            kprintf("N");
+            break;
+        case 0x32:
+            kprintf("M");
+            break;
+        case 0x33:
+            kprintf(",");
+            break;
+        case 0x34:
+            kprintf(".");
+            break;
+        case 0x35:
+            kprintf("/");
+            break;
+        case 0x36:
+            kprintf("Rshift");
+            break;
+        case 0x37:
+            kprintf("Keypad *");
+            break;
+        case 0x38:
+            kprintf("LAlt");
+            break;
+        case 0x39:
+            kprintf("Spc");
+            break;
+        default:
+            /* 'keuyp' event corresponds to the 'keydown' + 0x80 
+             * it may still be a scancode we haven't implemented yet, or
+             * maybe a control/escape sequence */
+            /*
+            if (scancode <= 0x7f) {
+                kprintf("Unknown key down");
+            } else if (scancode <= 0x39 + 0x80) {
+                kprintf("key up ");
+                print_letter(scancode - 0x80);
+            } else kprintf("Unknown key up");
+            */
+            break;
+    }
+}
\ No newline at end of file
diff --git a/src/kernel/platform/interrupts/isr.c b/src/kernel/platform/interrupts/isr.c
index 2790c63..c94bcf1 100644
--- a/src/kernel/platform/interrupts/isr.c
+++ b/src/kernel/platform/interrupts/isr.c
@@ -1,11 +1,35 @@
 #include <printf.h>
 #include <shade/platform/interrupts/isr.h>
+#include <shade/platform/interrupts/pic.h>
 
 void exception_handler(uint8_t vector, uint16_t err) {
+    (*interrupt_handlers[vector])(vector, err);
+    if (vector >= IRQ_MIN && vector <= IRQ_MAX) {
+        // needs EOI
+        pic_send_eoi(vector);
+    }
+}
+
+int register_interrupt_handler(int vector, void (*handler)(uint8_t, uint16_t)) {
+    interrupt_handlers[vector] = handler;
+}
+
+int unregister_interrupt_handler(int vector) {
+    interrupt_handlers[vector] = default_handler;
+}
+
+void default_handler(uint8_t vector, uint16_t err) {
     kprintf(" %i: cpu: check_exception 0x%x err_code => %x\n", err_count, vector, err);
+    kprintf(" %i: cpu: no irq for vector 0x%x\n", err_count, vector);
     err_count++;
     if (err_count > ERR_MAX) {
         kprintf("cpu: ierr hit err_max, halt\n");
         __asm__ __volatile__("cli; hlt");
     }
+}
+
+void fill_isr() {
+    for (int i = 0; i < 256; i++) {
+        register_interrupt_handler(i, default_handler);
+    }
 }
\ No newline at end of file
diff --git a/src/kernel/platform/interrupts/pic.c b/src/kernel/platform/interrupts/pic.c
index e312c8a..542c913 100644
--- a/src/kernel/platform/interrupts/pic.c
+++ b/src/kernel/platform/interrupts/pic.c
@@ -1,5 +1,6 @@
 #include <shade/platform/interrupts/pic.h>
 #include <shade/platform/ports.h>
+#include <shade/platform/interrupts/idt.h>
 
 void pic_send_eoi(uint8_t irq) {
     if (irq >= 8) {
@@ -37,6 +38,7 @@ void pic_remap(int offset1, int offset2) {
 }
 
 void pic_mask_irq(uint8_t irq) {
+    idt_disable_interrupts();
     uint16_t port;
     uint8_t value;
 
@@ -48,9 +50,11 @@ void pic_mask_irq(uint8_t irq) {
     }
     value = inb(port) | (1 << irq);
     outb(port, value);
+    idt_enable_interrupts();
 }
 
 void pic_unmask_irq(uint8_t irq) {
+    idt_disable_interrupts();
     uint16_t port;
     uint8_t value;
 
@@ -62,6 +66,7 @@ void pic_unmask_irq(uint8_t irq) {
     }
     value = inb(port) & ~(1 << irq);
     outb(port, value);
+    idt_enable_interrupts();
 }
 
 static uint16_t __pic_get_irq_reg(int ocw3) {