:: commit 902109ef68ba56323dc235a88fef32c97f2268de

mintsuki <mintsuki@protonmail.com> — 2020-09-02 07:55

parents: 3eb7d5f0ba

Wire up logic for graphical terminal

diff --git a/limine.bin b/limine.bin
index e6bb1327..c0cbc09c 100644
Binary files a/limine.bin and b/limine.bin differ
diff --git a/src/drivers/vbe.c b/src/drivers/vbe.c
index 5fac9b83..c96eeb35 100644
--- a/src/drivers/vbe.c
+++ b/src/drivers/vbe.c
@@ -19,7 +19,7 @@ static void vga_font_retrieve(void) {
     struct rm_regs r = {0};
 
     r.eax = 0x1130;
-    r.ebx = 0x06;
+    r.ebx = 0x0600;
     rm_int(0x10, &r, &r);
 
     vga_font = ext_mem_balloc(VGA_FONT_MAX);
@@ -39,6 +39,12 @@ void vbe_plot_px(int x, int y, uint32_t hex) {
     vbe_framebuffer[fb_i] = hex;
 }
 
+struct vbe_char {
+    char c;
+    uint32_t fg;
+    uint32_t bg;
+};
+
 void vbe_plot_char(struct vbe_char c, int x, int y) {
     int orig_x = x;
     uint8_t *glyph = &vga_font[c.c * VGA_FONT_HEIGHT];
@@ -86,7 +92,7 @@ static void draw_cursor(void) {
         vbe_plot_char(c, cursor_x * VGA_FONT_WIDTH, cursor_y * VGA_FONT_HEIGHT);
 }
 
-void vbe_scroll(void) {
+static void scroll(void) {
     clear_cursor();
 
     for (int i = cols; i < rows * cols; i++) {
@@ -105,7 +111,7 @@ void vbe_scroll(void) {
     draw_cursor();
 }
 
-void vbe_clear(void) {
+void vbe_clear(bool move) {
     clear_cursor();
 
     struct vbe_char empty;
@@ -116,8 +122,10 @@ void vbe_clear(void) {
         plot_char_grid(empty, i % cols, i / cols);
     }
 
-    cursor_x = 0;
-    cursor_y = 0;
+    if (move) {
+        cursor_x = 0;
+        cursor_y = 0;
+    }
 
     draw_cursor();
 }
@@ -144,25 +152,78 @@ void vbe_get_cursor_pos(int *x, int *y) {
     *y = cursor_y;
 }
 
-void vbe_set_text_attributes(uint32_t fg, uint32_t bg) {
-    text_fg = fg;
-    text_bg = bg;
+static uint32_t ansi_colours[] = {
+    0x00000000,              // black
+    0x00aa0000,              // red
+    0x0000aa00,              // green
+    0x00aa5500,              // brown
+    0x000000aa,              // blue
+    0x00aa00aa,              // magenta
+    0x0000aaaa,              // cyan
+    0x00aaaaaa               // grey
+};
+
+void vbe_set_text_fg(int fg) {
+    text_fg = ansi_colours[fg];
 }
 
-void vbe_set_cursor_attributes(uint32_t fg, uint32_t bg) {
-    clear_cursor();
-    cursor_fg = fg;
-    cursor_bg = bg;
-    draw_cursor();
+void vbe_set_text_bg(int bg) {
+    text_bg = ansi_colours[bg];
+}
+
+void vbe_putchar(char c) {
+    switch (c) {
+        case '\b':
+            if (cursor_x || cursor_y) {
+                clear_cursor();
+                if (cursor_x) {
+                    cursor_x--;
+                } else {
+                    cursor_y--;
+                    cursor_x = cols - 1;
+                }
+                draw_cursor();
+            }
+            break;
+        case '\r':
+            vbe_set_cursor_pos(0, cursor_y);
+            break;
+        case '\n':
+            if (cursor_y == (rows - 1)) {
+                vbe_set_cursor_pos(0, rows - 1);
+                scroll();
+            } else {
+                vbe_set_cursor_pos(0, cursor_y + 1);
+            }
+            break;
+        default: {
+            clear_cursor();
+            struct vbe_char ch;
+            ch.c  = c;
+            ch.fg = text_fg;
+            ch.bg = text_bg;
+            plot_char_grid(ch, cursor_x++, cursor_y);
+            if (cursor_x == cols) {
+                cursor_x = 0;
+                cursor_y++;
+            }
+            if (cursor_y == rows) {
+                cursor_y--;
+                scroll();
+            }
+            draw_cursor();
+            break;
+        }
+    }
 }
 
-void vbe_tty_init(void) {
+void vbe_tty_init(int *_rows, int *_cols) {
     init_vbe(&vbe_framebuffer, &vbe_pitch, &vbe_width, &vbe_height, &vbe_bpp);
     vga_font_retrieve();
-    cols = vbe_width / VGA_FONT_WIDTH;
-    rows = vbe_height / VGA_FONT_HEIGHT;
+    *_cols = cols = vbe_width / VGA_FONT_WIDTH;
+    *_rows = rows = vbe_height / VGA_FONT_HEIGHT;
     grid = ext_mem_balloc(rows * cols * sizeof(struct vbe_char));
-    vbe_clear();
+    vbe_clear(true);
 }
 
 struct vbe_info_struct {
diff --git a/src/drivers/vbe.h b/src/drivers/vbe.h
index af9fa30e..03f6ac65 100644
--- a/src/drivers/vbe.h
+++ b/src/drivers/vbe.h
@@ -2,13 +2,19 @@
 #define __DRIVERS__VBE_H__
 
 #include <stdint.h>
-
-struct vbe_char {
-    char c;
-    uint32_t fg;
-    uint32_t bg;
-};
+#include <stdbool.h>
 
 int init_vbe(uint32_t **framebuffer, uint16_t *pitch, uint16_t *target_width, uint16_t *target_height, uint16_t *target_bpp);
 
+void vbe_tty_init(int *rows, int *cols);
+
+void vbe_putchar(char c);
+void vbe_clear(bool move);
+void vbe_enable_cursor(void);
+void vbe_disable_cursor(void);
+void vbe_set_cursor_pos(int x, int y);
+void vbe_get_cursor_pos(int *x, int *y);
+void vbe_set_text_fg(int fg);
+void vbe_set_text_bg(int bg);
+
 #endif
diff --git a/src/drivers/vga_textmode.c b/src/drivers/vga_textmode.c
index 4aeafd41..15dcf47b 100644
--- a/src/drivers/vga_textmode.c
+++ b/src/drivers/vga_textmode.c
@@ -9,23 +9,11 @@
 #define VD_COLS (80 * 2)
 #define VD_ROWS 25
 
-static bool vga_textmode_initialised = false;
-
-static void escape_parse(char c);
-static void text_putchar(char c);
-
 static char *video_mem = (char *)0xb8000;
 static size_t cursor_offset = 0;
 static int cursor_status = 1;
 static uint8_t text_palette = 0x07;
 static uint8_t cursor_palette = 0x70;
-static int escape = 0;
-static int esc_value0 = 0;
-static int esc_value1 = 0;
-static int *esc_value = &esc_value0;
-static int esc_default0 = 1;
-static int esc_default1 = 1;
-static int *esc_default = &esc_default0;
 
 static void clear_cursor(void) {
     video_mem[cursor_offset + 1] = text_palette;
@@ -51,23 +39,14 @@ static void scroll(void) {
     return;
 }
 
-void text_clear(void) {
-    clear_cursor();
-    for (size_t i = 0; i < VIDEO_BOTTOM; i += 2) {
-        video_mem[i] = ' ';
-        video_mem[i + 1] = text_palette;
-    }
-    cursor_offset = 0;
-    draw_cursor();
-    return;
-}
-
-static void text_clear_no_move(void) {
+void text_clear(bool move) {
     clear_cursor();
     for (size_t i = 0; i < VIDEO_BOTTOM; i += 2) {
         video_mem[i] = ' ';
         video_mem[i + 1] = text_palette;
     }
+    if (move)
+        cursor_offset = 0;
     draw_cursor();
     return;
 }
@@ -86,20 +65,13 @@ void text_disable_cursor(void) {
 
 // VGA cursor code taken from: https://wiki.osdev.org/Text_Mode_Cursor
 
-void init_vga_textmode(void) {
+void init_vga_textmode(int *_rows, int *_cols) {
     port_out_b(0x3d4, 0x0a);
     port_out_b(0x3d5, 0x20);
-    text_clear();
-
-    vga_textmode_initialised = true;
-}
-
-void deinit_vga_textmode(void) {
-    struct rm_regs r = {0};
-    r.eax = 0x0003;
-    rm_int(0x10, &r, &r);
+    text_clear(true);
 
-    vga_textmode_initialised = false;
+    *_rows = VD_ROWS;
+    *_cols = VD_COLS / 2;
 }
 
 static void text_set_cursor_palette(uint8_t c) {
@@ -140,24 +112,28 @@ void text_set_cursor_pos(int x, int y) {
     draw_cursor();
 }
 
-void text_write(const char *buf, size_t count) {
-    if (!vga_textmode_initialised)
-        return;
-    for (size_t i = 0; i < count; i++)
-        text_putchar(buf[i]);
+static uint8_t ansi_colours[] = { 0, 4, 2, 0x0e, 1, 5, 3, 7 };
+
+void text_set_text_fg(int fg) {
+    text_palette = (text_palette & 0xf0) | ansi_colours[fg];
 }
 
-static void text_putchar(char c) {
-    if (escape) {
-        escape_parse(c);
-        return;
-    }
+void text_set_text_bg(int bg) {
+    text_palette = (text_palette & 0x0f) | (ansi_colours[bg] << 4);
+}
+
+void text_putchar(char c) {
     switch (c) {
-        case 0x00:
+        case '\b':
+            if (cursor_offset) {
+                clear_cursor();
+                cursor_offset -= 2;
+                draw_cursor();
+            }
+            break;
+        case '\r':
+            text_set_cursor_pos(0, text_get_cursor_pos_y());
             break;
-        case 0x1B:
-            escape = 1;
-            return;
         case '\n':
             if (text_get_cursor_pos_y() == (VD_ROWS - 1)) {
                 clear_cursor();
@@ -167,16 +143,6 @@ static void text_putchar(char c) {
                 text_set_cursor_pos(0, (text_get_cursor_pos_y() + 1));
             }
             break;
-        case '\r':
-            text_set_cursor_pos(0, text_get_cursor_pos_y());
-            break;
-        case '\b':
-            if (cursor_offset) {
-                clear_cursor();
-                cursor_offset -= 2;
-                draw_cursor();
-            }
-            break;
         default:
             clear_cursor();
             video_mem[cursor_offset] = c;
@@ -187,116 +153,4 @@ static void text_putchar(char c) {
                 cursor_offset += 2;
             draw_cursor();
     }
-    return;
-}
-
-static uint8_t ansi_colours[] = { 0, 4, 2, 0x0e, 1, 5, 3, 7 };
-
-static void sgr(void) {
-
-    if (esc_value0 >= 30 && esc_value0 <= 37) {
-        uint8_t pal = text_get_text_palette();
-        pal = (pal & 0xf0) + ansi_colours[esc_value0 - 30];
-        text_set_text_palette(pal);
-        return;
-    }
-
-    if (esc_value0 >= 40 && esc_value0 <= 47) {
-        uint8_t pal = text_get_text_palette();
-        pal = (pal & 0x0f) + ansi_colours[esc_value0 - 40] * 0x10;
-        text_set_text_palette(pal);
-        return;
-    }
-
-    return;
-}
-
-static void escape_parse(char c) {
-
-    if (c >= '0' && c <= '9') {
-        *esc_value *= 10;
-        *esc_value += c - '0';
-        *esc_default = 0;
-        return;
-    }
-
-    switch (c) {
-        case '[':
-            return;
-        case ';':
-            esc_value = &esc_value1;
-            esc_default = &esc_default1;
-            return;
-        case 'A':
-            if (esc_default0)
-                esc_value0 = 1;
-            if (esc_value0 > text_get_cursor_pos_y())
-                esc_value0 = text_get_cursor_pos_y();
-            text_set_cursor_pos(text_get_cursor_pos_x(),
-                                text_get_cursor_pos_y() - esc_value0);
-            break;
-        case 'B':
-            if (esc_default0)
-                esc_value0 = 1;
-            if ((text_get_cursor_pos_y() + esc_value0) > (VD_ROWS - 1))
-                esc_value0 = (VD_ROWS - 1) - text_get_cursor_pos_y();
-            text_set_cursor_pos(text_get_cursor_pos_x(),
-                                text_get_cursor_pos_y() + esc_value0);
-            break;
-        case 'C':
-            if (esc_default0)
-                esc_value0 = 1;
-            if ((text_get_cursor_pos_x() + esc_value0) > (VD_COLS / 2 - 1))
-                esc_value0 = (VD_COLS / 2 - 1) - text_get_cursor_pos_x();
-            text_set_cursor_pos(text_get_cursor_pos_x() + esc_value0,
-                                text_get_cursor_pos_y());
-            break;
-        case 'D':
-            if (esc_default0)
-                esc_value0 = 1;
-            if (esc_value0 > text_get_cursor_pos_x())
-                esc_value0 = text_get_cursor_pos_x();
-            text_set_cursor_pos(text_get_cursor_pos_x() - esc_value0,
-                                text_get_cursor_pos_y());
-            break;
-        case 'H':
-            esc_value0--;
-            esc_value1--;
-            if (esc_default0)
-                esc_value0 = 0;
-            if (esc_default1)
-                esc_value1 = 0;
-            if (esc_value1 >= (VD_COLS / 2))
-                esc_value1 = (VD_COLS / 2) - 1;
-            if (esc_value0 >= VD_ROWS)
-                esc_value0 = VD_ROWS - 1;
-            text_set_cursor_pos(esc_value1, esc_value0);
-            break;
-        case 'm':
-            sgr();
-            break;
-        case 'J':
-            switch (esc_value0) {
-                case 2:
-                    text_clear_no_move();
-                    break;
-                default:
-                    break;
-            }
-            break;
-        default:
-            escape = 0;
-            text_putchar('?');
-            break;
-    }
-
-    esc_value = &esc_value0;
-    esc_value0 = 0;
-    esc_value1 = 0;
-    esc_default = &esc_default0;
-    esc_default0 = 1;
-    esc_default1 = 1;
-    escape = 0;
-
-    return;
 }
diff --git a/src/drivers/vga_textmode.h b/src/drivers/vga_textmode.h
index 310badf0..7cc3cc97 100644
--- a/src/drivers/vga_textmode.h
+++ b/src/drivers/vga_textmode.h
@@ -1,18 +1,17 @@
 #ifndef __DRIVERS__VGA_TEXTMODE_H__
 #define __DRIVERS__VGA_TEXTMODE_H__
 
-#include <stddef.h>
+#include <stdbool.h>
 
-void init_vga_textmode(void);
-void deinit_vga_textmode(void);
+void init_vga_textmode(int *rows, int *cols);
 
-void text_write(const char *, size_t);
-
-void text_get_cursor_pos(int *x, int *y);
-void text_set_cursor_pos(int x, int y);
-
-void text_clear(void);
+void text_putchar(char c);
+void text_clear(bool move);
 void text_enable_cursor(void);
 void text_disable_cursor(void);
+void text_set_cursor_pos(int x, int y);
+void text_get_cursor_pos(int *x, int *y);
+void text_set_text_fg(int fg);
+void text_set_text_bg(int bg);
 
 #endif
diff --git a/src/lib/blib.c b/src/lib/blib.c
index 3165da38..6f598cd3 100644
--- a/src/lib/blib.c
+++ b/src/lib/blib.c
@@ -4,7 +4,7 @@
 #include <stdbool.h>
 #include <lib/blib.h>
 #include <lib/libc.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 #include <lib/real.h>
 #include <lib/cio.h>
 #include <lib/e820.h>
@@ -214,14 +214,14 @@ int getchar(void) {
 
 static void gets_reprint_string(int x, int y, const char *s, size_t limit) {
     int last_x, last_y;
-    text_get_cursor_pos(&last_x, &last_y);
-    text_set_cursor_pos(x, y);
+    get_cursor_pos(&last_x, &last_y);
+    set_cursor_pos(x, y);
     for (size_t i = 0; i < limit; i++) {
-        text_write(" ", 1);
+        term_write(" ", 1);
     }
-    text_set_cursor_pos(x, y);
-    text_write(s, strlen(s));
-    text_set_cursor_pos(last_x, last_y);
+    set_cursor_pos(x, y);
+    term_write(s, strlen(s));
+    set_cursor_pos(last_x, last_y);
 }
 
 void gets(const char *orig_str, char *buf, size_t limit) {
@@ -230,7 +230,7 @@ void gets(const char *orig_str, char *buf, size_t limit) {
     buf[orig_str_len] = 0;
 
     int orig_x, orig_y;
-    text_get_cursor_pos(&orig_x, &orig_y);
+    get_cursor_pos(&orig_x, &orig_y);
 
     print("%s", buf);
 
@@ -240,13 +240,13 @@ void gets(const char *orig_str, char *buf, size_t limit) {
             case GETCHAR_CURSOR_LEFT:
                 if (i) {
                     i--;
-                    text_write("\b", 1);
+                    term_write("\b", 1);
                 }
                 continue;
             case GETCHAR_CURSOR_RIGHT:
                 if (i < strlen(buf)) {
                     i++;
-                    text_write(" ", 1);
+                    term_write(" ", 1);
                     gets_reprint_string(orig_x, orig_y, buf, limit);
                 }
                 continue;
@@ -258,12 +258,12 @@ void gets(const char *orig_str, char *buf, size_t limit) {
                         if (!buf[j])
                             break;
                     }
-                    text_write("\b", 1);
+                    term_write("\b", 1);
                     gets_reprint_string(orig_x, orig_y, buf, limit);
                 }
                 continue;
             case '\r':
-                text_write("\n", 1);
+                term_write("\n", 1);
                 return;
             default:
                 if (strlen(buf) < limit-1) {
@@ -273,7 +273,7 @@ void gets(const char *orig_str, char *buf, size_t limit) {
                             break;
                     }
                     buf[i++] = c;
-                    text_write(" ", 1);
+                    term_write(" ", 1);
                     gets_reprint_string(orig_x, orig_y, buf, limit);
                 }
         }
diff --git a/src/lib/print.c b/src/lib/print.c
index 947817fc..6531654a 100644
--- a/src/lib/print.c
+++ b/src/lib/print.c
@@ -4,7 +4,7 @@
 #include <lib/print.h>
 #include <lib/blib.h>
 #include <lib/cio.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 
 static const char *base_digits = "0123456789abcdef";
 
@@ -167,7 +167,7 @@ void vprint(const char *fmt, va_list args) {
     }
 
 out:
-    text_write(print_buf, print_buf_i);
+    term_write(print_buf, print_buf_i);
 
 #ifdef E9_OUTPUT
     for (size_t i = 0; i < print_buf_i; i++)
diff --git a/src/lib/term.c b/src/lib/term.c
new file mode 100644
index 00000000..1f87dfcf
--- /dev/null
+++ b/src/lib/term.c
@@ -0,0 +1,209 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <lib/term.h>
+#include <lib/real.h>
+#include <drivers/vga_textmode.h>
+#include <drivers/vbe.h>
+
+static enum {
+    NOT_READY,
+    VBE,
+    TEXTMODE
+} term_backend = NOT_READY;
+
+void (*raw_putchar)(char c);
+void (*clear)(bool move);
+void (*enable_cursor)(void);
+void (*disable_cursor)(void);
+void (*set_cursor_pos)(int x, int y);
+void (*get_cursor_pos)(int *x, int *y);
+void (*set_text_fg)(int fg);
+void (*set_text_bg)(int bg);
+
+static int rows, cols;
+
+void term_vbe(void) {
+    vbe_tty_init(&rows, &cols);
+
+    raw_putchar    = vbe_putchar;
+    clear          = vbe_clear;
+    enable_cursor  = vbe_enable_cursor;
+    disable_cursor = vbe_disable_cursor;
+    set_cursor_pos = vbe_set_cursor_pos;
+    get_cursor_pos = vbe_get_cursor_pos;
+    set_text_fg    = vbe_set_text_fg;
+    set_text_bg    = vbe_set_text_bg;
+
+    term_backend = VBE;
+}
+
+void term_textmode(void) {
+    init_vga_textmode(&rows, &cols);
+
+    raw_putchar    = text_putchar;
+    clear          = text_clear;
+    enable_cursor  = text_enable_cursor;
+    disable_cursor = text_disable_cursor;
+    set_cursor_pos = text_set_cursor_pos;
+    get_cursor_pos = text_get_cursor_pos;
+    set_text_fg    = text_set_text_fg;
+    set_text_bg    = text_set_text_bg;
+
+    term_backend = TEXTMODE;
+}
+
+void term_deinit(void) {
+    struct rm_regs r = {0};
+    r.eax = 0x0003;
+    rm_int(0x10, &r, &r);
+
+    term_backend = NOT_READY;
+}
+
+static void term_putchar(char c);
+
+void term_write(const char *buf, size_t count) {
+    if (term_backend == NOT_READY)
+        return;
+    for (size_t i = 0; i < count; i++)
+        term_putchar(buf[i]);
+}
+
+static int get_cursor_pos_x(void) {
+    int x, y;
+    get_cursor_pos(&x, &y);
+    return x;
+}
+
+static int get_cursor_pos_y(void) {
+    int x, y;
+    get_cursor_pos(&x, &y);
+    return y;
+}
+
+static void escape_parse(char c);
+
+static int escape = 0;
+static int esc_value0 = 0;
+static int esc_value1 = 0;
+static int *esc_value = &esc_value0;
+static int esc_default0 = 1;
+static int esc_default1 = 1;
+static int *esc_default = &esc_default0;
+
+static void term_putchar(char c) {
+    if (escape) {
+        escape_parse(c);
+        return;
+    }
+    switch (c) {
+        case 0x00:
+            break;
+        case 0x1B:
+            escape = 1;
+            return;
+        default:
+            raw_putchar(c);
+            break;
+    }
+}
+
+static void sgr(void) {
+    if (esc_value0 >= 30 && esc_value0 <= 37) {
+        set_text_fg(esc_value0 - 30);
+        return;
+    }
+
+    if (esc_value0 >= 40 && esc_value0 <= 47) {
+        set_text_bg(esc_value0 - 40);
+        return;
+    }
+}
+
+static void escape_parse(char c) {
+    if (c >= '0' && c <= '9') {
+        *esc_value *= 10;
+        *esc_value += c - '0';
+        *esc_default = 0;
+        return;
+    }
+
+    switch (c) {
+        case '[':
+            return;
+        case ';':
+            esc_value = &esc_value1;
+            esc_default = &esc_default1;
+            return;
+        case 'A':
+            if (esc_default0)
+                esc_value0 = 1;
+            if (esc_value0 > get_cursor_pos_y())
+                esc_value0 = get_cursor_pos_y();
+            set_cursor_pos(get_cursor_pos_x(),
+                                get_cursor_pos_y() - esc_value0);
+            break;
+        case 'B':
+            if (esc_default0)
+                esc_value0 = 1;
+            if ((get_cursor_pos_y() + esc_value0) > (rows - 1))
+                esc_value0 = (rows - 1) - get_cursor_pos_y();
+            set_cursor_pos(get_cursor_pos_x(),
+                                get_cursor_pos_y() + esc_value0);
+            break;
+        case 'C':
+            if (esc_default0)
+                esc_value0 = 1;
+            if ((get_cursor_pos_x() + esc_value0) > (cols - 1))
+                esc_value0 = (cols - 1) - get_cursor_pos_x();
+            set_cursor_pos(get_cursor_pos_x() + esc_value0,
+                                get_cursor_pos_y());
+            break;
+        case 'D':
+            if (esc_default0)
+                esc_value0 = 1;
+            if (esc_value0 > get_cursor_pos_x())
+                esc_value0 = get_cursor_pos_x();
+            set_cursor_pos(get_cursor_pos_x() - esc_value0,
+                                get_cursor_pos_y());
+            break;
+        case 'H':
+            esc_value0--;
+            esc_value1--;
+            if (esc_default0)
+                esc_value0 = 0;
+            if (esc_default1)
+                esc_value1 = 0;
+            if (esc_value1 >= cols)
+                esc_value1 = cols - 1;
+            if (esc_value0 >= rows)
+                esc_value0 = rows - 1;
+            set_cursor_pos(esc_value1, esc_value0);
+            break;
+        case 'm':
+            sgr();
+            break;
+        case 'J':
+            switch (esc_value0) {
+                case 2:
+                    clear(false);
+                    break;
+                default:
+                    break;
+            }
+            break;
+        default:
+            escape = 0;
+            raw_putchar('?');
+            break;
+    }
+
+    esc_value = &esc_value0;
+    esc_value0 = 0;
+    esc_value1 = 0;
+    esc_default = &esc_default0;
+    esc_default0 = 1;
+    esc_default1 = 1;
+    escape = 0;
+}
diff --git a/src/lib/term.h b/src/lib/term.h
new file mode 100644
index 00000000..4a4be458
--- /dev/null
+++ b/src/lib/term.h
@@ -0,0 +1,21 @@
+#ifndef __LIB__TERM_H__
+#define __LIB__TERM_H__
+
+#include <stddef.h>
+#include <stdbool.h>
+
+extern void (*raw_putchar)(char c);
+extern void (*clear)(bool move);
+extern void (*enable_cursor)(void);
+extern void (*disable_cursor)(void);
+extern void (*set_cursor_pos)(int x, int y);
+extern void (*get_cursor_pos)(int *x, int *y);
+extern void (*set_text_fg)(int fg);
+extern void (*set_text_bg)(int bg);
+
+void term_vbe(void);
+void term_textmode(void);
+void term_deinit(void);
+void term_write(const char *buf, size_t count);
+
+#endif
diff --git a/src/main.c b/src/main.c
index 480ba396..4edbd029 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@ ASM_BASIC(
 );
 
 #include <limine.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 #include <lib/real.h>
 #include <lib/blib.h>
 #include <lib/libc.h>
@@ -32,8 +32,7 @@ ASM_BASIC(
 #include <menu.h>
 
 void main(int boot_drive) {
-    // Initial prompt.
-    init_vga_textmode();
+    term_textmode();
 
     print("Limine " LIMINE_VERSION "\n\n");
 
@@ -59,25 +58,32 @@ void main(int boot_drive) {
         }
     }
 
-    char *cmdline = menu();
-
     init_e820();
     init_memmap();
 
-    char proto[32];
-    if (!config_get_value(proto, 0, 32, "KERNEL_PROTO")) {
-        if (!config_get_value(proto, 0, 32, "PROTOCOL")) {
+    char buf[32];
+
+    if (config_get_value(buf, 0, 32, "GRAPHICS")) {
+        if (!strcmp(buf, "on")) {
+            term_vbe();
+        }
+    }
+
+    char *cmdline = menu();
+
+    if (!config_get_value(buf, 0, 32, "KERNEL_PROTO")) {
+        if (!config_get_value(buf, 0, 32, "PROTOCOL")) {
             panic("PROTOCOL not specified");
         }
     }
 
-    if (!strcmp(proto, "stivale")) {
+    if (!strcmp(buf, "stivale")) {
         stivale_load(cmdline, boot_drive);
-    } else if (!strcmp(proto, "stivale2")) {
+    } else if (!strcmp(buf, "stivale2")) {
         stivale2_load(cmdline, boot_drive);
-    } else if (!strcmp(proto, "linux")) {
+    } else if (!strcmp(buf, "linux")) {
         linux_load(cmdline, boot_drive);
-    } else if (!strcmp(proto, "chainload")) {
+    } else if (!strcmp(buf, "chainload")) {
         chainload();
     } else {
         panic("Invalid protocol specified");
diff --git a/src/menu.c b/src/menu.c
index 8766c147..cf1f139a 100644
--- a/src/menu.c
+++ b/src/menu.c
@@ -7,7 +7,7 @@
 #include <lib/blib.h>
 #include <lib/libc.h>
 #include <lib/config.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 
 static char *cmdline;
 #define CMDLINE_MAX 1024
@@ -26,12 +26,12 @@ char *menu(void) {
         }
     }
 
-    text_disable_cursor();
+    disable_cursor();
     int selected_entry = 0;
     bool skip_timeout = false;
 
 refresh:
-    text_clear();
+    clear(true);
     print("\n\n  \e[36m Limine " LIMINE_VERSION " \e[37m\n\n\n");
 
     print("Select an entry:\n\n");
@@ -78,17 +78,17 @@ refresh:
             case '\r':
             autoboot:
                 config_set_entry(selected_entry);
-                text_enable_cursor();
+                enable_cursor();
                 if (!config_get_value(cmdline, 0, CMDLINE_MAX, "KERNEL_CMDLINE")) {
                     if (!config_get_value(cmdline, 0, CMDLINE_MAX, "CMDLINE")) {
                         cmdline[0] = '\0';
                     }
                 }
-                text_clear();
+                clear(true);
                 return cmdline;
             case 'e':
                 config_set_entry(selected_entry);
-                text_enable_cursor();
+                enable_cursor();
                 if (!config_get_value(cmdline, 0, CMDLINE_MAX, "KERNEL_CMDLINE")) {
                     if (!config_get_value(cmdline, 0, CMDLINE_MAX, "CMDLINE")) {
                         cmdline[0] = '\0';
@@ -96,7 +96,7 @@ refresh:
                 }
                 print("\n\n> ");
                 gets(cmdline, cmdline, CMDLINE_MAX);
-                text_clear();
+                clear(true);
                 return cmdline;
         }
     }
diff --git a/src/protos/chainload.c b/src/protos/chainload.c
index d72f5c48..f073db13 100644
--- a/src/protos/chainload.c
+++ b/src/protos/chainload.c
@@ -5,7 +5,7 @@
 #include <lib/config.h>
 #include <lib/blib.h>
 #include <drivers/disk.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 #include <lib/asm.h>
 
 void chainload(void) {
@@ -25,7 +25,7 @@ void chainload(void) {
         drive = (int)strtoui(buf);
     }
 
-    deinit_vga_textmode();
+    term_deinit();
 
     if (part != -1) {
         struct part p;
diff --git a/src/protos/linux.c b/src/protos/linux.c
index e296b599..3e38f3b7 100644
--- a/src/protos/linux.c
+++ b/src/protos/linux.c
@@ -4,7 +4,7 @@
 #include <fs/file.h>
 #include <lib/blib.h>
 #include <lib/real.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 #include <lib/config.h>
 #include <lib/print.h>
 #include <lib/memmap.h>
@@ -135,7 +135,7 @@ void linux_load(char *cmdline, int boot_drive) {
     uint16_t real_mode_code_seg = rm_seg(real_mode_code);
     uint16_t kernel_entry_seg   = real_mode_code_seg + 0x20;
 
-    deinit_vga_textmode();
+    term_deinit();
 
     ASM(
         "cli\n\t"
diff --git a/src/protos/stivale.c b/src/protos/stivale.c
index ebf10fce..6bec7370 100644
--- a/src/protos/stivale.c
+++ b/src/protos/stivale.c
@@ -12,7 +12,7 @@
 #include <lib/rand.h>
 #include <lib/real.h>
 #include <drivers/vbe.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 #include <drivers/pic.h>
 #include <fs/file.h>
 #include <lib/asm.h>
@@ -224,6 +224,8 @@ void stivale_load(char *cmdline, int boot_drive) {
     stivale_struct.framebuffer_height = stivale_hdr.framebuffer_height;
     stivale_struct.framebuffer_bpp    = stivale_hdr.framebuffer_bpp;
 
+    term_deinit();
+
     if (stivale_hdr.flags & (1 << 0)) {
         uint32_t *fb32;
         init_vbe(&fb32,
@@ -232,8 +234,6 @@ void stivale_load(char *cmdline, int boot_drive) {
                  &stivale_struct.framebuffer_height,
                  &stivale_struct.framebuffer_bpp);
         stivale_struct.framebuffer_addr = (uint64_t)(size_t)fb32;
-    } else {
-        deinit_vga_textmode();
     }
 
     size_t memmap_entries;
@@ -241,6 +241,12 @@ void stivale_load(char *cmdline, int boot_drive) {
     stivale_struct.memory_map_entries = (uint64_t)memmap_entries;
     stivale_struct.memory_map_addr    = (uint64_t)(size_t)memmap;
 
+    stivale_spinup(bits, level5pg && (stivale_hdr.flags & (1 << 1)),
+                   entry_point, &stivale_struct, stivale_hdr.stack);
+}
+
+__attribute__((noreturn)) void stivale_spinup(int bits, bool level5pg,
+                 uint64_t entry_point, void *stivale_struct, uint64_t stack) {
     if (bits == 64) {
         // If we're going 64, we might as well call this BIOS interrupt
         // to tell the BIOS that we are entering Long Mode, since it is in
@@ -256,7 +262,7 @@ void stivale_load(char *cmdline, int boot_drive) {
 
     if (bits == 64) {
         void *pagemap_ptr;
-        if (level5pg && (stivale_hdr.flags & (1 << 1))) {
+        if (level5pg) {
             // Enable CR4.LA57
             ASM(
                 "mov eax, cr4\n\t"
@@ -376,7 +382,7 @@ void stivale_load(char *cmdline, int boot_drive) {
             "iretq\n\t"
             ".code32\n\t",
             : "a" (pagemap_ptr), "b" (&entry_point),
-              "D" (&stivale_struct), "S" (&stivale_hdr.stack)
+              "D" (stivale_struct), "S" (&stack)
             : "memory"
         );
     } else if (bits == 32) {
@@ -402,8 +408,9 @@ void stivale_load(char *cmdline, int boot_drive) {
             "xor ebp, ebp\n\t"
 
             "iret\n\t",
-            : "b" (&entry_point), "D" (&stivale_struct), "S" (&stivale_hdr.stack)
+            : "b" (&entry_point), "D" (stivale_struct), "S" (&stack)
             : "memory"
         );
     }
+    for (;;);
 }
diff --git a/src/protos/stivale.h b/src/protos/stivale.h
index 5c808917..5a6fdaa6 100644
--- a/src/protos/stivale.h
+++ b/src/protos/stivale.h
@@ -1,8 +1,11 @@
 #ifndef __PROTOS__STIVALE_H__
 #define __PROTOS__STIVALE_H__
 
-#include <fs/file.h>
+#include <stdbool.h>
+#include <stdint.h>
 
 void stivale_load(char *cmdline, int boot_drive);
+__attribute__((noreturn)) void stivale_spinup(int bits, bool level5pg,
+                 uint64_t entry_point, void *stivale_struct, uint64_t stack);
 
 #endif
diff --git a/src/protos/stivale2.c b/src/protos/stivale2.c
index b7a1fd46..d5a4d6f2 100644
--- a/src/protos/stivale2.c
+++ b/src/protos/stivale2.c
@@ -2,6 +2,7 @@
 #include <stddef.h>
 #include <stdbool.h>
 #include <limine.h>
+#include <protos/stivale.h>
 #include <protos/stivale2.h>
 #include <lib/elf.h>
 #include <lib/blib.h>
@@ -14,7 +15,7 @@
 #include <lib/real.h>
 #include <lib/libc.h>
 #include <drivers/vbe.h>
-#include <drivers/vga_textmode.h>
+#include <lib/term.h>
 #include <drivers/pic.h>
 #include <fs/file.h>
 #include <lib/asm.h>
@@ -364,9 +365,9 @@ void stivale2_load(char *cmdline, int boot_drive) {
     {
     struct stivale2_hdr_tag_framebuffer *hdrtag = get_tag(&stivale2_hdr, STIVALE2_HDR_TAG_FRAMEBUFFER_ID);
 
-    if (hdrtag == NULL) {
-        deinit_vga_textmode();
-    } else {
+    term_deinit();
+
+    if (hdrtag != NULL) {
         struct stivale2_struct_tag_framebuffer *tag = balloc(sizeof(struct stivale2_struct_tag_framebuffer));
         tag->tag.identifier = STIVALE2_STRUCT_TAG_FRAMEBUFFER_ID;
 
@@ -407,169 +408,6 @@ void stivale2_load(char *cmdline, int boot_drive) {
     // Check if 5-level paging tag is requesting support
     bool level5pg_requested = get_tag(&stivale2_hdr, STIVALE2_HDR_TAG_5LV_PAGING_ID) ? true : false;
 
-    if (bits == 64) {
-        // If we're going 64, we might as well call this BIOS interrupt
-        // to tell the BIOS that we are entering Long Mode, since it is in
-        // the specification.
-        struct rm_regs r = {0};
-        r.eax = 0xec00;
-        r.ebx = 0x02;   // Long mode only
-        rm_int(0x15, &r, &r);
-    }
-
-    pic_mask_all();
-    pic_flush();
-
-    if (bits == 64) {
-        void *pagemap_ptr;
-        if (level5pg && level5pg_requested) {
-            // Enable CR4.LA57
-            ASM(
-                "mov eax, cr4\n\t"
-                "bts eax, 12\n\t"
-                "mov cr4, eax\n\t", :: "eax", "memory"
-            );
-
-            struct pagemap {
-                uint64_t pml5[512];
-                uint64_t pml4_lo[512];
-                uint64_t pml4_hi[512];
-                uint64_t pml3_lo[512];
-                uint64_t pml3_hi[512];
-                uint64_t pml2_0gb[512];
-                uint64_t pml2_1gb[512];
-                uint64_t pml2_2gb[512];
-                uint64_t pml2_3gb[512];
-            };
-            struct pagemap *pagemap = balloc_aligned(sizeof(struct pagemap), 0x1000);
-            pagemap_ptr = (void *)pagemap;
-
-            // zero out the pagemap
-            for (uint64_t *p = (uint64_t *)pagemap; p < &pagemap->pml3_hi[512]; p++)
-                *p = 0;
-
-            pagemap->pml5[511]    = (uint64_t)(size_t)pagemap->pml4_hi  | 0x03;
-            pagemap->pml5[0]      = (uint64_t)(size_t)pagemap->pml4_lo  | 0x03;
-            pagemap->pml4_hi[511] = (uint64_t)(size_t)pagemap->pml3_hi  | 0x03;
-            pagemap->pml4_hi[256] = (uint64_t)(size_t)pagemap->pml3_lo  | 0x03;
-            pagemap->pml4_lo[0]   = (uint64_t)(size_t)pagemap->pml3_lo  | 0x03;
-            pagemap->pml3_hi[510] = (uint64_t)(size_t)pagemap->pml2_0gb | 0x03;
-            pagemap->pml3_hi[511] = (uint64_t)(size_t)pagemap->pml2_1gb | 0x03;
-            pagemap->pml3_lo[0]   = (uint64_t)(size_t)pagemap->pml2_0gb | 0x03;
-            pagemap->pml3_lo[1]   = (uint64_t)(size_t)pagemap->pml2_1gb | 0x03;
-            pagemap->pml3_lo[2]   = (uint64_t)(size_t)pagemap->pml2_2gb | 0x03;
-            pagemap->pml3_lo[3]   = (uint64_t)(size_t)pagemap->pml2_3gb | 0x03;
-
-            // populate the page directories
-            for (size_t i = 0; i < 512 * 4; i++)
-                (&pagemap->pml2_0gb[0])[i] = (i * 0x200000) | 0x03 | (1 << 7);
-        } else {
-            struct pagemap {
-                uint64_t pml4[512];
-                uint64_t pml3_lo[512];
-                uint64_t pml3_hi[512];
-                uint64_t pml2_0gb[512];
-                uint64_t pml2_1gb[512];
-                uint64_t pml2_2gb[512];
-                uint64_t pml2_3gb[512];
-            };
-            struct pagemap *pagemap = balloc_aligned(sizeof(struct pagemap), 0x1000);
-            pagemap_ptr = (void *)pagemap;
-
-            // zero out the pagemap
-            for (uint64_t *p = (uint64_t *)pagemap; p < &pagemap->pml3_hi[512]; p++)
-                *p = 0;
-
-            pagemap->pml4[511]    = (uint64_t)(size_t)pagemap->pml3_hi  | 0x03;
-            pagemap->pml4[256]    = (uint64_t)(size_t)pagemap->pml3_lo  | 0x03;
-            pagemap->pml4[0]      = (uint64_t)(size_t)pagemap->pml3_lo  | 0x03;
-            pagemap->pml3_hi[510] = (uint64_t)(size_t)pagemap->pml2_0gb | 0x03;
-            pagemap->pml3_hi[511] = (uint64_t)(size_t)pagemap->pml2_1gb | 0x03;
-            pagemap->pml3_lo[0]   = (uint64_t)(size_t)pagemap->pml2_0gb | 0x03;
-            pagemap->pml3_lo[1]   = (uint64_t)(size_t)pagemap->pml2_1gb | 0x03;
-            pagemap->pml3_lo[2]   = (uint64_t)(size_t)pagemap->pml2_2gb | 0x03;
-            pagemap->pml3_lo[3]   = (uint64_t)(size_t)pagemap->pml2_3gb | 0x03;
-
-            // populate the page directories
-            for (size_t i = 0; i < 512 * 4; i++)
-                (&pagemap->pml2_0gb[0])[i] = (i * 0x200000) | 0x03 | (1 << 7);
-        }
-
-        ASM(
-            "cli\n\t"
-            "cld\n\t"
-            "mov cr3, eax\n\t"
-            "mov eax, cr4\n\t"
-            "or eax, 1 << 5\n\t"
-            "mov cr4, eax\n\t"
-            "mov ecx, 0xc0000080\n\t"
-            "rdmsr\n\t"
-            "or eax, 1 << 8\n\t"
-            "wrmsr\n\t"
-            "mov eax, cr0\n\t"
-            "or eax, 1 << 31\n\t"
-            "mov cr0, eax\n\t"
-            FARJMP32("0x28", "1f")
-            "1: .code64\n\t"
-            "mov ax, 0x30\n\t"
-            "mov ds, ax\n\t"
-            "mov es, ax\n\t"
-            "mov fs, ax\n\t"
-            "mov gs, ax\n\t"
-            "mov ss, ax\n\t"
-
-            "push 0x30\n\t"
-            "push [rsi]\n\t"
-            "pushfq\n\t"
-            "push 0x28\n\t"
-            "push [rbx]\n\t"
-
-            "xor rax, rax\n\t"
-            "xor rbx, rbx\n\t"
-            "xor rcx, rcx\n\t"
-            "xor rdx, rdx\n\t"
-            "xor rsi, rsi\n\t"
-            "xor rbp, rbp\n\t"
-            "xor r8,  r8\n\t"
-            "xor r9,  r9\n\t"
-            "xor r10, r10\n\t"
-            "xor r11, r11\n\t"
-            "xor r12, r12\n\t"
-            "xor r13, r13\n\t"
-            "xor r14, r14\n\t"
-            "xor r15, r15\n\t"
-
-            "iretq\n\t"
-            ".code32\n\t",
-            : "a" (pagemap_ptr), "b" (&entry_point),
-              "D" (&stivale2_struct), "S" (&stivale2_hdr.stack)
-            : "memory"
-        );
-    } else if (bits == 32) {
-        ASM(
-            "cli\n\t"
-            "cld\n\t"
-
-            "sub esp, 4\n\t"
-            "mov [esp], edi\n\t"
-
-            "push 0x20\n\t"
-            "push [esi]\n\t"
-            "pushfd\n\t"
-            "push 0x18\n\t"
-            "push [ebx]\n\t"
-
-            "xor eax, eax\n\t"
-            "xor ebx, ebx\n\t"
-            "xor ecx, ecx\n\t"
-            "xor edx, edx\n\t"
-            "xor esi, esi\n\t"
-            "xor edi, edi\n\t"
-            "xor ebp, ebp\n\t"
-
-            "iret\n\t",
-            : "b" (&entry_point), "D" (&stivale2_struct), "S" (&stivale2_hdr.stack)
-            : "memory"
-        );
-    }
+    stivale_spinup(bits, level5pg && level5pg_requested,
+                   entry_point, &stivale2_struct, stivale2_hdr.stack);
 }
diff --git a/test/limine.cfg b/test/limine.cfg
index 2492b4d2..8f70a2f0 100644
--- a/test/limine.cfg
+++ b/test/limine.cfg
@@ -1,4 +1,5 @@
 TIMEOUT=3
+GRAPHICS=on
 
 :MyOS
 
tab: 248 wrap: offon