:: commit ad8151a6a61bd852d213d0e40d34324a5a53d465

mintsuki <mintsuki@protonmail.com> — 2022-10-03 22:58

parents: fc6e44c82c

term: Move to use external, portable terminal

diff --git a/.gitignore b/.gitignore
index 45fc17a3..40d5f11d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 /limine-efi
 /freestanding-headers
 /libgcc-binaries
+/common/term
 /ovmf*
 *.o
 *.d
diff --git a/GNUmakefile.in b/GNUmakefile.in
index 1ea0413d..830f18b7 100644
--- a/GNUmakefile.in
+++ b/GNUmakefile.in
@@ -251,6 +251,7 @@ dist:
 	cp -r '$(call SHESCAPE,$(SRCDIR))'/.git '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)"/
 	cd '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)" && git checkout .
 	cd '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)" && ./bootstrap
+	rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/common/term/.git"
 	rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/freestanding-headers/.git"
 	rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/libgcc-binaries/.git"
 	rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/libgcc-binaries/.gitignore"
@@ -287,7 +288,7 @@ distclean: clean
 
 .PHONY: maintainer-clean
 maintainer-clean: distclean
-	cd '$(call SHESCAPE,$(SRCDIR))' && rm -rf freestanding-headers libgcc-binaries limine-efi cross-detect configure build-aux *'~' autom4te.cache *.tar.xz *.tar.gz
+	cd '$(call SHESCAPE,$(SRCDIR))' && rm -rf common/term freestanding-headers libgcc-binaries limine-efi cross-detect configure build-aux *'~' autom4te.cache *.tar.xz *.tar.gz
 
 .PHONY: common-uefi-x86-64
 common-uefi-x86-64:
diff --git a/bootstrap b/bootstrap
index 2376e6f6..f440d36a 100755
--- a/bootstrap
+++ b/bootstrap
@@ -7,6 +7,7 @@ test -z "$srcdir" && srcdir=.
 
 cd "$srcdir"
 
+[ -d common/term ] || git clone https://github.com/limine-bootloader/terminal.git common/term
 [ -d cross-detect ] || git clone https://github.com/mintsuki/cross-detect.git
 [ -d freestanding-headers ] || git clone https://github.com/mintsuki/freestanding-headers.git
 [ -d limine-efi ] || git clone https://github.com/limine-bootloader/limine-efi.git
diff --git a/common/drivers/vga_textmode.c b/common/drivers/vga_textmode.c
index 9ed7da42..d5da701c 100644
--- a/common/drivers/vga_textmode.c
+++ b/common/drivers/vga_textmode.c
@@ -15,149 +15,99 @@
 #define VD_COLS (80 * 2)
 #define VD_ROWS 25
 
