:: commit 689e82dba7479049a598d7fa2034d378447b856e

mintsuki <mintsuki@protonmail.com> — 2021-03-02 05:05

parents: d4ea15e39e

vbe: Move graphical terminal code to gterm and ship font instead of dumping from graphics card

diff --git a/stage23/Makefile b/stage23/Makefile
index f6148b59..c753cee3 100644
--- a/stage23/Makefile
+++ b/stage23/Makefile
@@ -12,7 +12,7 @@ LIMINE_VERSION := $(shell git describe --exact-match --tags `git log -n1 --prett
 WERROR = -Werror
 CFLAGS = -Os -pipe -Wall -Wextra $(WERROR)
 
-INTERNAL_CFLAGS = \
+INTERNAL_CFLAGS := \
 	-std=gnu11 \
 	-fplan9-extensions \
 	-ffreestanding \
@@ -21,11 +21,7 @@ INTERNAL_CFLAGS = \
 	-fno-omit-frame-pointer \
 	-Wno-address-of-packed-member \
 	-masm=intel \
-	-mno-80387 \
-	-mno-mmx \
-	-mno-3dnow \
-	-mno-sse \
-	-mno-sse2 \
+	-mgeneral-regs-only \
 	-MMD \
 	-DBUILD_ID=$(BUILD_ID) \
 	-DLIMINE_VERSION='"$(LIMINE_VERSION)"' \
@@ -36,7 +32,7 @@ INTERNAL_CFLAGS = \
 
 LDFLAGS = -Os
 
-INTERNAL_LDFLAGS = \
+INTERNAL_LDFLAGS := \
 	-lgcc \
 	-static-libgcc \
 	-nostdlib \
@@ -66,13 +62,16 @@ limine.sys: limine.elf
 	$(OBJCOPY) -O binary $< $@
 
 limine_nomap.elf: $(OBJ)
-	$(LD) $(OBJ) $(LDFLAGS) $(INTERNAL_LDFLAGS) -Tlinker_nomap.ld -o $@
-	$(LD) $(OBJ) $(LDFLAGS) $(INTERNAL_LDFLAGS) -Wl,--gc-sections -Tlinker_stage2only.ld -o limine_stage2only.elf || \
+	$(LD) $(OBJ) font.o $(LDFLAGS) $(INTERNAL_LDFLAGS) -Tlinker_nomap.ld -o $@
+	$(LD) $(OBJ) font.o $(LDFLAGS) $(INTERNAL_LDFLAGS) -Wl,--gc-sections -Tlinker_stage2only.ld -o limine_stage2only.elf || \
 		( echo "This error means that stage 2 was trying to use stage 3 symbols before loading stage 3" && \
 		  false )
 
-limine.elf: $(OBJ) limine.map.o
-	$(LD) $(OBJ) limine.map.o $(LDFLAGS) $(INTERNAL_LDFLAGS) -Tlinker.ld -o $@
+font.o:
+	$(OBJCOPY) -B i8086 -I binary -O default font.bin $@
+
+limine.elf: $(OBJ) font.o limine.map.o
+	$(LD) $(OBJ) font.o limine.map.o $(LDFLAGS) $(INTERNAL_LDFLAGS) -Tlinker.ld -o $@
 
 -include $(HEADER_DEPS)
 
@@ -83,4 +82,4 @@ limine.elf: $(OBJ) limine.map.o
 	nasm $< -f elf32 -o $@
 
 clean:
-	rm -f limine.elf limine_nomap.elf limine_stage2only.elf limine.map.o limine.sys stage2.bin stage2.bin.gz $(OBJ) $(HEADER_DEPS)
+	rm -f limine.elf limine_nomap.elf limine_stage2only.elf font.o limine.map.o limine.sys stage2.bin stage2.bin.gz $(OBJ) $(HEADER_DEPS)
diff --git a/stage23/drivers/vbe.c b/stage23/drivers/vbe.c
index d5b2f5cf..726cc49d 100644
--- a/stage23/drivers/vbe.c
+++ b/stage23/drivers/vbe.c
@@ -12,416 +12,6 @@
 #include <mm/pmm.h>
 #include <mm/mtrr.h>
 
-#define VGA_FONT_WIDTH  8
-#define VGA_FONT_HEIGHT 16
-#define VGA_FONT_GLYPHS 256
-#define VGA_FONT_MAX    (VGA_FONT_HEIGHT * VGA_FONT_GLYPHS)
-
-static uint8_t *vga_font;
-
-static void vga_font_retrieve(void) {
-    struct rm_regs r = {0};
-
-    r.eax = 0x1130;
-    r.ebx = 0x0600;
-    rm_int(0x10, &r, &r);
-
-    vga_font = ext_mem_alloc(VGA_FONT_MAX);
-
-    memcpy(vga_font, (void *)rm_desegment(r.es, r.ebp), VGA_FONT_MAX);
-}
-
-static uint32_t ansi_colours[8];
-
-static struct vbe_framebuffer_info fbinfo;
-static uint32_t *vbe_framebuffer;
-static uint16_t  vbe_pitch;
-static uint16_t  vbe_width;
-static uint16_t  vbe_height;
-static uint16_t  vbe_bpp;
-
-static int frame_height, frame_width;
-
-static struct image *background;
-
-static struct vbe_char *grid;
-static struct vbe_char *front_grid;
-
-static bool double_buffer_enabled = false;
-
-static bool cursor_status = true;
-
-static int cursor_x;
-static int cursor_y;
-
-static uint32_t cursor_fg = 0x00000000;
-static uint32_t cursor_bg = 0x00ffffff;
-static uint32_t text_fg;
-static uint32_t text_bg;
-
-static int rows;
-static int cols;
-static int margin_gradient;
-
-#define A(rgb) (uint8_t)(rgb >> 24)
-#define R(rgb) (uint8_t)(rgb >> 16)
-#define G(rgb) (uint8_t)(rgb >> 8)
-#define B(rgb) (uint8_t)(rgb)
-#define ARGB(a, r, g, b) (a << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)
-
-static inline uint32_t colour_blend(uint32_t fg, uint32_t bg) {
-    unsigned alpha = 255 - A(fg);
-    unsigned inv_alpha = A(fg) + 1;
-
-    uint8_t r = (uint8_t)((alpha * R(fg) + inv_alpha * R(bg)) / 256);
-    uint8_t g = (uint8_t)((alpha * G(fg) + inv_alpha * G(bg)) / 256);
-    uint8_t b = (uint8_t)((alpha * B(fg) + inv_alpha * B(bg)) / 256);
-
-    return ARGB(0, r, g, b);
-}
-
-void vbe_plot_px(int x, int y, uint32_t hex) {
-    size_t fb_i = x + (vbe_pitch / sizeof(uint32_t)) * y;
-
-    vbe_framebuffer[fb_i] = hex;
-}
-
-static void _vbe_plot_bg_blent_px(int x, int y, uint32_t hex) {
-    vbe_plot_px(x, y, colour_blend(hex, background->get_pixel(background, x, y)));
-}
-
-void (*vbe_plot_bg_blent_px)(int x, int y, uint32_t hex) = vbe_plot_px;
-
-static uint32_t blend_gradient_from_box(int x, int y, uint32_t hex) {
-    if (x >= frame_width  && x < frame_width  + VGA_FONT_WIDTH  * cols
-     && y >= frame_height && y < frame_height + VGA_FONT_HEIGHT * rows) {
-        return hex;
-    }
-
-    uint32_t bg_px = background->get_pixel(background, x, y);
-
-    if (margin_gradient == 0)
-        return bg_px;
-
-    int distance, x_distance, y_distance;
-
-    if (x < frame_width)
-        x_distance = frame_width - x;
-    else
-        x_distance = x - (frame_width + VGA_FONT_WIDTH * cols);
-
-    if (y < frame_height)
-        y_distance = frame_height - y;
-    else
-        y_distance = y - (frame_height + VGA_FONT_HEIGHT * rows);
-
-    if (x >= frame_width && x < frame_width + VGA_FONT_WIDTH * cols) {
-        distance = y_distance;
-    } else if (y >= frame_height && y < frame_height + VGA_FONT_HEIGHT * rows) {
-        distance = x_distance;
-    } else {
-        distance = sqrt((uint64_t)x_distance * (uint64_t)x_distance
-                      + (uint64_t)y_distance * (uint64_t)y_distance);
-    }
-
-    if (distance > margin_gradient)
-        return bg_px;
-
-    uint8_t gradient_step = (0xff - A(hex)) / margin_gradient;
-    uint8_t new_alpha     = A(hex) + gradient_step * distance;
-
-    return colour_blend((hex & 0xffffff) | (new_alpha << 24), bg_px);
-}
-
-void vbe_plot_background(int x, int y, int width, int height) {
-    if (background) {
-        for (int yy = 0; yy < height; yy++) {
-            for (int xx = 0; xx < width; xx++) {
-                vbe_plot_px(x + xx, y + yy, blend_gradient_from_box(xx, yy, text_bg));
-            }
-        }
-    } else {
-        for (int yy = 0; yy < height; yy++) {
-            for (int xx = 0; xx < width; xx++) {
-                vbe_plot_px(x + xx, y + yy, text_bg);
-            }
-        }
-    }
-}
-
-void vbe_plot_rect(int x, int y, int width, int height, uint32_t hex) {
-    for (int yy = 0; yy < height; yy++) {
-        for (int xx = 0; xx < width; xx++) {
-            vbe_plot_px(x + xx, y + yy, hex);
-        }
-    }
-}
-
-void vbe_plot_bg_blent_rect(int x, int y, int width, int height, uint32_t hex) {
-    for (int yy = 0; yy < height; yy++) {
-        for (int xx = 0; xx < width; xx++) {
-            vbe_plot_bg_blent_px(x + xx, y + yy, hex);
-        }
-    }
-}
-
-struct vbe_char {
-    uint32_t c;
-    uint32_t fg;
-    uint32_t bg;
-};
-
-void vbe_plot_char(struct vbe_char *c, int x, int y) {
-    uint8_t *glyph = &vga_font[(size_t)c->c * VGA_FONT_HEIGHT];
-
-    vbe_plot_bg_blent_rect(x, y, VGA_FONT_WIDTH, VGA_FONT_HEIGHT, c->bg);
-
-    for (int i = 0; i < VGA_FONT_HEIGHT; i++) {
-        for (int j = 0; j < VGA_FONT_WIDTH; j++) {
-            if ((glyph[i] & (0x80 >> j)))
-                vbe_plot_bg_blent_px(x + j, y + i, c->fg);
-        }
-    }
-}
-
-static void plot_char_grid(struct vbe_char *c, int x, int y) {
-    if (!double_buffer_enabled) {
-        vbe_plot_char(c, x * VGA_FONT_WIDTH + frame_width,
-                         y * VGA_FONT_HEIGHT + frame_height);
-    }
-    grid[x + y * cols] = *c;
-}
-
-static void clear_cursor(void) {
-    struct vbe_char c = grid[cursor_x + cursor_y * cols];
-    c.fg = text_fg;
-    c.bg = text_bg;
-    plot_char_grid(&c, cursor_x, cursor_y);
-}
-
-static void draw_cursor(void) {
-    if (cursor_status) {
-        struct vbe_char c = grid[cursor_x + cursor_y * cols];
-        c.fg = cursor_fg;
-        c.bg = cursor_bg;
-        plot_char_grid(&c, cursor_x, cursor_y);
-    }
-}
-
-static void scroll(void) {
-    clear_cursor();
-
-    for (int i = cols; i < rows * cols; i++) {
-        plot_char_grid(&grid[i], (i - cols) % cols, (i - cols) / cols);
-    }
-
-    // Clear the last line of the screen.
-    struct vbe_char empty;
-    empty.c  = ' ';
-    empty.fg = text_fg;
-    empty.bg = text_bg;
-    for (int i = rows * cols - cols; i < rows * cols; i++) {
-        plot_char_grid(&empty, i % cols, i / cols);
-    }
-
-    draw_cursor();
-}
-
-void vbe_clear(bool move) {
-    clear_cursor();
-
-    struct vbe_char empty;
-    empty.c  = ' ';
-    empty.fg = text_fg;
-    empty.bg = text_bg;
-    for (int i = 0; i < rows * cols; i++) {
-        plot_char_grid(&empty, i % cols, i / cols);
-    }
-
-    if (move) {
-        cursor_x = 0;
-        cursor_y = 0;
-    }
-
-    draw_cursor();
-}
-
-void vbe_enable_cursor(void) {
-    cursor_status = true;
-    draw_cursor();
-}
-
-void vbe_disable_cursor(void) {
-    clear_cursor();
-    cursor_status = false;
-}
-
-void vbe_set_cursor_pos(int x, int y) {
-    clear_cursor();
-    cursor_x = x;
-    cursor_y = y;
-    draw_cursor();
-}
-
-void vbe_get_cursor_pos(int *x, int *y) {
-    *x = cursor_x;
-    *y = cursor_y;
-}
-
-void vbe_set_text_fg(int fg) {
-    text_fg = ansi_colours[fg];
-}
-
-void vbe_set_text_bg(int bg) {
-    text_bg = ansi_colours[bg];
-}
-
-void vbe_double_buffer_flush(void) {
-    for (size_t i = 0; i < (size_t)rows * cols; i++) {
-        if (!memcmp(&grid[i], &front_grid[i], sizeof(struct vbe_char)))
-            continue;
-
-        front_grid[i] = grid[i];
-
-        int x = i % cols;
-        int y = i / cols;
-
-        vbe_plot_char(&grid[i], x * VGA_FONT_WIDTH + frame_width,
-                                y * VGA_FONT_HEIGHT + frame_height);
-    }
-}
-
-void vbe_double_buffer(bool state) {
-    if (state) {
-        memcpy(front_grid, grid, rows * cols * sizeof(struct vbe_char));
-        double_buffer_enabled = true;
-        vbe_clear(true);
-        vbe_double_buffer_flush();
-    } else {
-        bool pcs = cursor_status;
-        cursor_status = false;
-        vbe_clear(true);
-        vbe_double_buffer_flush();
-        cursor_status = pcs;
-        draw_cursor();
-        double_buffer_enabled = false;
-    }
-}
-
-void vbe_putchar(uint8_t c) {
-    switch (c) {
-        case '\b':
-            if (cursor_x || cursor_y) {
-                clear_cursor();
-                if (cursor_x) {
-                    cursor_x--;
-                } else {
-                    cursor_y--;
-                    cursor_x = cols - 1;
-                }
-                draw_cursor();
-            }
-            break;
-        case '\r':
-            vbe_set_cursor_pos(0, cursor_y);
-            break;
-        case '\n':
-            if (cursor_y == (rows - 1)) {
-                vbe_set_cursor_pos(0, rows - 1);
-                scroll();
-            } else {
-                vbe_set_cursor_pos(0, cursor_y + 1);
-            }
-            break;
-        default: {
-            clear_cursor();
-            struct vbe_char ch;
-            ch.c  = c;
-            ch.fg = text_fg;
-            ch.bg = text_bg;
-            plot_char_grid(&ch, cursor_x++, cursor_y);
-            if (cursor_x == cols) {
-                cursor_x = 0;
-                cursor_y++;
-            }
-            if (cursor_y == rows) {
-                cursor_y--;
-                scroll();
-            }
-            draw_cursor();
-            break;
-        }
-    }
-}
-
-bool vbe_tty_init(int *_rows, int *_cols, uint32_t *_colours, int _margin, int _margin_gradient, struct image *_background) {
-    int req_width = 0, req_height = 0, req_bpp = 0;
-
-    char *menu_resolution = config_get_value(NULL, 0, "MENU_RESOLUTION");
-    if (menu_resolution == NULL)
-        parse_resolution(&req_width, &req_height, &req_bpp, menu_resolution);
-
-    // We force bpp to 32
-    req_bpp = 32;
-
-    init_vbe(&fbinfo, req_width, req_height, req_bpp);
-
-    // 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;
-
-    vbe_framebuffer = (void *)fbinfo.framebuffer_addr;
-    vbe_width       = fbinfo.framebuffer_width;
-    vbe_height      = fbinfo.framebuffer_height;
-    vbe_bpp         = fbinfo.framebuffer_bpp;
-    vbe_pitch       = fbinfo.framebuffer_pitch;
-
-    mtrr_set_range((uint64_t)(size_t)vbe_framebuffer,
-                   (uint64_t)vbe_pitch * vbe_height, MTRR_MEMORY_TYPE_WC);
-
-    char *menu_font = config_get_value(NULL, 0, "MENU_FONT");
-    if (menu_font == NULL) {
-        vga_font_retrieve();
-    } else {
-        struct file_handle f;
-        if (!uri_open(&f, menu_font)) {
-            print("menu: Could not open font file.\n");
-            vga_font_retrieve();
-        } else {
-            vga_font = ext_mem_alloc(VGA_FONT_MAX);
-            fread(&f, vga_font, 0, VGA_FONT_MAX);
-        }
-    }
-
-    *_cols = cols = (vbe_width - _margin * 2) / VGA_FONT_WIDTH;
-    *_rows = rows = (vbe_height - _margin * 2) / VGA_FONT_HEIGHT;
-    grid = ext_mem_alloc(rows * cols * sizeof(struct vbe_char));
-    front_grid = ext_mem_alloc(rows * cols * sizeof(struct vbe_char));
-    background = _background;
-
-    if (background)
-        vbe_plot_bg_blent_px = _vbe_plot_bg_blent_px;
-
-    memcpy(ansi_colours, _colours, sizeof(ansi_colours));
-    text_bg = ansi_colours[0];
-    text_fg = ansi_colours[7];
-
-    margin_gradient = _margin_gradient;
-
-    frame_height = vbe_height / 2 - (VGA_FONT_HEIGHT * rows) / 2;
-    frame_width  = vbe_width  / 2 - (VGA_FONT_WIDTH  * cols) / 2;
-
-    vbe_plot_background(0, 0, vbe_width, vbe_height);
-    vbe_clear(true);
-
-    return true;
-}
-
 struct vbe_info_struct {
     char     signature[4];
     uint8_t  version_min;
diff --git a/stage23/drivers/vbe.h b/stage23/drivers/vbe.h
index ef6c4139..b13d0060 100644
--- a/stage23/drivers/vbe.h
+++ b/stage23/drivers/vbe.h
@@ -3,7 +3,6 @@
 
 #include <stdint.h>
 #include <stdbool.h>
-#include <lib/image.h>
 
 struct vbe_framebuffer_info {
     uint8_t  memory_model;
@@ -23,18 +22,4 @@ struct vbe_framebuffer_info {
 bool init_vbe(struct vbe_framebuffer_info *ret,
               uint16_t target_width, uint16_t target_height, uint16_t target_bpp);
 
-bool vbe_tty_init(int *rows, int *cols, uint32_t *colours, int margin, int margin_gradient, struct image *background);
-
-void vbe_putchar(uint8_t c);
-void vbe_clear(bool move);
-void vbe_enable_cursor(void);
-void vbe_disable_cursor(void);
-void vbe_set_cursor_pos(int x, int y);
-void vbe_get_cursor_pos(int *x, int *y);
-void vbe_set_text_fg(int fg);
-void vbe_set_text_bg(int bg);
-
-void vbe_double_buffer_flush(void);
-void vbe_double_buffer(bool state);
-
 #endif
diff --git a/stage23/font.bin b/stage23/font.bin
new file mode 100644
index 00000000..672d0e1a
Binary files /dev/null and b/stage23/font.bin differ
diff --git a/stage23/lib/gterm.c b/stage23/lib/gterm.c
new file mode 100644
index 00000000..6642c1dd
--- /dev/null
+++ b/stage23/lib/gterm.c
@@ -0,0 +1,407 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <lib/gterm.h>
+#include <lib/blib.h>
+#include <lib/libc.h>
+#include <lib/config.h>
+#include <lib/print.h>
+#include <lib/uri.h>
+#include <mm/mtrr.h>
+#include <mm/pmm.h>
+
+#define VGA_FONT_WIDTH  8
+#define VGA_FONT_HEIGHT 16
+#define VGA_FONT_GLYPHS 256
+#define VGA_FONT_MAX    (VGA_FONT_HEIGHT * VGA_FONT_GLYPHS)
+
+static struct vbe_framebuffer_info fbinfo;
+static 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 = (void *)_binary_font_bin_start;
+
+static uint32_t ansi_colours[8];
+
+static int frame_height, frame_width;
+
+static struct image *background;
+
+static struct gterm_char *grid;
+static struct gterm_char *front_grid;
+
+static bool double_buffer_enabled = false;
+
+static bool cursor_status = true;
+
+static int cursor_x;
+static int cursor_y;
+
+static uint32_t cursor_fg = 0x00000000;
+static uint32_t cursor_bg = 0x00ffffff;
+static uint32_t text_fg;
+static uint32_t text_bg;
+
+static int rows;
+static int cols;
+static int margin_gradient;
+
+#define A(rgb) (uint8_t)(rgb >> 24)
+#define R(rgb) (uint8_t)(rgb >> 16)
+#define G(rgb) (uint8_t)(rgb >> 8)
+#define B(rgb) (uint8_t)(rgb)
+#define ARGB(a, r, g, b) (a << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF)
+
+static inline uint32_t colour_blend(uint32_t fg, uint32_t bg) {
+    unsigned alpha = 255 - A(fg);
+    unsigned inv_alpha = A(fg) + 1;
+
+    uint8_t r = (uint8_t)((alpha * R(fg) + inv_alpha * R(bg)) / 256);
+    uint8_t g = (uint8_t)((alpha * G(fg) + inv_alpha * G(bg)) / 256);
+    uint8_t b = (uint8_t)((alpha * B(fg) + inv_alpha * B(bg)) / 256);
+
+    return ARGB(0, r, g, b);
+}
+
+void gterm_plot_px(int x, int y, uint32_t hex) {
+    size_t fb_i = x + (gterm_pitch / sizeof(uint32_t)) * y;
+
+    gterm_framebuffer[fb_i] = hex;
+}
+
+static void _gterm_plot_bg_blent_px(int x, int y, uint32_t hex) {
+    gterm_plot_px(x, y, colour_blend(hex, background->get_pixel(background, x, y)));
+}
+
+void (*gterm_plot_bg_blent_px)(int x, int y, uint32_t hex) = gterm_plot_px;
+
+static uint32_t blend_gradient_from_box(int x, int y, uint32_t hex) {
+    if (x >= frame_width  && x < frame_width  + VGA_FONT_WIDTH  * cols
+     && y >= frame_height && y < frame_height + VGA_FONT_HEIGHT * rows) {
+        return hex;
+    }
+
+    uint32_t bg_px = background->get_pixel(background, x, y);
+
+    if (margin_gradient == 0)
+        return bg_px;
+
+    int distance, x_distance, y_distance;
+
+    if (x < frame_width)
+        x_distance = frame_width - x;
+    else
+        x_distance = x - (frame_width + VGA_FONT_WIDTH * cols);
+
+    if (y < frame_height)
+        y_distance = frame_height - y;
+    else
+        y_distance = y - (frame_height + VGA_FONT_HEIGHT * rows);
+
+    if (x >= frame_width && x < frame_width + VGA_FONT_WIDTH * cols) {
+        distance = y_distance;
+    } else if (y >= frame_height && y < frame_height + VGA_FONT_HEIGHT * rows) {
+        distance = x_distance;
+    } else {
+        distance = sqrt((uint64_t)x_distance * (uint64_t)x_distance
+                      + (uint64_t)y_distance * (uint64_t)y_distance);
+    }
+
+    if (distance > margin_gradient)
+        return bg_px;
+
+    uint8_t gradient_step = (0xff - A(hex)) / margin_gradient;
+    uint8_t new_alpha     = A(hex) + gradient_step * distance;
+
+    return colour_blend((hex & 0xffffff) | (new_alpha << 24), bg_px);
+}
+
+void gterm_plot_background(int x, int y, int width, int height) {
+    if (background) {
+        for (int yy = 0; yy < height; yy++) {
+            for (int xx = 0; xx < width; xx++) {
+                gterm_plot_px(x + xx, y + yy, blend_gradient_from_box(xx, yy, text_bg));
+            }
+        }
+    } else {
+        for (int yy = 0; yy < height; yy++) {
+            for (int xx = 0; xx < width; xx++) {
+                gterm_plot_px(x + xx, y + yy, text_bg);
+            }
+        }
+    }
+}
+
+void gterm_plot_rect(int x, int y, int width, int height, uint32_t hex) {
+    for (int yy = 0; yy < height; yy++) {
+        for (int xx = 0; xx < width; xx++) {
+            gterm_plot_px(x + xx, y + yy, hex);
+        }
+    }
+}
+
+void gterm_plot_bg_blent_rect(int x, int y, int width, int height, uint32_t hex) {
+    for (int yy = 0; yy < height; yy++) {
+        for (int xx = 0; xx < width; xx++) {
+            gterm_plot_bg_blent_px(x + xx, y + yy, hex);
+        }
+    }
+}
+
+struct gterm_char {
+    uint32_t c;
+    uint32_t fg;
+    uint32_t bg;
+};
+
+void gterm_plot_char(struct gterm_char *c, int x, int y) {
+    uint8_t *glyph = &vga_font[(size_t)c->c * VGA_FONT_HEIGHT];
+
+    gterm_plot_bg_blent_rect(x, y, VGA_FONT_WIDTH, VGA_FONT_HEIGHT, c->bg);
+
+    for (int i = 0; i < VGA_FONT_HEIGHT; i++) {
+        for (int j = 0; j < VGA_FONT_WIDTH; j++) {
+            if ((glyph[i] & (0x80 >> j)))
+                gterm_plot_bg_blent_px(x + j, y + i, c->fg);
+        }
+    }
+}
+
+static void plot_char_grid(struct gterm_char *c, int x, int y) {
+    if (!double_buffer_enabled) {
+        gterm_plot_char(c, x * VGA_FONT_WIDTH + frame_width,
+                         y * VGA_FONT_HEIGHT + frame_height);
+    }
+    grid[x + y * cols] = *c;
+}
+
+static void clear_cursor(void) {
+    struct gterm_char c = grid[cursor_x + cursor_y * cols];
+    c.fg = text_fg;
+    c.bg = text_bg;
+    plot_char_grid(&c, cursor_x, cursor_y);
+}
+
+static void draw_cursor(void) {
+    if (cursor_status) {
+        struct gterm_char c = grid[cursor_x + cursor_y * cols];
+        c.fg = cursor_fg;
+        c.bg = cursor_bg;
+        plot_char_grid(&c, cursor_x, cursor_y);
+    }
+}
+
+static void scroll(void) {
+    clear_cursor();
+
+    for (int i = cols; i < rows * cols; i++) {
+        plot_char_grid(&grid[i], (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 (int i = rows * cols - cols; i < rows * cols; i++) {
+        plot_char_grid(&empty, i % cols, i / cols);
+    }
+
+    draw_cursor();
+}
+
+void gterm_clear(bool move) {
+    clear_cursor();
+
+    struct gterm_char empty;
+    empty.c  = ' ';
+    empty.fg = text_fg;
+    empty.bg = text_bg;
+    for (int i = 0; i < rows * cols; i++) {
+        plot_char_grid(&empty, i % cols, i / cols);
+    }
+
+    if (move) {
+        cursor_x = 0;
+        cursor_y = 0;
+    }
+
+    draw_cursor();
+}
+
+void gterm_enable_cursor(void) {
+    cursor_status = true;
+    draw_cursor();
+}
+
+void gterm_disable_cursor(void) {
+    clear_cursor();
+    cursor_status = false;
+}
+
+void gterm_set_cursor_pos(int x, int y) {
+    clear_cursor();
+    cursor_x = x;
+    cursor_y = y;
+    draw_cursor();
+}
+
+void gterm_get_cursor_pos(int *x, int *y) {
+    *x = cursor_x;
+    *y = cursor_y;
+}
+
+void gterm_set_text_fg(int fg) {
+    text_fg = ansi_colours[fg];
+}
+
+void gterm_set_text_bg(int bg) {
+    text_bg = ansi_colours[bg];
+}
+
+void gterm_double_buffer_flush(void) {
+    for (size_t i = 0; i < (size_t)rows * cols; i++) {
+        if (!memcmp(&grid[i], &front_grid[i], sizeof(struct gterm_char)))
+            continue;
+
+        front_grid[i] = grid[i];
+
+        int x = i % cols;
+        int y = i / cols;
+
+        gterm_plot_char(&grid[i], x * VGA_FONT_WIDTH + frame_width,
+                                y * VGA_FONT_HEIGHT + frame_height);
+    }
+}
+
+void gterm_double_buffer(bool state) {
+    if (state) {
+        memcpy(front_grid, grid, rows * cols * sizeof(struct gterm_char));
+        double_buffer_enabled = true;
+        gterm_clear(true);
+        gterm_double_buffer_flush();
+    } else {
+        bool pcs = cursor_status;
+        cursor_status = false;
+        gterm_clear(true);
+        gterm_double_buffer_flush();
+        cursor_status = pcs;
+        draw_cursor();
+        double_buffer_enabled = false;
+    }
+}
+
+void gterm_putchar(uint8_t c) {
+    switch (c) {
+        case '\b':
+            if (cursor_x || cursor_y) {
+                clear_cursor();
+                if (cursor_x) {
+                    cursor_x--;
+                } else {
+                    cursor_y--;
+                    cursor_x = cols - 1;
+                }
+                draw_cursor();
+            }
+            break;
+        case '\r':
+            gterm_set_cursor_pos(0, cursor_y);
+            break;
+        case '\n':
+            if (cursor_y == (rows - 1)) {
+                gterm_set_cursor_pos(0, rows - 1);
+                scroll();
+            } else {
+                gterm_set_cursor_pos(0, cursor_y + 1);
+            }
+            break;
+        default: {
+            clear_cursor();
+            struct gterm_char ch;
+            ch.c  = c;
+            ch.fg = text_fg;
+            ch.bg = text_bg;
+            plot_char_grid(&ch, cursor_x++, cursor_y);
+            if (cursor_x == cols) {
+                cursor_x = 0;
+                cursor_y++;
+            }
+            if (cursor_y == rows) {
+                cursor_y--;
+                scroll();
+            }
+            draw_cursor();
+            break;
+        }
+    }
+}
+
+bool gterm_init(int *_rows, int *_cols, uint32_t *_colours, int _margin, int _margin_gradient, struct image *_background) {
+    int req_width = 0, req_height = 0, req_bpp = 0;
+
+    char *menu_resolution = config_get_value(NULL, 0, "MENU_RESOLUTION");
+    if (menu_resolution == NULL)
+        parse_resolution(&req_width, &req_height, &req_bpp, menu_resolution);
+
+    // We force bpp to 32
+    req_bpp = 32;
+
+    init_vbe(&fbinfo, req_width, req_height, req_bpp);
+
+    // 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 *)fbinfo.framebuffer_addr;
+    gterm_width       = fbinfo.framebuffer_width;
+    gterm_height      = fbinfo.framebuffer_height;
+    gterm_bpp         = fbinfo.framebuffer_bpp;
+    gterm_pitch       = fbinfo.framebuffer_pitch;
+
+    mtrr_set_range((uint64_t)(size_t)gterm_framebuffer,
+                   (uint64_t)gterm_pitch * gterm_height, MTRR_MEMORY_TYPE_WC);
+
+    char *menu_font = config_get_value(NULL, 0, "MENU_FONT");
+    if (menu_font != NULL) {
+        struct file_handle f;
+        if (!uri_open(&f, menu_font)) {
+            print("menu: Could not open font file.\n");
+        } else {
+            vga_font = ext_mem_alloc(VGA_FONT_MAX);
+            fread(&f, vga_font, 0, VGA_FONT_MAX);
+        }
+    }
+
+    *_cols = cols = (gterm_width - _margin * 2) / VGA_FONT_WIDTH;
+    *_rows = rows = (gterm_height - _margin * 2) / VGA_FONT_HEIGHT;
+    grid = ext_mem_alloc(rows * cols * sizeof(struct gterm_char));
+    front_grid = ext_mem_alloc(rows * cols * sizeof(struct gterm_char));
+    background = _background;
+
+    if (background)
+        gterm_plot_bg_blent_px = _gterm_plot_bg_blent_px;
+
+    memcpy(ansi_colours, _colours, sizeof(ansi_colours));
+    text_bg = ansi_colours[0];
+    text_fg = ansi_colours[7];
+
+    margin_gradient = _margin_gradient;
+
+    frame_height = gterm_height / 2 - (VGA_FONT_HEIGHT * rows) / 2;
+    frame_width  = gterm_width  / 2 - (VGA_FONT_WIDTH  * cols) / 2;
+
+    gterm_plot_background(0, 0, gterm_width, gterm_height);
+    gterm_clear(true);
+
+    return true;
+}
diff --git a/stage23/lib/gterm.h b/stage23/lib/gterm.h
new file mode 100644
index 00000000..65b7565c
--- /dev/null
+++ b/stage23/lib/gterm.h
@@ -0,0 +1,24 @@
+#ifndef __LIB__GTERM_H__
+#define __LIB__GTERM_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <lib/image.h>
+#include <drivers/vbe.h>
+
+bool gterm_init(int *rows, int *cols, uint32_t *colours, int margin,
+                int margin_gradient, struct image *background);
+
+void gterm_putchar(uint8_t c);
+void gterm_clear(bool move);
+void gterm_enable_cursor(void);
+void gterm_disable_cursor(void);
+void gterm_set_cursor_pos(int x, int y);
+void gterm_get_cursor_pos(int *x, int *y);
+void gterm_set_text_fg(int fg);
+void gterm_set_text_bg(int bg);
+
+void gterm_double_buffer_flush(void);
+void gterm_double_buffer(bool state);
+
+#endif
diff --git a/stage23/lib/term.c b/stage23/lib/term.c
index b636f4be..441c924b 100644
--- a/stage23/lib/term.c
+++ b/stage23/lib/term.c
@@ -4,27 +4,28 @@
 #include <lib/term.h>
 #include <lib/image.h>
 #include <lib/blib.h>
-#include <drivers/vbe.h>
+#include <lib/gterm.h>
 
 void term_vbe(uint32_t *colours, int margin, int margin_gradient, struct image *background) {
     term_deinit();
-    if (!vbe_tty_init(&term_rows, &term_cols, colours, margin, margin_gradient, background)) {
+
+    if (!gterm_init(&term_rows, &term_cols, colours, margin, margin_gradient, background)) {
         // Failed to set VBE properly, default to text mode
         term_textmode();
         return;
     }
 
-    raw_putchar    = vbe_putchar;
-    clear          = vbe_clear;
-    enable_cursor  = vbe_enable_cursor;
-    disable_cursor = vbe_disable_cursor;
-    set_cursor_pos = vbe_set_cursor_pos;
-    get_cursor_pos = vbe_get_cursor_pos;
-    set_text_fg    = vbe_set_text_fg;
-    set_text_bg    = vbe_set_text_bg;
+    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;
 
-    term_double_buffer       = vbe_double_buffer;
-    term_double_buffer_flush = vbe_double_buffer_flush;
+    term_double_buffer       = gterm_double_buffer;
+    term_double_buffer_flush = gterm_double_buffer_flush;
 
     term_backend = VBE;
 }
tab: 248 wrap: offon