-static volatile uint8_t *video_mem = (uint8_t *)0xb8000;
-
-static uint8_t *back_buffer = NULL;
-static uint8_t *front_buffer = NULL;
-
-static struct context {
-    size_t cursor_offset;
-#define cursor_offset context.cursor_offset
-    bool cursor_status;
-#define cursor_status context.cursor_status
-    uint8_t text_palette;
-#define text_palette context.text_palette
-    bool scroll_enabled;
-#define scroll_enabled context.scroll_enabled
-
-    uint8_t saved_state_text_palette;
-#define saved_state_text_palette context.saved_state_text_palette
-    size_t saved_state_cursor_offset;
-#define saved_state_cursor_offset context.saved_state_cursor_offset
-} context;
-
-static size_t old_cursor_offset = 0;
-
-static void draw_cursor(void) {
-    uint8_t pal = back_buffer[cursor_offset + 1];
-    video_mem[cursor_offset + 1] = ((pal & 0xf0) >> 4) | ((pal & 0x0f) << 4);
+static void draw_cursor(struct textmode_context *ctx) {
+    uint8_t pal = ctx->back_buffer[ctx->cursor_offset + 1];
+    ctx->video_mem[ctx->cursor_offset + 1] = ((pal & 0xf0) >> 4) | ((pal & 0x0f) << 4);
 }
 
-void text_save_state(void) {
-    saved_state_text_palette = text_palette;
-    saved_state_cursor_offset = cursor_offset;
+void text_save_state(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->saved_state_text_palette = ctx->text_palette;
+    ctx->saved_state_cursor_offset = ctx->cursor_offset;
 }
 
-void text_restore_state(void) {
-    text_palette = saved_state_text_palette;
-    cursor_offset = saved_state_cursor_offset;
+void text_restore_state(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = ctx->saved_state_text_palette;
+    ctx->cursor_offset = ctx->saved_state_cursor_offset;
 }
 
-void text_swap_palette(void) {
-    text_palette = (text_palette << 4) | (text_palette >> 4);
+void text_swap_palette(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = (ctx->text_palette << 4) | (ctx->text_palette >> 4);
 }
 
-bool text_scroll_disable(void) {
-    bool ret = scroll_enabled;
-    scroll_enabled = false;
-    return ret;
-}
-
-void text_scroll_enable(void) {
-    scroll_enabled = true;
-}
+void text_scroll(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
 
-void text_scroll(void) {
     // move the text up by one row
-    for (size_t i = term_context.scroll_top_margin * VD_COLS;
-         i < (term_context.scroll_bottom_margin - 1) * VD_COLS; i++) {
-        back_buffer[i] = back_buffer[i + VD_COLS];
+    for (size_t i = _ctx->scroll_top_margin * VD_COLS;
+         i < (_ctx->scroll_bottom_margin - 1) * VD_COLS; i++) {
+        ctx->back_buffer[i] = ctx->back_buffer[i + VD_COLS];
     }
     // clear the last line of the screen
-    for (size_t i = (term_context.scroll_bottom_margin - 1) * VD_COLS;
-         i < term_context.scroll_bottom_margin * VD_COLS; i += 2) {
-        back_buffer[i] = ' ';
-        back_buffer[i + 1] = text_palette;
+    for (size_t i = (_ctx->scroll_bottom_margin - 1) * VD_COLS;
+         i < _ctx->scroll_bottom_margin * VD_COLS; i += 2) {
+        ctx->back_buffer[i] = ' ';
+        ctx->back_buffer[i + 1] = ctx->text_palette;
     }
 }
 
-void text_revscroll(void) {
+void text_revscroll(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+
     // move the text up by one row
-    for (size_t i = (term_context.scroll_bottom_margin - 1) * VD_COLS - 2; ; i--) {
-        back_buffer[i + VD_COLS] = back_buffer[i];
-        if (i == term_context.scroll_top_margin * VD_COLS) {
+    for (size_t i = (_ctx->scroll_bottom_margin - 1) * VD_COLS - 2; ; i--) {
+        ctx->back_buffer[i + VD_COLS] = ctx->back_buffer[i];
+        if (i == _ctx->scroll_top_margin * VD_COLS) {
             break;
         }
     }
     // clear the first line of the screen
-    for (size_t i = term_context.scroll_top_margin * VD_COLS;
-         i < (term_context.scroll_top_margin + 1) * VD_COLS; i += 2) {
-        back_buffer[i] = ' ';
-        back_buffer[i + 1] = text_palette;
+    for (size_t i = _ctx->scroll_top_margin * VD_COLS;
+         i < (_ctx->scroll_top_margin + 1) * VD_COLS; i += 2) {
+        ctx->back_buffer[i] = ' ';
+        ctx->back_buffer[i + 1] = ctx->text_palette;
     }
 }
 
-void text_clear(bool move) {
+void text_clear(struct term_context *_ctx, bool move) {
+    struct textmode_context *ctx = (void *)_ctx;
+
     for (size_t i = 0; i < VIDEO_BOTTOM; i += 2) {
-        back_buffer[i] = ' ';
-        back_buffer[i + 1] = text_palette;
+        ctx->back_buffer[i] = ' ';
+        ctx->back_buffer[i + 1] = ctx->text_palette;
+    }
+    if (move) {
+        ctx->cursor_offset = 0;
     }
-    if (move)
-        cursor_offset = 0;
 }
 
-void text_enable_cursor(void) {
-    cursor_status = true;
-}
+void text_enable_cursor(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
 
-bool text_disable_cursor(void) {
-    bool ret = cursor_status;
-    cursor_status = false;
-    return ret;
+    ctx->cursor_status = true;
 }
 
-uint64_t text_context_size(void) {
-    uint64_t ret = 0;
-
-    ret += sizeof(struct context);
-    ret += VD_ROWS * VD_COLS; // front buffer
+bool text_disable_cursor(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
 
+    bool ret = ctx->cursor_status;
+    ctx->cursor_status = false;
     return ret;
 }
 
-void text_context_save(uint64_t ptr) {
-    memcpy32to64(ptr, (uint64_t)(uintptr_t)&context, sizeof(struct context));
-    ptr += sizeof(struct context);
-
-    memcpy32to64(ptr, (uint64_t)(uintptr_t)front_buffer, VD_ROWS * VD_COLS);
-}
-
-void text_context_restore(uint64_t ptr) {
-    memcpy32to64((uint64_t)(uintptr_t)&context, ptr, sizeof(struct context));
-    ptr += sizeof(struct context);
-
-    memcpy32to64((uint64_t)(uintptr_t)front_buffer, ptr, VD_ROWS * VD_COLS);
+void text_full_refresh(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
 
     for (size_t i = 0; i < VD_ROWS * VD_COLS; i++) {
-        video_mem[i] = front_buffer[i];
-        back_buffer[i] = front_buffer[i];
+        ctx->video_mem[i] = ctx->front_buffer[i];
+        ctx->back_buffer[i] = ctx->front_buffer[i];
     }
 
-    if (cursor_status) {
-        draw_cursor();
-        old_cursor_offset = cursor_offset;
-    }
-}
-
-void text_full_refresh(void) {
-    for (size_t i = 0; i < VD_ROWS * VD_COLS; i++) {
-        video_mem[i] = front_buffer[i];
-        back_buffer[i] = front_buffer[i];
-    }
-
-    if (cursor_status) {
-        draw_cursor();
-        old_cursor_offset = cursor_offset;
+    if (ctx->cursor_status) {
+        draw_cursor(ctx);
+        ctx->old_cursor_offset = ctx->cursor_offset;
     }
 }
 
@@ -170,23 +120,26 @@ void init_vga_textmode(size_t *_rows, size_t *_cols, bool managed) {
         current_video_mode = 0x3;
     }
 
-    if (back_buffer == NULL) {
-        back_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
+    struct textmode_context *ctx = (void *)term;
+
+    if (ctx->back_buffer == NULL) {
+        ctx->back_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
     } else {
-        memset(back_buffer, 0, VD_ROWS * VD_COLS);
+        memset(ctx->back_buffer, 0, VD_ROWS * VD_COLS);
     }
-    if (front_buffer == NULL) {
-        front_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
+    if (ctx->front_buffer == NULL) {
+        ctx->front_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
     } else {
-        memset(front_buffer, 0, VD_ROWS * VD_COLS);
+        memset(ctx->front_buffer, 0, VD_ROWS * VD_COLS);
     }
 
-    cursor_offset = 0;
-    cursor_status = true;
-    text_palette = 0x07;
-    scroll_enabled = true;
+    ctx->cursor_offset = 0;
+    ctx->cursor_status = true;
+    ctx->text_palette = 0x07;
 
-    text_clear(false);
+    ctx->video_mem = (volatile uint8_t *)0xb8000;
+
+    text_clear(term, false);
 
     *_rows = VD_ROWS;
     *_cols = VD_COLS / 2;
@@ -194,7 +147,7 @@ void init_vga_textmode(size_t *_rows, size_t *_cols, bool managed) {
     // VGA cursor code taken from: https://wiki.osdev.org/Text_Mode_Cursor
 
     if (!managed) {
-        text_disable_cursor();
+        text_disable_cursor(term);
 
         outb(0x3d4, 0x0a);
         outb(0x3d5, (inb(0x3d5) & 0xc0) | 14);
@@ -213,51 +166,59 @@ void init_vga_textmode(size_t *_rows, size_t *_cols, bool managed) {
         outb(0x3d5, 0x20);
     }
 
-    text_double_buffer_flush();
+    text_double_buffer_flush(term);
 }
 
-void text_double_buffer_flush(void) {
-    if (cursor_status) {
-        draw_cursor();
+void text_double_buffer_flush(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+
+    if (ctx->cursor_status) {
+        draw_cursor(ctx);
     }
 
-    if (cursor_offset != old_cursor_offset || cursor_status == false) {
-        video_mem[old_cursor_offset + 1] = back_buffer[old_cursor_offset + 1];
+    if (ctx->cursor_offset != ctx->old_cursor_offset || ctx->cursor_status == false) {
+        ctx->video_mem[ctx->old_cursor_offset + 1] = ctx->back_buffer[ctx->old_cursor_offset + 1];
     }
 
     for (size_t i = 0; i < VD_ROWS * VD_COLS; i++) {
-        if (back_buffer[i] == front_buffer[i]) {
+        if (ctx->back_buffer[i] == ctx->front_buffer[i]) {
             continue;
         }
 
-        if (cursor_status && i == cursor_offset + 1) {
+        if (ctx->cursor_status && i == ctx->cursor_offset + 1) {
             continue;
         }
 
-        front_buffer[i] = back_buffer[i];
-        video_mem[i]    = back_buffer[i];
+        ctx->front_buffer[i] = ctx->back_buffer[i];
+        ctx->video_mem[i]    = ctx->back_buffer[i];
     }
 
-    if (cursor_status) {
-        old_cursor_offset = cursor_offset;
+    if (ctx->cursor_status) {
+        ctx->old_cursor_offset = ctx->cursor_offset;
     }
 }
 
-void text_get_cursor_pos(size_t *x, size_t *y) {
-    *x = (cursor_offset % VD_COLS) / 2;
-    *y = cursor_offset / VD_COLS;
+void text_get_cursor_pos(struct term_context *_ctx, size_t *x, size_t *y) {
+    struct textmode_context *ctx = (void *)_ctx;
+
+    *x = (ctx->cursor_offset % VD_COLS) / 2;
+    *y = ctx->cursor_offset / VD_COLS;
 }
 
-void text_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y) {
+void text_move_character(struct term_context *_ctx, size_t new_x, size_t new_y, size_t old_x, size_t old_y) {
+    struct textmode_context *ctx = (void *)_ctx;
+
     if (old_x >= VD_COLS / 2 || old_y >= VD_ROWS
      || new_x >= VD_COLS / 2 || new_y >= VD_ROWS) {
         return;
     }
 
-    back_buffer[new_y * VD_COLS + new_x * 2] = back_buffer[old_y * VD_COLS + old_x * 2];
+    ctx->back_buffer[new_y * VD_COLS + new_x * 2] = ctx->back_buffer[old_y * VD_COLS + old_x * 2];
 }
 
-void text_set_cursor_pos(size_t x, size_t y) {
+void text_set_cursor_pos(struct term_context *_ctx, size_t x, size_t y) {
+    struct textmode_context *ctx = (void *)_ctx;
+
     if (x >= VD_COLS / 2) {
         if ((int)x < 0) {
             x = 0;
@@ -272,48 +233,56 @@ void text_set_cursor_pos(size_t x, size_t y) {
             y = VD_ROWS - 1;
         }
     }
-    cursor_offset = y * VD_COLS + x * 2;
+    ctx->cursor_offset = y * VD_COLS + x * 2;
 }
 
 static uint8_t ansi_colours[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
 
-void text_set_text_fg(size_t fg) {
-    text_palette = (text_palette & 0xf0) | ansi_colours[fg];
+void text_set_text_fg(struct term_context *_ctx, size_t fg) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = (ctx->text_palette & 0xf0) | ansi_colours[fg];
 }
 
-void text_set_text_bg(size_t bg) {
-    text_palette = (text_palette & 0x0f) | (ansi_colours[bg] << 4);
+void text_set_text_bg(struct term_context *_ctx, size_t bg) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = (ctx->text_palette & 0x0f) | (ansi_colours[bg] << 4);
 }
 
-void text_set_text_fg_bright(size_t fg) {
-    text_palette = (text_palette & 0xf0) | (ansi_colours[fg] | (1 << 3));
+void text_set_text_fg_bright(struct term_context *_ctx, size_t fg) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = (ctx->text_palette & 0xf0) | (ansi_colours[fg] | (1 << 3));
 }
 
-void text_set_text_bg_bright(size_t bg) {
-    text_palette = (text_palette & 0x0f) | ((ansi_colours[bg] | (1 << 3)) << 4);
+void text_set_text_bg_bright(struct term_context *_ctx, size_t bg) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = (ctx->text_palette & 0x0f) | ((ansi_colours[bg] | (1 << 3)) << 4);
 }
 
-void text_set_text_fg_default(void) {
-    text_palette = (text_palette & 0xf0) | 7;
+void text_set_text_fg_default(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette = (ctx->text_palette & 0xf0) | 7;
 }
 
-void text_set_text_bg_default(void) {
-    text_palette &= 0x0f;
+void text_set_text_bg_default(struct term_context *_ctx) {
+    struct textmode_context *ctx = (void *)_ctx;
+    ctx->text_palette &= 0x0f;
 }
 
-void text_putchar(uint8_t c) {
-    back_buffer[cursor_offset] = c;
-    back_buffer[cursor_offset + 1] = text_palette;
-    if (cursor_offset / VD_COLS == term_context.scroll_bottom_margin - 1
-     && cursor_offset % VD_COLS == VD_COLS - 2) {
-        if (scroll_enabled) {
-            text_scroll();
-            cursor_offset -= cursor_offset % VD_COLS;
+void text_putchar(struct term_context *_ctx, uint8_t c) {
+    struct textmode_context *ctx = (void *)_ctx;
+
+    ctx->back_buffer[ctx->cursor_offset] = c;
+    ctx->back_buffer[ctx->cursor_offset + 1] = ctx->text_palette;
+    if (ctx->cursor_offset / VD_COLS == _ctx->scroll_bottom_margin - 1
+     && ctx->cursor_offset % VD_COLS == VD_COLS - 2) {
+        if (_ctx->scroll_enabled) {
+            text_scroll(_ctx);
+            ctx->cursor_offset -= ctx->cursor_offset % VD_COLS;
         }
-    } else if (cursor_offset >= (VIDEO_BOTTOM - 1)) {
-        cursor_offset -= cursor_offset % VD_COLS;
+    } else if (ctx->cursor_offset >= (VIDEO_BOTTOM - 1)) {
+        ctx->cursor_offset -= ctx->cursor_offset % VD_COLS;
     } else {
-        cursor_offset += 2;
+        ctx->cursor_offset += 2;
     }
 }
 
diff --git a/common/drivers/vga_textmode.h b/common/drivers/vga_textmode.h
index 1c76c357..5d165c82 100644
--- a/common/drivers/vga_textmode.h
+++ b/common/drivers/vga_textmode.h
@@ -4,36 +4,48 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stddef.h>
+#include <term/term.h>
+
+struct textmode_context {
+    struct term_context term;
+
+    volatile uint8_t *video_mem;
+
+    uint8_t *back_buffer;
+    uint8_t *front_buffer;
+
+    size_t cursor_offset;
+    size_t old_cursor_offset;
+    bool cursor_status;
+    uint8_t text_palette;
+
+    uint8_t saved_state_text_palette;
+    size_t saved_state_cursor_offset;
+};
 
 void init_vga_textmode(size_t *rows, size_t *cols, bool managed);
 
-void text_putchar(uint8_t c);
-void text_clear(bool move);
-void text_enable_cursor(void);
-bool text_disable_cursor(void);
-void text_set_cursor_pos(size_t x, size_t y);
-void text_get_cursor_pos(size_t *x, size_t *y);
-void text_set_text_fg(size_t fg);
-void text_set_text_bg(size_t bg);
-void text_set_text_fg_bright(size_t fg);
-void text_set_text_bg_bright(size_t bg);
-void text_set_text_fg_default(void);
-void text_set_text_bg_default(void);
-bool text_scroll_disable(void);
-void text_scroll_enable(void);
-void text_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
-void text_scroll(void);
-void text_revscroll(void);
-void text_swap_palette(void);
-void text_save_state(void);
-void text_restore_state(void);
-
-void text_double_buffer(bool state);
-void text_double_buffer_flush(void);
-
-uint64_t text_context_size(void);
-void text_context_save(uint64_t ptr);
-void text_context_restore(uint64_t ptr);
-void text_full_refresh(void);
+void text_putchar(struct term_context *ctx, uint8_t c);
+void text_clear(struct term_context *ctx, bool move);
+void text_enable_cursor(struct term_context *ctx);
+bool text_disable_cursor(struct term_context *ctx);
+void text_set_cursor_pos(struct term_context *ctx, size_t x, size_t y);
+void text_get_cursor_pos(struct term_context *ctx, size_t *x, size_t *y);
+void text_set_text_fg(struct term_context *ctx, size_t fg);
+void text_set_text_bg(struct term_context *ctx, size_t bg);
+void text_set_text_fg_bright(struct term_context *ctx, size_t fg);
+void text_set_text_bg_bright(struct term_context *ctx, size_t bg);
+void text_set_text_fg_default(struct term_context *ctx);
+void text_set_text_bg_default(struct term_context *ctx);
+bool text_scroll_disable(struct term_context *ctx);
+void text_scroll_enable(struct term_context *ctx);
+void text_move_character(struct term_context *ctx, size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+void text_scroll(struct term_context *ctx);
+void text_revscroll(struct term_context *ctx);
+void text_swap_palette(struct term_context *ctx);
+void text_save_state(struct term_context *ctx);
+void text_restore_state(struct term_context *ctx);
+void text_double_buffer_flush(struct term_context *ctx);
+void text_full_refresh(struct term_context *ctx);
 
 #endif
diff --git a/common/lib/gterm.c b/common/lib/gterm.c
index 386248ab..8f250c1f 100644
--- a/common/lib/gterm.c
+++ b/common/lib/gterm.c
@@ -1,7 +1,6 @@
 #include <stdint.h>
 #include <stddef.h>
 #include <lib/gterm.h>
-#include <lib/term.h>
 #include <lib/misc.h>
 #include <lib/libc.h>
 #include <lib/config.h>
@@ -9,121 +8,23 @@
 #include <lib/uri.h>
 #include <lib/fb.h>
 #include <mm/pmm.h>
-
-// Maximum allowed font size in bytes. 16kB should be enough as 9x32 is the
-// largest font I've seen, and that would take 9*32 * 256 * 1/8 byte =
-// 9216 bytes.
-#define VGA_FONT_MAX 16384
-#define VGA_FONT_GLYPHS 256
-
-#define DEFAULT_FONT_WIDTH 8
-#define DEFAULT_FONT_HEIGHT 16
-
-static size_t vga_font_width;
-static size_t vga_font_height;
-static size_t glyph_width = 8;
-static size_t glyph_height = 16;
-
-static size_t vga_font_scale_x = 1;
-static size_t vga_font_scale_y = 1;
-
-static size_t offset_x, offset_y;
+#include <term/term.h>
+#include <term/backends/framebuffer.h>
+#include <lib/term.h>
 
 struct fb_info fbinfo;
-static volatile uint32_t *gterm_framebuffer;
-static uint16_t  gterm_pitch;
-static uint16_t  gterm_width;
-static uint16_t  gterm_height;
-static uint16_t  gterm_bpp;
-
-extern symbol _binary_font_bin_start;
-
-static uint8_t *vga_font_bits = NULL;
-static size_t vga_font_bool_size = 0;
-static bool *vga_font_bool = NULL;
 
-static uint32_t ansi_colours[8];
-static uint32_t ansi_bright_colours[8];
-static uint32_t default_fg, default_bg;
+extern symbol _binary_font_bin_start, _binary_font_bin_size;
 
 static struct image *background;
 
-static size_t bg_canvas_size = 0;
-static uint32_t *bg_canvas = NULL;
-
-static size_t rows;
-static size_t cols;
-static size_t margin;
-static size_t margin_gradient;
-
-static size_t grid_size = 0;
-static size_t queue_size = 0;
-static size_t map_size = 0;
-
-struct gterm_char {
-    uint32_t c;
-    uint32_t fg;
-    uint32_t bg;
-};
-
-static struct gterm_char *grid = NULL;
-
-struct queue_item {
-    size_t x, y;
-    struct gterm_char c;
-};
-
-static struct queue_item *queue = NULL;
-static size_t queue_i = 0;
-
-static struct queue_item **map = NULL;
-
-static struct context {
-    uint32_t text_fg;
-#define text_fg context.text_fg
-    uint32_t text_bg;
-#define text_bg context.text_bg
-    bool cursor_status;
-#define cursor_status context.cursor_status
-    size_t cursor_x;
-#define cursor_x context.cursor_x
-    size_t cursor_y;
-#define cursor_y context.cursor_y
-    bool scroll_enabled;
-#define scroll_enabled context.scroll_enabled
-
-    uint32_t saved_state_text_fg;
-#define saved_state_text_fg context.saved_state_text_fg
-    uint32_t saved_state_text_bg;
-#define saved_state_text_bg context.saved_state_text_bg
-    size_t saved_state_cursor_x;
-#define saved_state_cursor_x context.saved_state_cursor_x
-    size_t saved_state_cursor_y;
-#define saved_state_cursor_y context.saved_state_cursor_y
-} context;
-
-static size_t old_cursor_x = 0;
-static size_t old_cursor_y = 0;
-
-void gterm_save_state(void) {
-    saved_state_text_fg = text_fg;
-    saved_state_text_bg = text_bg;
-    saved_state_cursor_x = cursor_x;
-    saved_state_cursor_y = cursor_y;
-}
+static size_t margin = 64;
+static size_t margin_gradient = 4;
 
-void gterm_restore_state(void) {
-    text_fg = saved_state_text_fg;
-    text_bg = saved_state_text_bg;
-    cursor_x = saved_state_cursor_x;
-    cursor_y = saved_state_cursor_y;
-}
+static uint32_t default_bg, default_fg;
 
-void gterm_swap_palette(void) {
-    uint32_t tmp = text_bg;
-    text_bg = text_fg;
-    text_fg = tmp;
-}
+static size_t bg_canvas_size;
+static uint32_t *bg_canvas;
 
 #define A(rgb) (uint8_t)(rgb >> 24)
 #define R(rgb) (uint8_t)(rgb >> 16)
@@ -142,20 +43,10 @@ static inline uint32_t colour_blend(uint32_t fg, uint32_t bg) {
     return ARGB(0, r, g, b);
 }
 
-static inline void gterm_plot_px(size_t x, size_t y, uint32_t hex) {
-    if (x >= gterm_width || y >= gterm_height) {
-        return;
-    }
-
-    size_t fb_i = x + (gterm_pitch / sizeof(uint32_t)) * y;
-
-    gterm_framebuffer[fb_i] = hex;
-}
-
 static uint32_t blend_gradient_from_box(size_t x, size_t y, uint32_t bg_px, uint32_t hex) {
     size_t distance, x_distance, y_distance;
-    size_t gradient_stop_x = gterm_width - margin;
-    size_t gradient_stop_y = gterm_height - margin;
+    size_t gradient_stop_x = fbinfo.framebuffer_width - margin;
+    size_t gradient_stop_y = fbinfo.framebuffer_height - margin;
 
     if (x < margin)
         x_distance = margin - x;
@@ -199,11 +90,11 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
         for (size_t y = ystart; y < yend; y++) {
             size_t image_y = y % img_height, image_x = xstart % img_width;
             const size_t off = img_pitch * (img_height - 1 - image_y);
-            size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
+            size_t canvas_off = fbinfo.framebuffer_width * y;
             for (size_t x = xstart; x < xend; x++) {
                 uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off);
                 uint32_t i = blend(x, y, img_pixel);
-                bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
+                bg_canvas[canvas_off + x] = i;
                 if (image_x++ == img_width) image_x = 0; // image_x = x % img_width, but modulo is too expensive
             }
         }
@@ -213,11 +104,11 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
         for (size_t y = ystart; y < yend; y++) {
             size_t image_y = y - background->y_displacement;
             const size_t off = img_pitch * (img_height - 1 - image_y);
-            size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
+            size_t canvas_off = fbinfo.framebuffer_width * y;
             if (image_y >= background->y_size) { /* external part */
                 for (size_t x = xstart; x < xend; x++) {
                     uint32_t i = blend(x, y, background->back_colour);
-                    bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
+                    bg_canvas[canvas_off + x] = i;
                 }
             }
             else { /* internal part */
@@ -226,7 +117,7 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
                     bool x_external = image_x >= background->x_size;
                     uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off);
                     uint32_t i = blend(x, y, x_external ? background->back_colour : img_pixel);
-                    bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
+                    bg_canvas[canvas_off + x] = i;
                 }
             }
         }
@@ -236,16 +127,16 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
     // so you can set x = xstart * ratio, and increment by ratio at each iteration
     case IMAGE_STRETCHED:
         for (size_t y = ystart; y < yend; y++) {
-            size_t img_y = (y * img_height) / gterm_height; // calculate Y with full precision
+            size_t img_y = (y * img_height) / fbinfo.framebuffer_height; // calculate Y with full precision
             size_t off = img_pitch * (img_height - 1 - img_y);
-            size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
+            size_t canvas_off = fbinfo.framebuffer_width * y;
 
-            size_t ratio = int_to_fixedp6(img_width) / gterm_width;
+            size_t ratio = int_to_fixedp6(img_width) / fbinfo.framebuffer_width;
             fixedp6 img_x = ratio * xstart;
             for (size_t x = xstart; x < xend; x++) {
                 uint32_t img_pixel = *(uint32_t*)(img + fixedp6_to_int(img_x) * colsize + off);
                 uint32_t i = blend(x, y, img_pixel);
-                bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
+                bg_canvas[canvas_off + x] = i;
                 img_x += ratio;
             }
         }
@@ -261,24 +152,27 @@ static void loop_external(size_t xstart, size_t xend, size_t ystart, size_t yend
 static void loop_margin(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_margin); }
 static void loop_internal(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_internal); }
 
-static void gterm_generate_canvas(void) {
+static void *generate_canvas(void) {
     if (background) {
+        bg_canvas_size = fbinfo.framebuffer_width * fbinfo.framebuffer_height * sizeof(uint32_t);
+        bg_canvas = ext_mem_alloc(bg_canvas_size);
+
         int64_t margin_no_gradient = (int64_t)margin - margin_gradient;
 
         if (margin_no_gradient < 0) {
             margin_no_gradient = 0;
         }
 
-        size_t scan_stop_x = gterm_width - margin_no_gradient;
-        size_t scan_stop_y = gterm_height - margin_no_gradient;
+        size_t scan_stop_x = fbinfo.framebuffer_width - margin_no_gradient;
+        size_t scan_stop_y = fbinfo.framebuffer_height - margin_no_gradient;
 
-        loop_external(0, gterm_width, 0, margin_no_gradient);
-        loop_external(0, gterm_width, scan_stop_y, gterm_height);
+        loop_external(0, fbinfo.framebuffer_width, 0, margin_no_gradient);
+        loop_external(0, fbinfo.framebuffer_width, scan_stop_y, fbinfo.framebuffer_height);
         loop_external(0, margin_no_gradient, margin_no_gradient, scan_stop_y);
-        loop_external(scan_stop_x, gterm_width, margin_no_gradient, scan_stop_y);
+        loop_external(scan_stop_x, fbinfo.framebuffer_width, margin_no_gradient, scan_stop_y);
 
-        size_t gradient_stop_x = gterm_width - margin;
-        size_t gradient_stop_y = gterm_height - margin;
+        size_t gradient_stop_x = fbinfo.framebuffer_width - margin;
+        size_t gradient_stop_y = fbinfo.framebuffer_height - margin;
 
         if (margin_gradient) {
             loop_margin(margin_no_gradient, scan_stop_x, margin_no_gradient, margin);
@@ -288,327 +182,17 @@ static void gterm_generate_canvas(void) {
         }
 
         loop_internal(margin, gradient_stop_x, margin, gradient_stop_y);
-    } else {
-        for (size_t y = 0; y < gterm_height; y++) {
-            for (size_t x = 0; x < gterm_width; x++) {
-                bg_canvas[y * gterm_width + x] = default_bg;
-                gterm_plot_px(x, y, default_bg);
-            }
-        }
-    }
-}
-
-static void plot_char(struct gterm_char *c, size_t x, size_t y) {
-    if (x >= cols || y >= rows) {
-        return;
-    }
-
-    x = offset_x + x * glyph_width;
-    y = offset_y + y * glyph_height;
-
-    bool *glyph = &vga_font_bool[c->c * vga_font_height * vga_font_width];
-    // naming: fx,fy for font coordinates, gx,gy for glyph coordinates
-    for (size_t gy = 0; gy < glyph_height; gy++) {
-        uint8_t fy = gy / vga_font_scale_y;
-        volatile uint32_t *fb_line = gterm_framebuffer + x + (y + gy) * (gterm_pitch / 4);
-        uint32_t *canvas_line = bg_canvas + x + (y + gy) * gterm_width;
-        for (size_t fx = 0; fx < vga_font_width; fx++) {
-            bool draw = glyph[fy * vga_font_width + fx];
-            for (size_t i = 0; i < vga_font_scale_x; i++) {
-                size_t gx = vga_font_scale_x * fx + i;
-                uint32_t bg = c->bg == 0xffffffff ? canvas_line[gx] : c->bg;
-                uint32_t fg = c->fg == 0xffffffff ? canvas_line[gx] : c->fg;
-                fb_line[gx] = draw ? fg : bg;
-            }
-        }
-    }
-}
-
-static void plot_char_fast(struct gterm_char *old, struct gterm_char *c, size_t x, size_t y) {
-    if (x >= cols || y >= rows) {
-        return;
-    }
-
-    x = offset_x + x * glyph_width;
-    y = offset_y + y * glyph_height;
-
-    bool *new_glyph = &vga_font_bool[c->c * vga_font_height * vga_font_width];
-    bool *old_glyph = &vga_font_bool[old->c * vga_font_height * vga_font_width];
-    for (size_t gy = 0; gy < glyph_height; gy++) {
-        uint8_t fy = gy / vga_font_scale_y;
-        volatile uint32_t *fb_line = gterm_framebuffer + x + (y + gy) * (gterm_pitch / 4);
-        uint32_t *canvas_line = bg_canvas + x + (y + gy) * gterm_width;
-        for (size_t fx = 0; fx < vga_font_width; fx++) {
-            bool old_draw = old_glyph[fy * vga_font_width + fx];
-            bool new_draw = new_glyph[fy * vga_font_width + fx];
-            if (old_draw == new_draw)
-                continue;
-            for (size_t i = 0; i < vga_font_scale_x; i++) {
-                size_t gx = vga_font_scale_x * fx + i;
-                uint32_t bg = c->bg == 0xffffffff ? canvas_line[gx] : c->bg;
-                uint32_t fg = c->fg == 0xffffffff ? canvas_line[gx] : c->fg;
-                fb_line[gx] = new_draw ? fg : bg;
-            }
-        }
-    }
-}
-
-static inline bool compare_char(struct gterm_char *a, struct gterm_char *b) {
-    return !(a->c != b->c || a->bg != b->bg || a->fg != b->fg);
-}
-
-static void push_to_queue(struct gterm_char *c, size_t x, size_t y) {
-    if (x >= cols || y >= rows) {
-        return;
-    }
-
-    size_t i = y * cols + x;
-
-    struct queue_item *q = map[i];
-
-    if (q == NULL) {
-        if (compare_char(&grid[i], c)) {
-            return;
-        }
-        q = &queue[queue_i++];
-        q->x = x;
-        q->y = y;
-        map[i] = q;
-    }
-
-    q->c = *c;
-}
-
-bool gterm_scroll_disable(void) {
-    bool ret = scroll_enabled;
-    scroll_enabled = false;
-    return ret;
-}
-
-void gterm_scroll_enable(void) {
-    scroll_enabled = true;
-}
-
-void gterm_revscroll(void) {
-    for (size_t i = (term_context.scroll_bottom_margin - 1) * cols - 1; ; i--) {
-        struct gterm_char *c;
-        struct queue_item *q = map[i];
-        if (q != NULL) {
-            c = &q->c;
-        } else {
-            c = &grid[i];
-        }
-        push_to_queue(c, (i + cols) % cols, (i + cols) / cols);
-        if (i == term_context.scroll_top_margin * cols) {
-            break;
-        }
-    }
-
-    // Clear the first line of the screen.
-    struct gterm_char empty;
-    empty.c  = ' ';
-    empty.fg = text_fg;
-    empty.bg = text_bg;
-    for (size_t i = term_context.scroll_top_margin * cols;
-         i < (term_context.scroll_top_margin + 1) * cols; i++) {
-        push_to_queue(&empty, i % cols, i / cols);
-    }
-}
-
-void gterm_scroll(void) {
-    for (size_t i = (term_context.scroll_top_margin + 1) * cols;
-         i < term_context.scroll_bottom_margin * cols; i++) {
-        struct gterm_char *c;
-        struct queue_item *q = map[i];
-        if (q != NULL) {
-            c = &q->c;
-        } else {
-            c = &grid[i];
-        }
-        push_to_queue(c, (i - cols) % cols, (i - cols) / cols);
-    }
-
-    // Clear the last line of the screen.
-    struct gterm_char empty;
-    empty.c  = ' ';
-    empty.fg = text_fg;
-    empty.bg = text_bg;
-    for (size_t i = (term_context.scroll_bottom_margin - 1) * cols;
-         i < term_context.scroll_bottom_margin * cols; i++) {
-        push_to_queue(&empty, i % cols, i / cols);
-    }
-}
-
-void gterm_clear(bool move) {
-    struct gterm_char empty;
-    empty.c  = ' ';
-    empty.fg = text_fg;
-    empty.bg = text_bg;
-    for (size_t i = 0; i < rows * cols; i++) {
-        push_to_queue(&empty, i % cols, i / cols);
-    }
-
-    if (move) {
-        cursor_x = 0;
-        cursor_y = 0;
-    }
-}
-
-void gterm_enable_cursor(void) {
-    cursor_status = true;
-}
-
-bool gterm_disable_cursor(void) {
-    bool ret = cursor_status;
-    cursor_status = false;
-    return ret;
-}
-
-void gterm_set_cursor_pos(size_t x, size_t y) {
-    if (x >= cols) {
-        if ((int)x < 0) {
-            x = 0;
-        } else {
-            x = cols - 1;
-        }
-    }
-    if (y >= rows) {
-        if ((int)y < 0) {
-            y = 0;
-        } else {
-            y = rows - 1;
-        }
-    }
-    cursor_x = x;
-    cursor_y = y;
-}
-
-void gterm_get_cursor_pos(size_t *x, size_t *y) {
-    *x = cursor_x;
-    *y = cursor_y;
-}
-
-void gterm_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y) {
-    if (old_x >= cols || old_y >= rows
-     || new_x >= cols || new_y >= rows) {
-        return;
-    }
-
-    size_t i = old_x + old_y * cols;
-
-    struct gterm_char *c;
-    struct queue_item *q = map[i];
-    if (q != NULL) {
-        c = &q->c;
-    } else {
-        c = &grid[i];
-    }
-
-    push_to_queue(c, new_x, new_y);
-}
-
-void gterm_set_text_fg(size_t fg) {
-    text_fg = ansi_colours[fg];
-}
 
-void gterm_set_text_bg(size_t bg) {
-    text_bg = ansi_colours[bg];
-}
-
-void gterm_set_text_fg_bright(size_t fg) {
-    text_fg = ansi_bright_colours[fg];
-}
-
-void gterm_set_text_bg_bright(size_t bg) {
-    text_bg = ansi_bright_colours[bg];
-}
-
-void gterm_set_text_fg_rgb(uint32_t fg) {
-    text_fg = fg;
-}
-
-void gterm_set_text_bg_rgb(uint32_t bg) {
-    text_bg = bg;
-}
-
-void gterm_set_text_fg_default(void) {
-    text_fg = default_fg;
-}
-
-void gterm_set_text_bg_default(void) {
-    text_bg = 0xffffffff;
-}
-
-static void draw_cursor(void) {
-    size_t i = cursor_x + cursor_y * cols;
-    struct gterm_char c;
-    struct queue_item *q = map[i];
-    if (q != NULL) {
-        c = q->c;
-    } else {
-        c = grid[i];
-    }
-    uint32_t tmp = c.fg;
-    c.fg = c.bg;
-    c.bg = tmp;
-    plot_char(&c, cursor_x, cursor_y);
-    if (q != NULL) {
-        grid[i] = q->c;
-        map[i] = NULL;
-    }
-}
-
-void gterm_double_buffer_flush(void) {
-    if (cursor_status) {
-        draw_cursor();
-    }
-
-    for (size_t i = 0; i < queue_i; i++) {
-        struct queue_item *q = &queue[i];
-        size_t offset = q->y * cols + q->x;
-        if (map[offset] == NULL) {
-            continue;
-        }
-        struct gterm_char *old = &grid[offset];
-        if (q->c.bg == old->bg && q->c.fg == old->fg) {
-            plot_char_fast(old, &q->c, q->x, q->y);
-        } else {
-            plot_char(&q->c, q->x, q->y);
-        }
-        grid[offset] = q->c;
-        map[offset] = NULL;
+        return bg_canvas;
     }
 
-    if ((old_cursor_x != cursor_x || old_cursor_y != cursor_y) || cursor_status == false) {
-        plot_char(&grid[old_cursor_x + old_cursor_y * cols], old_cursor_x, old_cursor_y);
-    }
-
-    old_cursor_x = cursor_x;
-    old_cursor_y = cursor_y;
-
-    queue_i = 0;
-}
-
-void gterm_putchar(uint8_t c) {
-    struct gterm_char ch;
-    ch.c  = c;
-    ch.fg = text_fg;
-    ch.bg = text_bg;
-    push_to_queue(&ch, cursor_x++, cursor_y);
-    if (cursor_x == cols && (cursor_y < term_context.scroll_bottom_margin - 1 || scroll_enabled)) {
-        cursor_x = 0;
-        cursor_y++;
-    }
-    if (cursor_y == term_context.scroll_bottom_margin) {
-        cursor_y--;
-        gterm_scroll();
-    }
+    return NULL;
 }
 
 static bool last_serial = false;
 static char *last_config = NULL;
 
-bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t height) {
+bool gterm_init(char *config, size_t width, size_t height) {
     if (current_video_mode >= 0
 #if defined (BIOS)
      && current_video_mode != 0x03
@@ -619,9 +203,7 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
      && fbinfo.framebuffer_bpp == 32
      && serial == last_serial
      && config == last_config) {
-        *_rows = rows;
-        *_cols = cols;
-        gterm_clear(true);
+        term->clear(term, true);
         return true;
     }
 
@@ -634,9 +216,7 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
      && fbinfo.framebuffer_bpp == 32
      && serial == last_serial
      && config == last_config) {
-        *_rows = rows;
-        *_cols = cols;
-        gterm_clear(true);
+        term->clear(term, true);
         return true;
     }
 
@@ -644,15 +224,23 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
     if (!fb_init(&fbinfo, width, height, 32))
         return false;
 
-    last_serial = serial;
+    // Ensure this is xRGB8888, we only support that for the menu
+    if (fbinfo.red_mask_size    != 8
+     || fbinfo.red_mask_shift   != 16
+     || fbinfo.green_mask_size  != 8
+     || fbinfo.green_mask_shift != 8
+     || fbinfo.blue_mask_size   != 8
+     || fbinfo.blue_mask_shift  != 0)
+        return false;
 
-    cursor_status = true;
-    scroll_enabled = true;
+    last_serial = serial;
 
     // default scheme
     margin = 64;
     margin_gradient = 4;
 
+    uint32_t ansi_colours[8];
+
     ansi_colours[0] = 0x00000000; // black
     ansi_colours[1] = 0x00aa0000; // red
     ansi_colours[2] = 0x0000aa00; // green
@@ -678,6 +266,8 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
         }
     }
 
+    uint32_t ansi_bright_colours[8];
+
     ansi_bright_colours[0] = 0x00555555; // black
     ansi_bright_colours[1] = 0x00ff5555; // red
     ansi_bright_colours[2] = 0x0055ff55; // green
@@ -716,9 +306,6 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
         default_fg = strtoui(theme_foreground, NULL, 16) & 0xffffff;
     }
 
-    text_fg = default_fg;
-    text_bg = 0xffffffff;
-
     background = NULL;
     char *background_path = config_get_value(config, 0, "TERM_WALLPAPER");
     if (background_path != NULL) {
@@ -762,27 +349,14 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
         }
     }
 
-    // Ensure this is xRGB8888, we only support that for the menu
-    if (fbinfo.red_mask_size    != 8
-     || fbinfo.red_mask_shift   != 16
-     || fbinfo.green_mask_size  != 8
-     || fbinfo.green_mask_shift != 8
-     || fbinfo.blue_mask_size   != 8
-     || fbinfo.blue_mask_shift  != 0)
-        return false;
-
-    gterm_framebuffer = (void *)(uintptr_t)fbinfo.framebuffer_addr;
-    gterm_width       = fbinfo.framebuffer_width;
-    gterm_height      = fbinfo.framebuffer_height;
-    gterm_bpp         = fbinfo.framebuffer_bpp;
-    gterm_pitch       = fbinfo.framebuffer_pitch;
-
-    vga_font_width = DEFAULT_FONT_WIDTH, vga_font_height = DEFAULT_FONT_HEIGHT;
-    size_t font_bytes = (vga_font_width * vga_font_height * VGA_FONT_GLYPHS) / 8;
+    size_t font_width = 8;
+    size_t font_height = 16;
+    size_t font_size = (font_width * font_height * FBTERM_FONT_GLYPHS) / 8;
 
-    vga_font_bits = ext_mem_alloc(VGA_FONT_MAX);
+#define FONT_MAX 16384
+    uint8_t *font = ext_mem_alloc(FONT_MAX);
 
-    memcpy(vga_font_bits, (void *)_binary_font_bin_start, VGA_FONT_MAX);
+    memcpy(font, (void *)_binary_font_bin_start, (uintptr_t)_binary_font_bin_size);
 
     size_t tmp_font_width, tmp_font_height;
 
@@ -790,14 +364,14 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
     if (menu_font_size != NULL) {
         parse_resolution(&tmp_font_width, &tmp_font_height, NULL, menu_font_size);
 
-        size_t tmp_font_bytes = (tmp_font_width * tmp_font_height * VGA_FONT_GLYPHS) / 8;
+        size_t tmp_font_size = (tmp_font_width * tmp_font_height * FBTERM_FONT_GLYPHS) / 8;
 
-        if (tmp_font_bytes > VGA_FONT_MAX) {
-            print("Font would be too large (%u bytes, %u bytes allowed). Not loading.\n", tmp_font_bytes, VGA_FONT_MAX);
+        if (tmp_font_size > FONT_MAX) {
+            print("Font would be too large (%u bytes, %u bytes allowed). Not loading.\n", tmp_font_size, FONT_MAX);
             goto no_load_font;
         }
 
-        font_bytes = tmp_font_bytes;
+        font_size = tmp_font_size;
     }
 
     char *menu_font = config_get_value(config, 0, "TERM_FONT");
@@ -806,10 +380,10 @@ bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t
         if ((f = uri_open(menu_font)) == NULL) {
             print("menu: Could not open font file.\n");
         } else {
-            fread(f, vga_font_bits, 0, font_bytes);
+            fread(f, font, 0, font_size);
             if (menu_font_size != NULL) {
-                vga_font_width = tmp_font_width;
-                vga_font_height = tmp_font_height;
+                font_width = tmp_font_width;
+                font_height = tmp_font_height;
             }
             fclose(f);
         }
@@ -822,138 +396,31 @@ no_load_font:;
         font_spacing = strtoui(font_spacing_str, NULL, 10);
     }
 
-    vga_font_width += font_spacing;
-
-    vga_font_bool_size = VGA_FONT_GLYPHS * vga_font_height * vga_font_width * sizeof(bool);
-    vga_font_bool = ext_mem_alloc(vga_font_bool_size);
-
-    for (size_t i = 0; i < VGA_FONT_GLYPHS; i++) {
-        uint8_t *glyph = &vga_font_bits[i * vga_font_height];
-
-        for (size_t y = 0; y < vga_font_height; y++) {
-            // NOTE: the characters in VGA fonts are always one byte wide.
-            // 9 dot wide fonts have 8 dots and one empty column, except
-            // characters 0xC0-0xDF replicate column 9.
-            for (size_t x = 0; x < 8; x++) {
-                size_t offset = i * vga_font_height * vga_font_width + y * vga_font_width + x;
-
-                if ((glyph[y] & (0x80 >> x))) {
-                    vga_font_bool[offset] = true;
-                } else {
-                    vga_font_bool[offset] = false;
-                }
-            }
-            // fill columns above 8 like VGA Line Graphics Mode does
-            for (size_t x = 8; x < vga_font_width; x++) {
-                size_t offset = i * vga_font_height * vga_font_width + y *  vga_font_width + x;
-
-                if (i >= 0xC0 && i <= 0xDF) {
-                    vga_font_bool[offset] = (glyph[y] & 1);
-                } else {
-                    vga_font_bool[offset] = false;
-                }
-            }
-        }
-    }
-
-    vga_font_scale_x = 1;
-    vga_font_scale_y = 1;
+    size_t font_scale_x = 1;
+    size_t font_scale_y = 1;
 
     char *menu_font_scale = config_get_value(config, 0, "TERM_FONT_SCALE");
     if (menu_font_scale != NULL) {
-        parse_resolution(&vga_font_scale_x, &vga_font_scale_y, NULL, menu_font_scale);
-        if (vga_font_scale_x > 8 || vga_font_scale_y > 8) {
-            vga_font_scale_x = 1;
-            vga_font_scale_y = 1;
+        parse_resolution(&font_scale_x, &font_scale_y, NULL, menu_font_scale);
+        if (font_scale_x > 8 || font_scale_y > 8) {
+            font_scale_x = 1;
+            font_scale_y = 1;
         }
     }
 
-    glyph_width = vga_font_width * vga_font_scale_x;
-    glyph_height = vga_font_height * vga_font_scale_y;
-
-    *_cols = cols = (gterm_width - margin * 2) / glyph_width;
-    *_rows = rows = (gterm_height - margin * 2) / glyph_height;
+    uint32_t *canvas = generate_canvas();
 
-    offset_x = margin + ((gterm_width - margin * 2) % glyph_width) / 2;
-    offset_y = margin + ((gterm_height - margin * 2) % glyph_height) / 2;
+    term->deinit(term, pmm_free);
 
-    grid_size = rows * cols * sizeof(struct gterm_char);
-    grid = ext_mem_alloc(grid_size);
-
-    queue_size = rows * cols * sizeof(struct queue_item);
-    queue = ext_mem_alloc(queue_size);
-    queue_i = 0;
-
-    map_size = rows * cols * sizeof(struct queue_item *);
-    map = ext_mem_alloc(map_size);
-
-    bg_canvas_size = gterm_width * gterm_height * sizeof(uint32_t);
-    bg_canvas = ext_mem_alloc(bg_canvas_size);
-
-    gterm_generate_canvas();
-    gterm_clear(true);
-    gterm_double_buffer_flush();
+    term = fbterm_init(ext_mem_alloc,
+                (void *)(uintptr_t)fbinfo.framebuffer_addr,
+                fbinfo.framebuffer_width, fbinfo.framebuffer_height, fbinfo.framebuffer_pitch,
+                canvas,
+                ansi_colours, ansi_bright_colours,
+                &default_bg, &default_fg,
+                font, font_width, font_height, font_spacing,
+                font_scale_x, font_scale_y,
+                margin);
 
     return true;
 }
-
-void gterm_deinit(void) {
-    if (background != NULL) {
-        image_close(background);
-    }
-    pmm_free(vga_font_bits, VGA_FONT_MAX);
-    pmm_free(vga_font_bool, vga_font_bool_size);
-    pmm_free(grid, grid_size);
-    pmm_free(queue, queue_size);
-    pmm_free(map, map_size);
-    pmm_free(bg_canvas, bg_canvas_size);
-}
-
-uint64_t gterm_context_size(void) {
-    uint64_t ret = 0;
-
-    ret += sizeof(struct context);
-    ret += grid_size;
-
-    return ret;
-}
-
-void gterm_context_save(uint64_t ptr) {
-    memcpy32to64(ptr, (uint64_t)(uintptr_t)&context, sizeof(struct context));
-    ptr += sizeof(struct context);
-
-    memcpy32to64(ptr, (uint64_t)(uintptr_t)grid, grid_size);
-}
-
-void gterm_context_restore(uint64_t ptr) {
-    memcpy32to64((uint64_t)(uintptr_t)&context, ptr, sizeof(struct context));
-    ptr += sizeof(struct context);
-
-    memcpy32to64((uint64_t)(uintptr_t)grid, ptr, grid_size);
-
-    for (size_t i = 0; i < (size_t)rows * cols; i++) {
-        size_t x = i % cols;
-        size_t y = i / cols;
-
-        plot_char(&grid[i], x, y);
-    }
-
-    if (cursor_status) {
-        draw_cursor();
-    }
-}
-
-void gterm_full_refresh(void) {
-    gterm_generate_canvas();
-
-    for (size_t i = 0; i < (size_t)rows * cols; i++) {
-        size_t x = i % cols;
-        size_t y = i / cols;
-
-        plot_char(&grid[i], x, y);
-    }
-
-    if (cursor_status) {
-        draw_cursor();
-    }
-}
diff --git a/common/lib/gterm.h b/common/lib/gterm.h
index 6b3686a5..a680e7f2 100644
--- a/common/lib/gterm.h
+++ b/common/lib/gterm.h
@@ -1,44 +1,12 @@
 #ifndef __LIB__GTERM_H__
 #define __LIB__GTERM_H__
 
-#include <stdint.h>
+#include <stddef.h>
 #include <stdbool.h>
-#include <lib/image.h>
-#include <drivers/vbe.h>
+#include <lib/fb.h>
 
 extern struct fb_info fbinfo;
 
-bool gterm_init(char *config, size_t *_rows, size_t *_cols, size_t width, size_t height);
-void gterm_deinit(void);
-
-void gterm_putchar(uint8_t c);
-void gterm_clear(bool move);
-void gterm_enable_cursor(void);
-bool gterm_disable_cursor(void);
-void gterm_set_cursor_pos(size_t x, size_t y);
-void gterm_get_cursor_pos(size_t *x, size_t *y);
-void gterm_set_text_fg(size_t fg);
-void gterm_set_text_bg(size_t bg);
-void gterm_set_text_fg_bright(size_t fg);
-void gterm_set_text_bg_bright(size_t bg);
-void gterm_set_text_fg_rgb(uint32_t fg);
-void gterm_set_text_bg_rgb(uint32_t bg);
-void gterm_set_text_fg_default(void);
-void gterm_set_text_bg_default(void);
-bool gterm_scroll_disable(void);
-void gterm_scroll_enable(void);
-void gterm_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
-void gterm_scroll(void);
-void gterm_revscroll(void);
-void gterm_swap_palette(void);
-void gterm_save_state(void);
-void gterm_restore_state(void);
-
-void gterm_double_buffer_flush(void);
-
-uint64_t gterm_context_size(void);
-void gterm_context_save(uint64_t ptr);
-void gterm_context_restore(uint64_t ptr);
-void gterm_full_refresh(void);
+bool gterm_init(char *config, size_t width, size_t height);
 
 #endif
diff --git a/common/lib/print.s2.c b/common/lib/print.s2.c
index 4737d69c..8dffdc3d 100644
--- a/common/lib/print.s2.c
+++ b/common/lib/print.s2.c
@@ -203,7 +203,7 @@ out:
 #if defined (BIOS)
         if (stage3_loaded) {
 #endif
-            term_write((uint64_t)(uintptr_t)print_buf, print_buf_i);
+            term_write(term, print_buf, print_buf_i);
 #if defined (BIOS)
         } else {
             s2_print(print_buf, print_buf_i);
diff --git a/common/lib/readline.c b/common/lib/readline.c
index c523a58c..a3e0b59f 100644
--- a/common/lib/readline.c
+++ b/common/lib/readline.c
@@ -348,32 +348,32 @@ again:
 
 static void reprint_string(int x, int y, const char *s) {
     size_t orig_x, orig_y;
-    disable_cursor();
-    get_cursor_pos(&orig_x, &orig_y);
+    term->disable_cursor(term);
+    term->get_cursor_pos(term, &orig_x, &orig_y);
     set_cursor_pos_helper(x, y);
     print("%s", s);
     set_cursor_pos_helper(orig_x, orig_y);
-    enable_cursor();
+    term->enable_cursor(term);
 }
 
 static void cursor_back(void) {
     size_t x, y;
-    get_cursor_pos(&x, &y);
+    term->get_cursor_pos(term, &x, &y);
     if (x) {
         x--;
     } else if (y) {
         y--;
-        x = term_cols - 1;
+        x = term->cols - 1;
     }
     set_cursor_pos_helper(x, y);
 }
 
 static void cursor_fwd(void) {
     size_t x, y;
-    get_cursor_pos(&x, &y);
-    if (x < term_cols - 1) {
+    term->get_cursor_pos(term, &x, &y);
+    if (x < term->cols - 1) {
         x++;
-    } else if (y < term_rows - 1) {
+    } else if (y < term->rows - 1) {
         y++;
         x = 0;
     }
@@ -381,20 +381,20 @@ static void cursor_fwd(void) {
 }
 
 void readline(const char *orig_str, char *buf, size_t limit) {
-    bool prev_autoflush = term_autoflush;
-    term_autoflush = false;
+    bool prev_autoflush = term->autoflush;
+    term->autoflush = false;
 
     size_t orig_str_len = strlen(orig_str);
     memmove(buf, orig_str, orig_str_len);
     buf[orig_str_len] = 0;
 
     size_t orig_x, orig_y;
-    get_cursor_pos(&orig_x, &orig_y);
+    term->get_cursor_pos(term, &orig_x, &orig_y);
 
     print("%s", orig_str);
 
     for (size_t i = orig_str_len; ; ) {
-        term_double_buffer_flush();
+        term->double_buffer_flush(term);
         int c = getchar();
         switch (c) {
             case GETCHAR_CURSOR_LEFT:
@@ -461,6 +461,6 @@ void readline(const char *orig_str, char *buf, size_t limit) {
     }
 
 out:
-    term_double_buffer_flush();
-    term_autoflush = prev_autoflush;
+    term->double_buffer_flush(term);
+    term->autoflush = prev_autoflush;
 }
diff --git a/common/lib/term.c b/common/lib/term.c
index 46f75812..5fd4334f 100644
--- a/common/lib/term.c
+++ b/common/lib/term.c
@@ -12,54 +12,111 @@
 
 int current_video_mode = -1;
 int term_backend = _NOT_READY;
-size_t term_rows, term_cols;
-bool term_runtime = false;
 
-void (*raw_putchar)(uint8_t c);
-void (*clear)(bool move);
-void (*enable_cursor)(void);
-bool (*disable_cursor)(void);
-void (*set_cursor_pos)(size_t x, size_t y);
-void (*get_cursor_pos)(size_t *x, size_t *y);
-void (*set_text_fg)(size_t fg);
-void (*set_text_bg)(size_t bg);
-void (*set_text_fg_bright)(size_t fg);
-void (*set_text_bg_bright)(size_t bg);
-void (*set_text_fg_rgb)(uint32_t fg);
-void (*set_text_bg_rgb)(uint32_t bg);
-void (*set_text_fg_default)(void);
-void (*set_text_bg_default)(void);
-bool (*scroll_disable)(void);
-void (*scroll_enable)(void);
-void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
-void (*term_scroll)(void);
-void (*term_revscroll)(void);
-void (*term_swap_palette)(void);
-void (*term_save_state)(void);
-void (*term_restore_state)(void);
+struct term_context *term;
 
-void (*term_double_buffer_flush)(void);
+static struct textmode_context term_local_struct;
 
-uint64_t (*term_context_size)(void);
-void (*term_context_save)(uint64_t ptr);
-void (*term_context_restore)(uint64_t ptr);
-void (*term_full_refresh)(void);
+// --- notready ---
+
+static void notready_raw_putchar(struct term_context *ctx, uint8_t c) {
+    (void)ctx;
+    (void)c;
+}
+static void notready_clear(struct term_context *ctx, bool move) {
+    (void)ctx;
+    (void)move;
+}
+static void notready_void(struct term_context *ctx) {
+    (void)ctx;
+}
+static void notready_set_cursor_pos(struct term_context *ctx, size_t x, size_t y) {
+    (void)ctx;
+    (void)x; (void)y;
+}
+static void notready_get_cursor_pos(struct term_context *ctx, size_t *x, size_t *y) {
+    (void)ctx;
+    *x = 0;
+    *y = 0;
+}
+static void notready_size_t(struct term_context *ctx, size_t n) {
+    (void)ctx;
+    (void)n;
+}
+static bool notready_disable(struct term_context *ctx) {
+    (void)ctx;
+    return false;
+}
+static void notready_move_character(struct term_context *ctx, size_t a, size_t b, size_t c, size_t d) {
+    (void)ctx;
+    (void)a; (void)b; (void)c; (void)d;
+}
+static void notready_uint32_t(struct term_context *ctx, uint32_t n) {
+    (void)ctx;
+    (void)n;
+}
+static void notready_deinit(struct term_context *ctx, void (*_free)(void *, size_t)) {
+    (void)ctx;
+    (void)_free;
+}
+
+void term_notready(void) {
+    if (term != NULL) {
+        term->deinit(term, pmm_free);
+    }
+
+    term = &term_local_struct.term;
+
+    term->raw_putchar = notready_raw_putchar;
+    term->clear = notready_clear;
+    term->enable_cursor = notready_void;
+    term->disable_cursor = notready_disable;
+    term->set_cursor_pos = notready_set_cursor_pos;
+    term->get_cursor_pos = notready_get_cursor_pos;
+    term->set_text_fg = notready_size_t;
+    term->set_text_bg = notready_size_t;
+    term->set_text_fg_bright = notready_size_t;
+    term->set_text_bg_bright = notready_size_t;
+    term->set_text_fg_rgb = notready_uint32_t;
+    term->set_text_bg_rgb = notready_uint32_t;
+    term->set_text_fg_default = notready_void;
+    term->set_text_bg_default = notready_void;
+    term->move_character = notready_move_character;
+    term->scroll = notready_void;
+    term->revscroll = notready_void;
+    term->swap_palette = notready_void;
+    term->save_state = notready_void;
+    term->restore_state = notready_void;
+    term->double_buffer_flush = notready_void;
+    term->full_refresh = notready_void;
+    term->deinit = notready_deinit;
+
+    term->cols = 80;
+    term->rows = 24;
+
+    term_backend = _NOT_READY;
+    term_context_reinit(term);
+
+    term->in_bootloader = true;
+}
 
 // --- fallback ---
 
 #if defined (BIOS)
-static void fallback_raw_putchar(uint8_t c) {
+static void fallback_raw_putchar(struct term_context *ctx, uint8_t c) {
+    (void)ctx;
     struct rm_regs r = {0};
     r.eax = 0x0e00 | c;
     rm_int(0x10, &r, &r);
 }
 
-static void fallback_set_cursor_pos(size_t x, size_t y);
-static void fallback_get_cursor_pos(size_t *x, size_t *y);
+static void fallback_set_cursor_pos(struct term_context *ctx, size_t x, size_t y);
+static void fallback_get_cursor_pos(struct term_context *ctx, size_t *x, size_t *y);
 
-static void fallback_clear(bool move) {
+static void fallback_clear(struct term_context *ctx, bool move) {
+    (void)ctx;
     size_t x, y;
-    fallback_get_cursor_pos(&x, &y);
+    fallback_get_cursor_pos(NULL, &x, &y);
     struct rm_regs r = {0};
     rm_int(0x11, &r, &r);
     switch ((r.eax >> 4) & 3) {
@@ -80,10 +137,11 @@ static void fallback_clear(bool move) {
     if (move) {
         x = y = 0;
     }
-    fallback_set_cursor_pos(x, y);
+    fallback_set_cursor_pos(NULL, x, y);
 }
 
-static void fallback_set_cursor_pos(size_t x, size_t y) {
+static void fallback_set_cursor_pos(struct term_context *ctx, size_t x, size_t y) {
+    (void)ctx;
     struct rm_regs r = {0};
     r.eax = 0x0200;
     r.ebx = 0;
@@ -91,7 +149,8 @@ static void fallback_set_cursor_pos(size_t x, size_t y) {
     rm_int(0x10, &r, &r);
 }
 
-static void fallback_get_cursor_pos(size_t *x, size_t *y) {
+static void fallback_get_cursor_pos(struct term_context *ctx, size_t *x, size_t *y) {
+    (void)ctx;
     struct rm_regs r = {0};
     r.eax = 0x0300;
     r.ebx = 0;
@@ -100,20 +159,22 @@ static void fallback_get_cursor_pos(size_t *x, size_t *y) {
     *y = r.edx >> 8;
 }
 
-static void fallback_scroll(void) {
+static void fallback_scroll(struct term_context *ctx) {
+    (void)ctx;
     size_t x, y;
-    fallback_get_cursor_pos(&x, &y);
-    fallback_set_cursor_pos(term_cols - 1, term_rows - 1);
-    fallback_raw_putchar(' ');
-    fallback_set_cursor_pos(x, y);
+    fallback_get_cursor_pos(NULL, &x, &y);
+    fallback_set_cursor_pos(NULL, term->cols - 1, term->rows - 1);
+    fallback_raw_putchar(NULL, ' ');
+    fallback_set_cursor_pos(NULL, x, y);
 }
 
 #elif defined (UEFI)
 
 static size_t cursor_x = 0, cursor_y = 0;
 
-static void fallback_scroll(void) {
-    gST->ConOut->SetCursorPosition(gST->ConOut, term_cols - 1, term_rows - 1);
+static void fallback_scroll(struct term_context *ctx) {
+    (void)ctx;
+    gST->ConOut->SetCursorPosition(gST->ConOut, term->cols - 1, term->rows - 1);
     CHAR16 string[2];
     string[0] = ' ';
     string[1] = 0;
@@ -121,21 +182,23 @@ static void fallback_scroll(void) {
     gST->ConOut->SetCursorPosition(gST->ConOut, cursor_x, cursor_y);
 }
 
-static void fallback_raw_putchar(uint8_t c) {
+static void fallback_raw_putchar(struct term_context *ctx, uint8_t c) {
+    (void)ctx;
     CHAR16 string[2];
     string[0] = c;
     string[1] = 0;
     gST->ConOut->OutputString(gST->ConOut, string);
-    if (++cursor_x >= term_cols) {
+    if (++cursor_x >= term->cols) {
         cursor_x = 0;
-        if (++cursor_y >= term_rows) {
+        if (++cursor_y >= term->rows) {
             cursor_y--;
         }
     }
     gST->ConOut->SetCursorPosition(gST->ConOut, cursor_x, cursor_y);
 }
 
-static void fallback_clear(bool move) {
+static void fallback_clear(struct term_context *ctx, bool move) {
+    (void)ctx;
     gST->ConOut->ClearScreen(gST->ConOut);
     if (move) {
         cursor_x = cursor_y = 0;
@@ -143,8 +206,9 @@ static void fallback_clear(bool move) {
     gST->ConOut->SetCursorPosition(gST->ConOut, cursor_x, cursor_y);
 }
 
-static void fallback_set_cursor_pos(size_t x, size_t y) {
-    if (x >= term_cols || y >= term_rows) {
+static void fallback_set_cursor_pos(struct term_context *ctx, size_t x, size_t y) {
+    (void)ctx;
+    if (x >= term->cols || y >= term->rows) {
         return;
     }
     gST->ConOut->SetCursorPosition(gST->ConOut, x, y);
@@ -152,7 +216,8 @@ static void fallback_set_cursor_pos(size_t x, size_t y) {
     cursor_y = y;
 }
 
-static void fallback_get_cursor_pos(size_t *x, size_t *y) {
+static void fallback_get_cursor_pos(struct term_context *ctx, size_t *x, size_t *y) {
+    (void)ctx;
     *x = cursor_x;
     *y = cursor_y;
 }
@@ -164,156 +229,46 @@ void term_fallback(void) {
 #if defined (UEFI)
     if (!efi_boot_services_exited) {
 #endif
-        fallback_clear(true);
+        fallback_clear(NULL, true);
 #if defined (UEFI)
         gST->ConOut->EnableCursor(gST->ConOut, false);
 #endif
-        raw_putchar = fallback_raw_putchar;
-        clear = fallback_clear;
-        set_cursor_pos = fallback_set_cursor_pos;
-        get_cursor_pos = fallback_get_cursor_pos;
-        term_scroll = fallback_scroll;
+        term->raw_putchar = fallback_raw_putchar;
+        term->clear = fallback_clear;
+        term->set_cursor_pos = fallback_set_cursor_pos;
+        term->get_cursor_pos = fallback_get_cursor_pos;
+        term->scroll = fallback_scroll;
 #if defined (UEFI)
         UINTN uefi_term_x_size, uefi_term_y_size;
         gST->ConOut->QueryMode(gST->ConOut, gST->ConOut->Mode->Mode, &uefi_term_x_size, &uefi_term_y_size);
-        term_cols = uefi_term_x_size;
-        term_rows = uefi_term_y_size;
+        term->cols = uefi_term_x_size;
+        term->rows = uefi_term_y_size;
 #elif defined (BIOS)
-        term_cols = 80;
-        term_rows = 25;
+        term->cols = 80;
+        term->rows = 25;
 #endif
         term_backend = FALLBACK;
-        term_reinit();
+        term_context_reinit(term);
+
+        term->in_bootloader = true;
 #if defined (UEFI)
     }
 #endif
 }
 
-// --- notready ---
-
-static void notready_raw_putchar(uint8_t c) {
-    (void)c;
-}
-static void notready_clear(bool move) {
-    (void)move;
-}
-static void notready_void(void) {}
-static void notready_set_cursor_pos(size_t x, size_t y) {
-    (void)x; (void)y;
-}
-static void notready_get_cursor_pos(size_t *x, size_t *y) {
-    *x = 0;
-    *y = 0;
-}
-static void notready_size_t(size_t n) {
-    (void)n;
-}
-static bool notready_disable(void) {
-    return false;
-}
-static void notready_move_character(size_t a, size_t b, size_t c, size_t d) {
-    (void)a; (void)b; (void)c; (void)d;
-}
-static uint64_t notready_context_size(void) {
-    return 0;
-}
-static void notready_uint32_t(uint32_t n) {
-    (void)n;
-}
-static void notready_uint64_t(uint64_t n) {
-    (void)n;
-}
-
-void term_notready(void) {
-    raw_putchar = notready_raw_putchar;
-    clear = notready_clear;
-    enable_cursor = notready_void;
-    disable_cursor = notready_disable;
-    set_cursor_pos = notready_set_cursor_pos;
-    get_cursor_pos = notready_get_cursor_pos;
-    set_text_fg = notready_size_t;
-    set_text_bg = notready_size_t;
-    set_text_fg_bright = notready_size_t;
-    set_text_bg_bright = notready_size_t;
-    set_text_fg_rgb = notready_uint32_t;
-    set_text_bg_rgb = notready_uint32_t;
-    set_text_fg_default = notready_void;
-    set_text_bg_default = notready_void;
-    scroll_disable = notready_disable;
-    scroll_enable = notready_void;
-    term_move_character = notready_move_character;
-    term_scroll = notready_void;
-    term_revscroll = notready_void;
-    term_swap_palette = notready_void;
-    term_save_state = notready_void;
-    term_restore_state = notready_void;
-    term_double_buffer_flush = notready_void;
-    term_context_size = notready_context_size;
-    term_context_save = notready_uint64_t;
-    term_context_restore = notready_uint64_t;
-    term_full_refresh = notready_void;
-
-    term_cols = 80;
-    term_rows = 24;
-
-    term_backend = _NOT_READY;
-}
-
-static const uint32_t col256[] = {
-    0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
-    0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
-    0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
-    0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
-    0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
-    0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
-    0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
-    0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
-    0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
-    0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
-    0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
-    0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
-    0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
-    0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
-    0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
-    0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
-    0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
-    0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
-    0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
-    0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
-    0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
-    0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
-    0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
-    0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
-    0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
-    0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
-    0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
-    0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
-    0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
-    0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
-};
-
 extern void reset_term(void);
 extern void set_cursor_pos_helper(size_t x, size_t y);
 
-void term_deinit(void) {
-    switch (term_backend) {
-        case VBE:
-            gterm_deinit();
-    }
-
-    term_notready();
-}
-
 void term_vbe(char *config, size_t width, size_t height) {
     if (term_backend != VBE) {
-        term_deinit();
+        term->deinit(term, pmm_free);
     }
 
     if (quiet || allocations_disallowed) {
         return;
     }
 
-    if (!gterm_init(config, &term_rows, &term_cols, width, height)) {
+    if (!gterm_init(config, width, height)) {
 #if defined (BIOS)
         // Failed to set VBE properly, default to text mode
         term_textmode();
@@ -322,177 +277,58 @@ void term_vbe(char *config, size_t width, size_t height) {
     }
 
     if (serial) {
-        term_cols = term_cols > 80 ? 80 : term_cols;
-        term_rows = term_rows > 24 ? 24 : term_rows;
+        term->cols = term->cols > 80 ? 80 : term->cols;
+        term->rows = term->rows > 24 ? 24 : term->rows;
     }
 
-    term_reinit();
-
-    raw_putchar    = gterm_putchar;
-    clear          = gterm_clear;
-    enable_cursor  = gterm_enable_cursor;
-    disable_cursor = gterm_disable_cursor;
-    set_cursor_pos = gterm_set_cursor_pos;
-    get_cursor_pos = gterm_get_cursor_pos;
-    set_text_fg    = gterm_set_text_fg;
-    set_text_bg    = gterm_set_text_bg;
-    set_text_fg_bright = gterm_set_text_fg_bright;
-    set_text_bg_bright = gterm_set_text_bg_bright;
-    set_text_fg_rgb = gterm_set_text_fg_rgb;
-    set_text_bg_rgb = gterm_set_text_bg_rgb;
-    set_text_fg_default = gterm_set_text_fg_default;
-    set_text_bg_default = gterm_set_text_bg_default;
-    scroll_disable = gterm_scroll_disable;
-    scroll_enable  = gterm_scroll_enable;
-    term_move_character = gterm_move_character;
-    term_scroll = gterm_scroll;
-    term_revscroll = gterm_revscroll;
-    term_swap_palette = gterm_swap_palette;
-    term_save_state = gterm_save_state;
-    term_restore_state = gterm_restore_state;
-
-    term_double_buffer_flush = gterm_double_buffer_flush;
-
-    term_context_size = gterm_context_size;
-    term_context_save = gterm_context_save;
-    term_context_restore = gterm_context_restore;
-    term_full_refresh = gterm_full_refresh;
-
     term_backend = VBE;
-}
-
-// Tries to implement this standard for terminfo
-// https://man7.org/linux/man-pages/man4/console_codes.4.html
-
-uint64_t term_arg = 0;
-void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t) = NULL;
-
-struct term_context term_context;
 
-#define escape_offset term_context.escape_offset
-#define control_sequence term_context.control_sequence
-#define csi term_context.csi
-#define escape term_context.escape
-#define rrr term_context.rrr
-#define discard_next term_context.discard_next
-#define bold term_context.bold
-#define reverse_video term_context.reverse_video
-#define dec_private term_context.dec_private
-#define esc_values term_context.esc_values
-#define esc_values_i term_context.esc_values_i
-#define saved_cursor_x term_context.saved_cursor_x
-#define saved_cursor_y term_context.saved_cursor_y
-#define current_primary term_context.current_primary
-#define insert_mode term_context.insert_mode
-#define scroll_top_margin term_context.scroll_top_margin
-#define scroll_bottom_margin term_context.scroll_bottom_margin
-#define current_charset term_context.current_charset
-#define charsets term_context.charsets
-#define g_select term_context.g_select
-
-#define saved_state_bold term_context.saved_state_bold
-#define saved_state_reverse_video term_context.saved_state_reverse_video
-#define saved_state_current_charset term_context.saved_state_current_charset
-#define saved_state_current_primary term_context.saved_state_current_primary
-
-#define CHARSET_DEFAULT 0
-#define CHARSET_DEC_SPECIAL 1
-
-void term_reinit(void) {
-    escape_offset = 0;
-    control_sequence = false;
-    csi = false;
-    escape = false;
-    rrr = false;
-    discard_next = false;
-    bold = false;
-    reverse_video = false;
-    dec_private = false;
-    esc_values_i = 0;
-    saved_cursor_x = 0;
-    saved_cursor_y = 0;
-    current_primary = (size_t)-1;
-    insert_mode = false;
-    scroll_top_margin = 0;
-    scroll_bottom_margin = term_rows;
-    current_charset = 0;
-    g_select = 0;
-    charsets[0] = CHARSET_DEFAULT;
-    charsets[1] = CHARSET_DEC_SPECIAL;
-    term_autoflush = true;
+    term->in_bootloader = true;
 }
 
 #if defined (BIOS)
 void term_textmode(void) {
-    term_deinit();
+    term_notready();
 
     if (quiet || allocations_disallowed) {
         return;
     }
 
-    init_vga_textmode(&term_rows, &term_cols, true);
+    init_vga_textmode(&term->rows, &term->cols, true);
 
     if (serial) {
-        term_cols = term_cols > 80 ? 80 : term_cols;
-        term_rows = term_rows > 24 ? 24 : term_rows;
-    }
-
-    term_reinit();
-
-    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;
-    set_text_fg_bright = text_set_text_fg_bright;
-    set_text_bg_bright = text_set_text_bg_bright;
-    set_text_fg_default = text_set_text_fg_default;
-    set_text_bg_default = text_set_text_bg_default;
-    scroll_disable = text_scroll_disable;
-    scroll_enable  = text_scroll_enable;
-    term_move_character = text_move_character;
-    term_scroll = text_scroll;
-    term_revscroll = text_revscroll;
-    term_swap_palette = text_swap_palette;
-    term_save_state = text_save_state;
-    term_restore_state = text_restore_state;
-
-    term_double_buffer_flush = text_double_buffer_flush;
-
-    term_context_size = text_context_size;
-    term_context_save = text_context_save;
-    term_context_restore = text_context_restore;
-    term_full_refresh = text_full_refresh;
+        term->cols = term->cols > 80 ? 80 : term->cols;
+        term->rows = term->rows > 24 ? 24 : term->rows;
+    }
+
+    term->raw_putchar    = text_putchar;
+    term->clear          = text_clear;
+    term->enable_cursor  = text_enable_cursor;
+    term->disable_cursor = text_disable_cursor;
+    term->set_cursor_pos = text_set_cursor_pos;
+    term->get_cursor_pos = text_get_cursor_pos;
+    term->set_text_fg    = text_set_text_fg;
+    term->set_text_bg    = text_set_text_bg;
+    term->set_text_fg_bright = text_set_text_fg_bright;
+    term->set_text_bg_bright = text_set_text_bg_bright;
+    term->set_text_fg_default = text_set_text_fg_default;
+    term->set_text_bg_default = text_set_text_bg_default;
+    term->move_character = text_move_character;
+    term->scroll = text_scroll;
+    term->revscroll = text_revscroll;
+    term->swap_palette = text_swap_palette;
+    term->save_state = text_save_state;
+    term->restore_state = text_restore_state;
+    term->double_buffer_flush = text_double_buffer_flush;
+    term->full_refresh = text_full_refresh;
+    //term->deinit = text_deinit;
 
     term_backend = TEXTMODE;
-}
-#endif
-
-static uint64_t context_size(void) {
-    uint64_t ret = 0;
-
-    ret += sizeof(struct term_context);
-    ret += term_context_size();
+    term_context_reinit(term);
 
-    return ret;
-}
-
-static void context_save(uint64_t ptr) {
-    memcpy32to64(ptr, (uint64_t)(uintptr_t)&term_context, sizeof(struct term_context));
-    ptr += sizeof(struct term_context);
-
-    term_context_save(ptr);
-}
-
-static void context_restore(uint64_t ptr) {
-    memcpy32to64((uint64_t)(uintptr_t)&term_context, ptr, sizeof(struct term_context));
-    ptr += sizeof(struct term_context);
-
-    term_context_restore(ptr);
+    term->in_bootloader = true;
 }
+#endif
 
 #if defined (__i386__)
 #define TERM_XFER_CHUNK 8192
@@ -500,27 +336,23 @@ static void context_restore(uint64_t ptr) {
 static uint8_t xfer_buf[TERM_XFER_CHUNK];
 #endif
 
-bool term_autoflush = true;
-
-static void term_putchar(uint8_t c);
-
-void term_write(uint64_t buf, uint64_t count) {
+void _term_write(uint64_t buf, uint64_t count) {
     switch (count) {
         case TERM_CTX_SIZE: {
-            uint64_t ret = context_size();
-            memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t));
+            //uint64_t ret = context_size();
+            //memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t));
             return;
         }
         case TERM_CTX_SAVE: {
-            context_save(buf);
+            //context_save(buf);
             return;
         }
         case TERM_CTX_RESTORE: {
-            context_restore(buf);
+            //context_restore(buf);
             return;
         }
         case TERM_FULL_REFRESH: {
-            term_full_refresh();
+            term->full_refresh(term);
             return;
         }
     }
@@ -532,11 +364,13 @@ void term_write(uint64_t buf, uint64_t count) {
 #error Unknown architecture
 #endif
 
-    if (!term_runtime || native) {
+    bool autoflush = term->autoflush;
+    term->autoflush = false;
+
+    if (term->in_bootloader || native) {
         const char *s = (const char *)(uintptr_t)buf;
 
-        for (size_t i = 0; i < count; i++)
-            term_putchar(s[i]);
+        term_write(term, s, count);
     } else {
 #if defined (__i386__)
         while (count != 0) {
@@ -549,8 +383,7 @@ void term_write(uint64_t buf, uint64_t count) {
 
             memcpy32to64((uint64_t)(uintptr_t)xfer_buf, buf, chunk);
 
-            for (size_t i = 0; i < chunk; i++)
-                term_putchar(xfer_buf[i]);
+            term_write(term, (const char *)xfer_buf, chunk);
 
             count -= chunk;
             buf += chunk;
@@ -558,825 +391,9 @@ void term_write(uint64_t buf, uint64_t count) {
 #endif
     }
 
-    if (term_autoflush) {
-        term_double_buffer_flush();
-    }
-}
-
-static void sgr(void) {
-    size_t i = 0;
-
-    if (!esc_values_i)
-        goto def;
-
-    for (; i < esc_values_i; i++) {
-        size_t offset;
-
-        if (esc_values[i] == 0) {
-def:
-            if (reverse_video) {
-                reverse_video = false;
-                term_swap_palette();
-            }
-            bold = false;
-            current_primary = (size_t)-1;
-            set_text_bg_default();
-            set_text_fg_default();
-            continue;
-        }
-
-        else if (esc_values[i] == 1) {
-            bold = true;
-            if (current_primary != (size_t)-1) {
-                if (!reverse_video) {
-                    set_text_fg_bright(current_primary);
-                } else {
-                    set_text_bg_bright(current_primary);
-                }
-            }
-            continue;
-        }
-
-        else if (esc_values[i] == 22) {
-            bold = false;
-            if (current_primary != (size_t)-1) {
-                if (!reverse_video) {
-                    set_text_fg(current_primary);
-                } else {
-                    set_text_bg(current_primary);
-                }
-            }
-            continue;
-        }
-
-        else if (esc_values[i] >= 30 && esc_values[i] <= 37) {
-            offset = 30;
-            current_primary = esc_values[i] - offset;
-
-            if (reverse_video) {
-                goto set_bg;
-            }
-
-set_fg:
-            if (bold && !reverse_video) {
-                set_text_fg_bright(esc_values[i] - offset);
-            } else {
-                set_text_fg(esc_values[i] - offset);
-            }
-            continue;
-        }
-
-        else if (esc_values[i] >= 40 && esc_values[i] <= 47) {
-            offset = 40;
-            if (reverse_video) {
-                goto set_fg;
-            }
-
-set_bg:
-            if (bold && reverse_video) {
-                set_text_bg_bright(esc_values[i] - offset);
-            } else {
-                set_text_bg(esc_values[i] - offset);
-            }
-            continue;
-        }
-
-        else if (esc_values[i] >= 90 && esc_values[i] <= 97) {
-            offset = 90;
-            current_primary = esc_values[i] - offset;
-
-            if (reverse_video) {
-                goto set_bg_bright;
-            }
-
-set_fg_bright:
-            set_text_fg_bright(esc_values[i] - offset);
-            continue;
-        }
-
-        else if (esc_values[i] >= 100 && esc_values[i] <= 107) {
-            offset = 100;
-            if (reverse_video) {
-                goto set_fg_bright;
-            }
-
-set_bg_bright:
-            set_text_bg_bright(esc_values[i] - offset);
-            continue;
-        }
-
-        else if (esc_values[i] == 39) {
-            current_primary = (size_t)-1;
-
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            set_text_fg_default();
-
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            continue;
-        }
-
-        else if (esc_values[i] == 49) {
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            set_text_bg_default();
-
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            continue;
-        }
-
-        else if (esc_values[i] == 7) {
-            if (!reverse_video) {
-                reverse_video = true;
-                term_swap_palette();
-            }
-            continue;
-        }
-
-        else if (esc_values[i] == 27) {
-            if (reverse_video) {
-                reverse_video = false;
-                term_swap_palette();
-            }
-            continue;
-        }
-
-        // 256/RGB
-        else if (esc_values[i] == 38 || esc_values[i] == 48) {
-            bool fg = esc_values[i] == 38;
-
-            i++;
-            if (i >= esc_values_i) {
-                break;
-            }
-
-            switch (esc_values[i]) {
-                case 2: { // RGB
-                    if (i + 3 >= esc_values_i) {
-                        goto out;
-                    }
-
-                    uint32_t rgb_value = 0;
-
-                    rgb_value |= esc_values[i + 1] << 16;
-                    rgb_value |= esc_values[i + 2] << 8;
-                    rgb_value |= esc_values[i + 3];
-
-                    i += 3;
-
-                    fg ? set_text_fg_rgb(rgb_value) : set_text_bg_rgb(rgb_value);
-
-                    break;
-                }
-                case 5: { // 256 colors
-                    if (i + 1 >= esc_values_i) {
-                        goto out;
-                    }
-
-                    uint32_t col = esc_values[i + 1];
-
-                    i++;
-
-                    if (col < 8) {
-                        fg ? set_text_fg(col) : set_text_bg(col);
-                    } else if (col < 16) {
-                        fg ? set_text_fg_bright(col - 8) : set_text_bg_bright(col - 8);
-                    } else {
-                        uint32_t rgb_value = col256[col - 16];
-                        fg ? set_text_fg_rgb(rgb_value) : set_text_bg_rgb(rgb_value);
-                    }
-
-                    break;
-                }
-                default: continue;
-            }
-        }
-    }
-
-out:;
-}
-
-static void dec_private_parse(uint8_t c) {
-    dec_private = false;
-
-    if (esc_values_i == 0) {
-        return;
-    }
-
-    bool set;
-
-    switch (c) {
-        case 'h':
-            set = true; break;
-        case 'l':
-            set = false; break;
-        default:
-            return;
-    }
-
-    switch (esc_values[0]) {
-        case 25: {
-            if (set) {
-                enable_cursor();
-            } else {
-                disable_cursor();
-            }
-            return;
-        }
-    }
-
-    if (term_callback != NULL) {
-        if (term_arg != 0) {
-            term_callback(term_arg, TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c);
-        } else {
-            term_callback(TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c, 0);
-        }
-    }
-}
-
-static void linux_private_parse(void) {
-    if (esc_values_i == 0) {
-        return;
-    }
-
-    if (term_callback != NULL) {
-        if (term_arg != 0) {
-            term_callback(term_arg, TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0);
-        } else {
-            term_callback(TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0, 0);
-        }
-    }
-}
-
-static void mode_toggle(uint8_t c) {
-    if (esc_values_i == 0) {
-        return;
-    }
-
-    bool set;
-
-    switch (c) {
-        case 'h':
-            set = true; break;
-        case 'l':
-            set = false; break;
-        default:
-            return;
-    }
-
-    switch (esc_values[0]) {
-        case 4:
-            insert_mode = set; return;
-    }
-
-    if (term_callback != NULL) {
-        if (term_arg != 0) {
-            term_callback(term_arg, TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c);
-        } else {
-            term_callback(TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c, 0);
-        }
-    }
-}
-
-static void control_sequence_parse(uint8_t c) {
-    if (escape_offset == 2) {
-        switch (c) {
-            case '[':
-                discard_next = true;
-                goto cleanup;
-            case '?':
-                dec_private = true;
-                return;
-        }
-    }
-
-    if (c >= '0' && c <= '9') {
-        if (esc_values_i == MAX_ESC_VALUES) {
-            return;
-        }
-        rrr = true;
-        esc_values[esc_values_i] *= 10;
-        esc_values[esc_values_i] += c - '0';
-        return;
-    }
-
-    if (rrr == true) {
-        esc_values_i++;
-        rrr = false;
-        if (c == ';')
-            return;
-    } else if (c == ';') {
-        if (esc_values_i == MAX_ESC_VALUES) {
-            return;
-        }
-        esc_values[esc_values_i] = 0;
-        esc_values_i++;
-        return;
-    }
-
-    size_t esc_default;
-    switch (c) {
-        case 'J': case 'K': case 'q':
-            esc_default = 0; break;
-        default:
-            esc_default = 1; break;
-    }
-
-    for (size_t i = esc_values_i; i < MAX_ESC_VALUES; i++) {
-        esc_values[i] = esc_default;
-    }
-
-    if (dec_private == true) {
-        dec_private_parse(c);
-        goto cleanup;
-    }
-
-    bool r = scroll_disable();
-    size_t x, y;
-    get_cursor_pos(&x, &y);
-
-    switch (c) {
-        case 'F':
-            x = 0;
-            // FALLTHRU
-        case 'A': {
-            if (esc_values[0] > y)
-                esc_values[0] = y;
-            size_t orig_y = y;
-            size_t dest_y = y - esc_values[0];
-            bool will_be_in_scroll_region = false;
-            if ((scroll_top_margin >= dest_y && scroll_top_margin <= orig_y)
-             || (scroll_bottom_margin >= dest_y && scroll_bottom_margin <= orig_y)) {
-                will_be_in_scroll_region = true;
-            }
-            if (will_be_in_scroll_region && dest_y < scroll_top_margin) {
-                dest_y = scroll_top_margin;
-            }
-            set_cursor_pos(x, dest_y);
-            break;
-        }
-        case 'E':
-            x = 0;
-            // FALLTHRU
-        case 'e':
-        case 'B': {
-            if (y + esc_values[0] > term_rows - 1)
-                esc_values[0] = (term_rows - 1) - y;
-            size_t orig_y = y;
-            size_t dest_y = y + esc_values[0];
-            bool will_be_in_scroll_region = false;
-            if ((scroll_top_margin >= orig_y && scroll_top_margin <= dest_y)
-             || (scroll_bottom_margin >= orig_y && scroll_bottom_margin <= dest_y)) {
-                will_be_in_scroll_region = true;
-            }
-            if (will_be_in_scroll_region && dest_y >= scroll_bottom_margin) {
-                dest_y = scroll_bottom_margin - 1;
-            }
-            set_cursor_pos(x, dest_y);
-            break;
-        }
-        case 'a':
-        case 'C':
-            if (x + esc_values[0] > term_cols - 1)
-                esc_values[0] = (term_cols - 1) - x;
-            set_cursor_pos(x + esc_values[0], y);
-            break;
-        case 'D':
-            if (esc_values[0] > x)
-                esc_values[0] = x;
-            set_cursor_pos(x - esc_values[0], y);
-            break;
-        case 'c':
-            if (term_callback != NULL) {
-                if (term_arg != 0) {
-                    term_callback(term_arg, TERM_CB_PRIVATE_ID, 0, 0, 0);
-                } else {
-                    term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0, 0);
-                }
-            }
-            break;
-        case 'd':
-            esc_values[0] -= 1;
-            if (esc_values[0] >= term_rows)
-                esc_values[0] = term_rows - 1;
-            set_cursor_pos(x, esc_values[0]);
-            break;
-        case 'G':
-        case '`':
-            esc_values[0] -= 1;
-            if (esc_values[0] >= term_cols)
-                esc_values[0] = term_cols - 1;
-            set_cursor_pos(esc_values[0], y);
-            break;
-        case 'H':
-        case 'f':
-            esc_values[0] -= 1;
-            esc_values[1] -= 1;
-            if (esc_values[1] >= term_cols)
-                esc_values[1] = term_cols - 1;
-            if (esc_values[0] >= term_rows)
-                esc_values[0] = term_rows - 1;
-            set_cursor_pos(esc_values[1], esc_values[0]);
-            break;
-        case 'n':
-            switch (esc_values[0]) {
-                case 5:
-                    if (term_callback != NULL) {
-                        if (term_arg != 0) {
-                            term_callback(term_arg, TERM_CB_STATUS_REPORT, 0, 0, 0);
-                        } else {
-                            term_callback(TERM_CB_STATUS_REPORT, 0, 0, 0, 0);
-                        }
-                    }
-                    break;
-                case 6:
-                    if (term_callback != NULL) {
-                        if (term_arg != 0) {
-                            term_callback(term_arg, TERM_CB_POS_REPORT, x + 1, y + 1, 0);
-                        } else {
-                            term_callback(TERM_CB_POS_REPORT, x + 1, y + 1, 0, 0);
-                        }
-                    }
-                    break;
-            }
-            break;
-        case 'q':
-            if (term_callback != NULL) {
-                if (term_arg != 0) {
-                    term_callback(term_arg, TERM_CB_KBD_LEDS, esc_values[0], 0, 0);
-                } else {
-                    term_callback(TERM_CB_KBD_LEDS, esc_values[0], 0, 0, 0);
-                }
-            }
-            break;
-        case 'J':
-            switch (esc_values[0]) {
-                case 0: {
-                    size_t rows_remaining = term_rows - (y + 1);
-                    size_t cols_diff = term_cols - (x + 1);
-                    size_t to_clear = rows_remaining * term_cols + cols_diff;
-                    for (size_t i = 0; i < to_clear; i++) {
-                        raw_putchar(' ');
-                    }
-                    set_cursor_pos(x, y);
-                    break;
-                }
-                case 1: {
-                    set_cursor_pos(0, 0);
-                    bool b = false;
-                    for (size_t yc = 0; yc < term_rows; yc++) {
-                        for (size_t xc = 0; xc < term_cols; xc++) {
-                            raw_putchar(' ');
-                            if (xc == x && yc == y) {
-                                set_cursor_pos(x, y);
-                                b = true;
-                                break;
-                            }
-                        }
-                        if (b == true)
-                            break;
-                    }
-                    break;
-                }
-                case 2:
-                case 3:
-                    clear(false);
-                    break;
-            }
-            break;
-        case '@':
-            for (size_t i = term_cols - 1; ; i--) {
-                term_move_character(i + esc_values[0], y, i, y);
-                set_cursor_pos(i, y);
-                raw_putchar(' ');
-                if (i == x) {
-                    break;
-                }
-            }
-            set_cursor_pos(x, y);
-            break;
-        case 'P':
-            for (size_t i = x + esc_values[0]; i < term_cols; i++)
-                term_move_character(i - esc_values[0], y, i, y);
-            set_cursor_pos(term_cols - esc_values[0], y);
-            // FALLTHRU
-        case 'X':
-            for (size_t i = 0; i < esc_values[0]; i++)
-                raw_putchar(' ');
-            set_cursor_pos(x, y);
-            break;
-        case 'm':
-            sgr();
-            break;
-        case 's':
-            get_cursor_pos(&saved_cursor_x, &saved_cursor_y);
-            break;
-        case 'u':
-            set_cursor_pos(saved_cursor_x, saved_cursor_y);
-            break;
-        case 'K':
-            switch (esc_values[0]) {
-                case 0: {
-                    for (size_t i = x; i < term_cols; i++)
-                        raw_putchar(' ');
-                    set_cursor_pos(x, y);
-                    break;
-                }
-                case 1: {
-                    set_cursor_pos(0, y);
-                    for (size_t i = 0; i < x; i++)
-                        raw_putchar(' ');
-                    break;
-                }
-                case 2: {
-                    set_cursor_pos(0, y);
-                    for (size_t i = 0; i < term_cols; i++)
-                        raw_putchar(' ');
-                    set_cursor_pos(x, y);
-                    break;
-                }
-            }
-            break;
-        case 'r':
-            scroll_top_margin = 0;
-            scroll_bottom_margin = term_rows;
-            if (esc_values_i > 0) {
-                scroll_top_margin = esc_values[0] - 1;
-            }
-            if (esc_values_i > 1) {
-                scroll_bottom_margin = esc_values[1];
-            }
-            if (scroll_top_margin >= term_rows
-             || scroll_bottom_margin > term_rows
-             || scroll_top_margin >= (scroll_bottom_margin - 1)) {
-                scroll_top_margin = 0;
-                scroll_bottom_margin = term_rows;
-            }
-            set_cursor_pos(0, 0);
-            break;
-        case 'l':
-        case 'h':
-            mode_toggle(c);
-            break;
-        case ']':
-            linux_private_parse();
-            break;
-    }
-
-    if (r)
-        scroll_enable();
-
-cleanup:
-    control_sequence = false;
-    escape = false;
-}
-
-static void restore_state(void) {
-    bold = saved_state_bold;
-    reverse_video = saved_state_reverse_video;
-    current_charset = saved_state_current_charset;
-    current_primary = saved_state_current_primary;
-
-    term_restore_state();
-}
-
-static void save_state(void) {
-    term_save_state();
-
-    saved_state_bold = bold;
-    saved_state_reverse_video = reverse_video;
-    saved_state_current_charset = current_charset;
-    saved_state_current_primary = current_primary;
-}
-
-static void escape_parse(uint8_t c) {
-    escape_offset++;
-
-    if (control_sequence == true) {
-        control_sequence_parse(c);
-        return;
-    }
-
-    if (csi == true) {
-        csi = false;
-        goto is_csi;
-    }
-
-    size_t x, y;
-    get_cursor_pos(&x, &y);
-
-    switch (c) {
-        case '[':
-is_csi:
-            for (size_t i = 0; i < MAX_ESC_VALUES; i++)
-                esc_values[i] = 0;
-            esc_values_i = 0;
-            rrr = false;
-            control_sequence = true;
-            return;
-        case '7':
-            save_state();
-            break;
-        case '8':
-            restore_state();
-            break;
-        case 'c':
-            term_reinit();
-            clear(true);
-            break;
-        case 'D':
-            if (y == scroll_bottom_margin - 1) {
-                term_scroll();
-                set_cursor_pos(x, y);
-            } else {
-                set_cursor_pos(x, y + 1);
-            }
-            break;
-        case 'E':
-            if (y == scroll_bottom_margin - 1) {
-                term_scroll();
-                set_cursor_pos(0, y);
-            } else {
-                set_cursor_pos(0, y + 1);
-            }
-            break;
-        case 'M':
-            // "Reverse linefeed"
-            if (y == scroll_top_margin) {
-                term_revscroll();
-                set_cursor_pos(0, y);
-            } else {
-                set_cursor_pos(0, y - 1);
-            }
-            break;
-        case 'Z':
-            if (term_callback != NULL) {
-                if (term_arg != 0) {
-                    term_callback(term_arg, TERM_CB_PRIVATE_ID, 0, 0, 0);
-                } else {
-                    term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0, 0);
-                }
-            }
-            break;
-        case '(':
-        case ')':
-            g_select = c - '\'';
-            break;
-        case '\e':
-            if (term_runtime == false) {
-                raw_putchar(c);
-            }
-            break;
-    }
-
-    escape = false;
-}
-
-static uint8_t dec_special_to_cp437(uint8_t c) {
-    switch (c) {
-        case '`': return 0x04;
-        case '0': return 0xdb;
-        case '-': return 0x18;
-        case ',': return 0x1b;
-        case '.': return 0x19;
-        case 'a': return 0xb1;
-        case 'f': return 0xf8;
-        case 'g': return 0xf1;
-        case 'h': return 0xb0;
-        case 'j': return 0xd9;
-        case 'k': return 0xbf;
-        case 'l': return 0xda;
-        case 'm': return 0xc0;
-        case 'n': return 0xc5;
-        case 'q': return 0xc4;
-        case 's': return 0x5f;
-        case 't': return 0xc3;
-        case 'u': return 0xb4;
-        case 'v': return 0xc1;
-        case 'w': return 0xc2;
-        case 'x': return 0xb3;
-        case 'y': return 0xf3;
-        case 'z': return 0xf2;
-        case '~': return 0xfa;
-        case '_': return 0xff;
-        case '+': return 0x1a;
-        case '{': return 0xe3;
-        case '}': return 0x9c;
-    }
-
-    return c;
-}
-
-static void term_putchar(uint8_t c) {
-    if (discard_next || (term_runtime == true && (c == 0x18 || c == 0x1a))) {
-        discard_next = false;
-        escape = false;
-        csi = false;
-        control_sequence = false;
-        g_select = 0;
-        return;
-    }
-
-    if (escape == true) {
-        escape_parse(c);
-        return;
-    }
-
-    if (g_select) {
-        g_select--;
-        switch (c) {
-            case 'B':
-                charsets[g_select] = CHARSET_DEFAULT; break;
-            case '0':
-                charsets[g_select] = CHARSET_DEC_SPECIAL; break;
-        }
-        g_select = 0;
-        return;
-    }
-
-    size_t x, y;
-    get_cursor_pos(&x, &y);
-
-    switch (c) {
-        case 0x00:
-        case 0x7f:
-            return;
-        case 0x9b:
-            csi = true;
-            // FALLTHRU
-        case '\e':
-            escape_offset = 0;
-            escape = true;
-            return;
-        case '\t':
-            if ((x / TERM_TABSIZE + 1) >= term_cols) {
-                set_cursor_pos(term_cols - 1, y);
-                return;
-            }
-            set_cursor_pos((x / TERM_TABSIZE + 1) * TERM_TABSIZE, y);
-            return;
-        case 0x0b:
-        case 0x0c:
-        case '\n':
-            if (y == scroll_bottom_margin - 1) {
-                term_scroll();
-                set_cursor_pos(0, y);
-            } else {
-                set_cursor_pos(0, y + 1);
-            }
-            return;
-        case '\b':
-            set_cursor_pos(x - 1, y);
-            return;
-        case '\r':
-            set_cursor_pos(0, y);
-            return;
-        case '\a':
-            // The bell is handled by the kernel
-            if (term_callback != NULL) {
-                if (term_arg != 0) {
-                    term_callback(term_arg, TERM_CB_BELL, 0, 0, 0);
-                } else {
-                    term_callback(TERM_CB_BELL, 0, 0, 0, 0);
-                }
-            }
-            return;
-        case 14:
-            // Move to G1 set
-            current_charset = 1;
-            return;
-        case 15:
-            // Move to G0 set
-            current_charset = 0;
-            return;
-    }
-
-    if (insert_mode == true) {
-        for (size_t i = term_cols - 1; ; i--) {
-            term_move_character(i + 1, y, i, y);
-            if (i == x) {
-                break;
-            }
-        }
-    }
-
-    // Translate character set
-    switch (charsets[current_charset]) {
-        case CHARSET_DEFAULT:
-            break;
-        case CHARSET_DEC_SPECIAL:
-            c = dec_special_to_cp437(c);
+    if (autoflush) {
+        term->double_buffer_flush(term);
     }
 
-    raw_putchar(c);
+    term->autoflush = autoflush;
 }
diff --git a/common/lib/term.h b/common/lib/term.h
index 82ff658d..f7fdbf11 100644
--- a/common/lib/term.h
+++ b/common/lib/term.h
@@ -6,37 +6,7 @@
 #include <stdbool.h>
 #include <lib/image.h>
 #include <lib/print.h>
-
-#define TERM_TABSIZE (8)
-#define MAX_ESC_VALUES (16)
-
-extern struct term_context {
-    bool control_sequence;
-    bool csi;
-    bool escape;
-    bool rrr;
-    bool discard_next;
-    bool bold;
-    bool reverse_video;
-    bool dec_private;
-    bool insert_mode;
-    uint8_t g_select;
-    uint8_t charsets[2];
-    size_t current_charset;
-    size_t escape_offset;
-    size_t esc_values_i;
-    size_t saved_cursor_x;
-    size_t saved_cursor_y;
-    size_t current_primary;
-    size_t scroll_top_margin;
-    size_t scroll_bottom_margin;
-    uint32_t esc_values[MAX_ESC_VALUES];
-
-    bool saved_state_bold;
-    bool saved_state_reverse_video;
-    size_t saved_state_current_charset;
-    size_t saved_state_current_primary;
-} term_context;
+#include <term/term.h>
 
 enum {
     _NOT_READY,
@@ -47,76 +17,29 @@ enum {
 
 extern int current_video_mode;
 extern int term_backend;
-extern size_t term_rows, term_cols;
-extern bool term_runtime;
-
-void term_notready(void);
-void term_fallback(void);
-
-void term_reinit(void);
-void term_deinit(void);
-void term_vbe(char *config, size_t width, size_t height);
-void term_textmode(void);
-void term_write(uint64_t buf, uint64_t count);
-
-extern void (*raw_putchar)(uint8_t c);
-extern void (*clear)(bool move);
-extern void (*enable_cursor)(void);
-extern bool (*disable_cursor)(void);
-extern void (*set_cursor_pos)(size_t x, size_t y);
-extern void (*get_cursor_pos)(size_t *x, size_t *y);
-extern void (*set_text_fg)(size_t fg);
-extern void (*set_text_bg)(size_t bg);
-extern void (*set_text_fg_bright)(size_t fg);
-extern void (*set_text_bg_bright)(size_t bg);
-extern void (*set_text_fg_rgb)(uint32_t fg);
-extern void (*set_text_bg_rgb)(uint32_t bg);
-extern void (*set_text_fg_default)(void);
-extern void (*set_text_bg_default)(void);
-extern bool (*scroll_disable)(void);
-extern void (*scroll_enable)(void);
-extern void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
-extern void (*term_scroll)(void);
-extern void (*term_revscroll)(void);
-extern void (*term_swap_palette)(void);
-extern void (*term_save_state)(void);
-extern void (*term_restore_state)(void);
 
-extern void (*term_double_buffer_flush)(void);
-
-extern uint64_t (*term_context_size)(void);
-extern void (*term_context_save)(uint64_t ptr);
-extern void (*term_context_restore)(uint64_t ptr);
-extern void (*term_full_refresh)(void);
-
-#define TERM_CB_DEC 10
-#define TERM_CB_BELL 20
-#define TERM_CB_PRIVATE_ID 30
-#define TERM_CB_STATUS_REPORT 40
-#define TERM_CB_POS_REPORT 50
-#define TERM_CB_KBD_LEDS 60
-#define TERM_CB_MODE 70
-#define TERM_CB_LINUX 80
+extern struct term_context *term;
 
 #define TERM_CTX_SIZE ((uint64_t)(-1))
 #define TERM_CTX_SAVE ((uint64_t)(-2))
 #define TERM_CTX_RESTORE ((uint64_t)(-3))
 #define TERM_FULL_REFRESH ((uint64_t)(-4))
 
-extern uint64_t term_arg;
-extern void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
-
-extern bool term_autoflush;
-
 inline void reset_term(void) {
-    term_autoflush = true;
-    enable_cursor();
+    term->autoflush = true;
+    term->enable_cursor(term);
     print("\e[2J\e[H");
-    term_double_buffer_flush();
+    term->double_buffer_flush(term);
 }
 
 inline void set_cursor_pos_helper(size_t x, size_t y) {
     print("\e[%u;%uH", (int)y + 1, (int)x + 1);
 }
 
+void term_fallback(void);
+void term_vbe(char *config, size_t width, size_t height);
+void term_textmode(void);
+
+void _term_write(uint64_t buf, uint64_t count);
+
 #endif
diff --git a/common/linker_bios.ld.in b/common/linker_bios.ld.in
index 74562944..3e0132b4 100644
--- a/common/linker_bios.ld.in
+++ b/common/linker_bios.ld.in
@@ -43,6 +43,7 @@ SECTIONS
         term_write = .;
         term_backend = .;
         term_fallback = .;
+        term = .;
         stage3_addr = .;
 #else
 #ifdef LINKER_NOS2MAP
diff --git a/common/menu.c b/common/menu.c
index afe0fc33..6bc8f3e2 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -154,17 +154,17 @@ static void putchar_tokencol(int type, char c) {
 static bool editor_no_term_reset = false;
 
 char *config_entry_editor(const char *title, const char *orig_entry) {
-    term_autoflush = false;
+    term->autoflush = false;
 
-    enable_cursor();
+    term->enable_cursor(term);
 
     print("\e[2J\e[H");
 
     size_t cursor_offset  = 0;
     size_t entry_size     = strlen(orig_entry);
-    size_t _window_size   = term_rows - 8;
+    size_t _window_size   = term->rows - 8;
     size_t window_offset  = 0;
-    size_t line_size      = term_cols - 2;
+    size_t line_size      = term->cols - 2;
 
     bool display_overflow_error = false;
 
@@ -200,12 +200,12 @@ refresh:
     invalid_syntax = false;
 
     print("\e[2J\e[H");
-    disable_cursor();
+    term->disable_cursor(term);
     {
         size_t x, y;
         print("\n");
-        get_cursor_pos(&x, &y);
-        set_cursor_pos_helper(term_cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
+        term->get_cursor_pos(term, &x, &y);
+        set_cursor_pos_helper(term->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
         print("\e[3%sm%s\e[37m", menu_branding_colour, menu_branding);
         print("\n\n");
     }
@@ -213,7 +213,7 @@ refresh:
     print("    \e[32mESC\e[0m Discard and Exit    \e[32mF10\e[0m Boot\n\n");
 
     print(serial ? "/" : "\xda");
-    for (size_t i = 0; i < term_cols - 2; i++) {
+    for (size_t i = 0; i < term->cols - 2; i++) {
         switch (i) {
             case 1: case 2: case 3:
                 if (window_offset > 0) {
@@ -223,7 +223,7 @@ refresh:
                 // FALLTHRU
             default: {
                 size_t title_length = strlen(title);
-                if (i == (term_cols / 2) - DIV_ROUNDUP(title_length, 2) - 1) {
+                if (i == (term->cols / 2) - DIV_ROUNDUP(title_length, 2) - 1) {
                     print("%s", title);
                     i += title_length - 1;
                 } else {
@@ -234,7 +234,7 @@ refresh:
     }
     size_t tmpx, tmpy;
 
-    get_cursor_pos(&tmpx, &tmpy);
+    term->get_cursor_pos(term, &tmpx, &tmpy);
     print(serial ? "\\" : "\xbf");
     set_cursor_pos_helper(0, tmpy + 1);
     print(serial ? "|" : "\xb3");
@@ -250,20 +250,20 @@ refresh:
          && current_line <  window_offset + window_size
          && current_line >= window_offset) {
             size_t x, y;
-            get_cursor_pos(&x, &y);
+            term->get_cursor_pos(term, &x, &y);
             if (i == cursor_offset) {
                 cursor_x = x;
                 cursor_y = y;
                 printed_cursor = true;
             }
-            set_cursor_pos_helper(term_cols - 1, y);
+            set_cursor_pos_helper(term->cols - 1, y);
             if (current_line == window_offset + window_size - 1) {
-                get_cursor_pos(&tmpx, &tmpy);
+                term->get_cursor_pos(term, &tmpx, &tmpy);
                 print(serial ? "|" : "\xb3");
                 set_cursor_pos_helper(0, tmpy + 1);
                 print(serial ? "\\" : "\xc0");
             } else {
-                get_cursor_pos(&tmpx, &tmpy);
+                term->get_cursor_pos(term, &tmpx, &tmpy);
                 print(serial ? "|" : "\xb3");
                 set_cursor_pos_helper(0, tmpy + 1);
                 print(serial ? "|" : "\xb3");
@@ -281,7 +281,7 @@ refresh:
             if (current_line <  window_offset + window_size
              && current_line >= window_offset) {
                 if (i == cursor_offset) {
-                    get_cursor_pos(&cursor_x, &cursor_y);
+                    term->get_cursor_pos(term, &cursor_x, &cursor_y);
                     printed_cursor = true;
                 }
                 if (syntax_highlighting_enabled) {
@@ -291,8 +291,8 @@ refresh:
                 }
                 printed_early = true;
                 size_t x, y;
-                get_cursor_pos(&x, &y);
-                if (y == term_rows - 3) {
+                term->get_cursor_pos(term, &x, &y);
+                if (y == term->rows - 3) {
                     print(serial ? ">" : "\x1a");
                     set_cursor_pos_helper(0, y + 1);
                     print(serial ? "\\" : "\xc0");
@@ -309,7 +309,7 @@ refresh:
          && current_line <  window_offset + window_size
          && current_line >= window_offset
          && !printed_cursor) {
-            get_cursor_pos(&cursor_x, &cursor_y);
+            term->get_cursor_pos(term, &cursor_x, &cursor_y);
             printed_cursor = true;
         }
 
@@ -356,35 +356,35 @@ refresh:
     // syntax error alert
     if (validation_enabled) {
         size_t x, y;
-        get_cursor_pos(&x, &y);
-        set_cursor_pos_helper(0, term_rows-1);
-        scroll_disable();
+        term->get_cursor_pos(term, &x, &y);
+        set_cursor_pos_helper(0, term->rows - 1);
+        term->scroll_enabled = false;
         if (invalid_syntax) {
             print("\e[31mConfiguration is INVALID.\e[0m");
         } else {
             print("\e[32mConfiguration is valid.\e[0m");
         }
-        scroll_enable();
+        term->scroll_enabled = true;
         set_cursor_pos_helper(x, y);
     }
 
     if (current_line - window_offset < window_size) {
         size_t x, y;
         for (size_t i = 0; i < (window_size - (current_line - window_offset)) - 1; i++) {
-            get_cursor_pos(&x, &y);
-            set_cursor_pos_helper(term_cols - 1, y);
+            term->get_cursor_pos(term, &x, &y);
+            set_cursor_pos_helper(term->cols - 1, y);
             print(serial ? "|" : "\xb3");
             set_cursor_pos_helper(0, y + 1);
             print(serial ? "|" : "\xb3");
         }
-        get_cursor_pos(&x, &y);
-        set_cursor_pos_helper(term_cols - 1, y);
+        term->get_cursor_pos(term, &x, &y);
+        set_cursor_pos_helper(term->cols - 1, y);
         print(serial ? "|" : "\xb3");
         set_cursor_pos_helper(0, y + 1);
         print(serial ? "\\" : "\xc0");
     }
 
-    for (size_t i = 0; i < term_cols - 2; i++) {
+    for (size_t i = 0; i < term->cols - 2; i++) {
         switch (i) {
             case 1: case 2: case 3:
                 if (current_line - window_offset >= window_size) {
@@ -396,22 +396,22 @@ refresh:
                 print(serial ? "-" : "\xc4");
         }
     }
-    get_cursor_pos(&tmpx, &tmpy);
+    term->get_cursor_pos(term, &tmpx, &tmpy);
     print(serial ? "/" : "\xd9");
     set_cursor_pos_helper(0, tmpy + 1);
 
     if (display_overflow_error) {
-        scroll_disable();
+        term->scroll_enabled = false;
         print("\e[31mText buffer not big enough, delete something instead.");
-        scroll_enable();
+        term->scroll_enabled = true;
         display_overflow_error = false;
     }
 
     // Hack to redraw the cursor
     set_cursor_pos_helper(cursor_x, cursor_y);
-    enable_cursor();
+    term->enable_cursor(term);
 
-    term_double_buffer_flush();
+    term->double_buffer_flush(term);
 
     int c = getchar();
     size_t buffer_len = strlen(buffer);
@@ -689,16 +689,16 @@ noreturn void _menu(bool first_run) {
     menu_init_term();
 
 refresh:
-    term_autoflush = false;
+    term->autoflush = false;
 
-    disable_cursor();
+    term->disable_cursor(term);
 
     print("\e[2J\e[H");
     {
         size_t x, y;
         print("\n");
-        get_cursor_pos(&x, &y);
-        set_cursor_pos_helper(term_cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
+        term->get_cursor_pos(term, &x, &y);
+        set_cursor_pos_helper(term->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
         print("\e[3%sm%s\e[37m", menu_branding_colour, menu_branding);
         print("\n\n\n\n");
     }
@@ -707,14 +707,14 @@ refresh:
         if (quiet) {
             quiet = false;
             menu_init_term();
-            term_autoflush = false;
-            disable_cursor();
+            term->autoflush = false;
+            term->disable_cursor(term);
         }
         print("Config file %s.\n\n", config_ready ? "contains no valid entries" : "not found");
         print("For information on the format of Limine config entries, consult CONFIG.md in\n");
         print("the root of the Limine source repository.\n\n");
         print("Press a key to enter the Limine console...");
-        term_double_buffer_flush();
+        term->double_buffer_flush(term);
         getchar();
         reset_term();
         console();
@@ -722,24 +722,24 @@ refresh:
 
     {   // Draw box around boot menu
         size_t x, y;
-        get_cursor_pos(&x, &y);
+        term->get_cursor_pos(term, &x, &y);
 
         print(serial ? "/" : "\xda");
-        for (size_t i = 0; i < term_cols - 2; i++) {
+        for (size_t i = 0; i < term->cols - 2; i++) {
             print(serial ? "-" : "\xc4");
         }
         print(serial ? "\\" : "\xbf");
 
-        for (size_t i = y + 1; i < term_rows - 2; i++) {
+        for (size_t i = y + 1; i < term->rows - 2; i++) {
             set_cursor_pos_helper(0, i);
             print(serial ? "|" : "\xb3");
-            set_cursor_pos_helper(term_cols - 1, i);
+            set_cursor_pos_helper(term->cols - 1, i);
             print(serial ? "|" : "\xb3");
         }
-        set_cursor_pos_helper(0, term_rows - 2);
+        set_cursor_pos_helper(0, term->rows - 2);
 
         print(serial ? "\\" : "\xc0");
-        for (size_t i = 0; i < term_cols - 2; i++) {
+        for (size_t i = 0; i < term->cols - 2; i++) {
             print(serial ? "-" : "\xc4");
         }
         print(serial ? "/" : "\xd9");
@@ -752,7 +752,7 @@ refresh:
 
     {
         size_t x, y;
-        get_cursor_pos(&x, &y);
+        term->get_cursor_pos(term, &x, &y);
         set_cursor_pos_helper(0, 3);
         if (editor_enabled && selected_menu_entry->sub == NULL) {
             print("    \e[32mARROWS\e[0m Select    \e[32mENTER\e[0m Boot    \e[32mE\e[0m Edit");
@@ -760,7 +760,7 @@ refresh:
             print("    \e[32mARROWS\e[0m Select    \e[32mENTER\e[0m %s",
                   selected_menu_entry->expanded ? "Collapse" : "Expand");
         }
-        set_cursor_pos_helper(term_cols - 13, 3);
+        set_cursor_pos_helper(term->cols - 13, 3);
         print("\e[32mC\e[0m Console");
         set_cursor_pos_helper(x, y);
     }
@@ -773,11 +773,11 @@ refresh:
     if (skip_timeout == false) {
         print("\n\n");
         for (size_t i = timeout; i; i--) {
-            set_cursor_pos_helper(0, term_rows - 1);
-            scroll_disable();
+            set_cursor_pos_helper(0, term->rows - 1);
+            term->scroll_enabled = false;
             print("\e[2K\e[32mBooting automatically in \e[92m%u\e[32m, press any key to stop the countdown...\e[0m", i);
-            scroll_enable();
-            term_double_buffer_flush();
+            term->scroll_enabled = true;
+            term->double_buffer_flush(term);
             if ((c = pit_sleep_and_quit_on_keypress(1))) {
                 skip_timeout = true;
                 if (quiet) {
@@ -786,21 +786,21 @@ refresh:
                     goto timeout_aborted;
                 }
                 print("\e[2K");
-                term_double_buffer_flush();
+                term->double_buffer_flush(term);
                 goto timeout_aborted;
             }
         }
         goto autoboot;
     }
 
-    set_cursor_pos_helper(0, term_rows - 1);
+    set_cursor_pos_helper(0, term->rows - 1);
     if (selected_menu_entry->comment != NULL) {
-        scroll_disable();
+        term->scroll_enabled = false;
         print("\e[36m%s\e[0m", selected_menu_entry->comment);
-        scroll_enable();
+        term->scroll_enabled = true;
     }
 
-    term_double_buffer_flush();
+    term->double_buffer_flush(term);
 
     for (;;) {
         c = getchar();
diff --git a/common/protos/chainload.c b/common/protos/chainload.c
index 80c57877..b2f0b716 100644
--- a/common/protos/chainload.c
+++ b/common/protos/chainload.c
@@ -217,7 +217,7 @@ noreturn void efi_chainload_file(char *config, struct file_handle *image) {
     pmm_free(_ptr, image->size);
     fclose(image);
 
-    term_deinit();
+    term->deinit(term, pmm_free);
 
     size_t req_width = 0, req_height = 0, req_bpp = 0;
 
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 97a6ba3c..77a079b9 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -269,7 +269,7 @@ void limine_term_callback(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
 
 static void term_write_shim(uint64_t context, uint64_t buf, uint64_t count) {
     (void)context;
-    term_write(buf, count);
+    _term_write(buf, count);
 }
 
 noreturn void limine_load(char *config, char *cmdline) {
@@ -697,17 +697,15 @@ FEAT_START
 
     if (terminal_request->callback != 0) {
 #if defined (__i386__)
-        term_callback = limine_term_callback;
+        term->callback = (void *)limine_term_callback;
         limine_term_callback_ptr = terminal_request->callback;
 #elif defined (__x86_64__) || defined (__aarch64__)
-        term_callback = (void *)terminal_request->callback;
+        term->callback = (void *)terminal_request->callback;
 #else
 #error Unknown architecture
 #endif
     }
 
-    term_arg = reported_addr(terminal);
-
 #if defined (__i386__)
     if (limine_rt_stack == NULL) {
         limine_rt_stack = ext_mem_alloc(16384) + 16384;
@@ -723,8 +721,8 @@ FEAT_START
 
     term_fb_ptr = &terminal->framebuffer;
 
-    terminal->columns = term_cols;
-    terminal->rows = term_rows;
+    terminal->columns = term->cols;
+    terminal->rows = term->rows;
 
     uint64_t *term_list = ext_mem_alloc(1 * sizeof(uint64_t));
     term_list[0] = reported_addr(terminal);
@@ -737,7 +735,7 @@ FEAT_START
     goto skip_fb_init;
 FEAT_END
 
-    term_deinit();
+    term->deinit(term, pmm_free);
 
     if (!fb_init(&fb, req_width, req_height, req_bpp)) {
         panic(true, "limine: Could not acquire framebuffer");
@@ -995,9 +993,9 @@ FEAT_START
 FEAT_END
 
     // Clear terminal for kernels that will use the Limine terminal
-    term_write((uint64_t)(uintptr_t)("\e[2J\e[H"), 7);
+    term_write(term, "\e[2J\e[H", 7);
 
-    term_runtime = true;
+    term->in_bootloader = false;
 
 #if defined (__x86_64__) || defined (__i386__)
 #if defined (BIOS)
diff --git a/common/protos/linux.c b/common/protos/linux.c
index e4c935a9..3eaee4dd 100644
--- a/common/protos/linux.c
+++ b/common/protos/linux.c
@@ -495,7 +495,7 @@ noreturn void linux_load(char *config, char *cmdline) {
     // Video
     ///////////////////////////////////////
 
-    term_deinit();
+    term->deinit(term, pmm_free);
 
     struct screen_info *screen_info = &boot_params->screen_info;
 
diff --git a/common/protos/multiboot1.c b/common/protos/multiboot1.c
index 18dedfa3..a870a4ae 100644
--- a/common/protos/multiboot1.c
+++ b/common/protos/multiboot1.c
@@ -302,7 +302,7 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
     multiboot1_info->bootloader_name = (uint32_t)(size_t)lowmem_bootname - mb1_info_slide;
     multiboot1_info->flags |= (1 << 9);
 
-    term_deinit();
+    term->deinit(term, pmm_free);
 
     if (header.flags & (1 << 2)) {
         size_t req_width  = header.fb_width;
diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c
index 949c2323..e92e2a4f 100644
--- a/common/protos/multiboot2.c
+++ b/common/protos/multiboot2.c
@@ -505,7 +505,7 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
         tag->common.type = MULTIBOOT_TAG_TYPE_FRAMEBUFFER;
         tag->common.size = sizeof(struct multiboot_tag_framebuffer);
 
-        term_deinit();
+        term->deinit(term, pmm_free);
 
         if (fbtag) {
             size_t req_width = fbtag->width;
tab: 248 wrap: offon