#include #include #include #include #include #include #include #include #include #include #include // 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; 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; 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; } 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; } void gterm_swap_palette(void) { uint32_t tmp = text_bg; text_bg = text_fg; text_fg = tmp; } #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); } 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; if (x < margin) x_distance = margin - x; else x_distance = x - gradient_stop_x; if (y < margin) y_distance = margin - y; else y_distance = y - gradient_stop_y; if (x >= margin && x < gradient_stop_x) { distance = y_distance; } else if (y >= margin && y < gradient_stop_y) { 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); } typedef size_t fixedp6; // the last 6 bits are the fixed point part static size_t fixedp6_to_int(fixedp6 value) { return value / 64; } static fixedp6 int_to_fixedp6(size_t value) { return value * 64; } // Draw rect at coordinates, copying from the image to the fb and canvas, applying fn on every pixel __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t xend, size_t ystart, size_t yend, uint32_t (*blend)(size_t x, size_t y, uint32_t orig)) { uint8_t *img = background->img; const size_t img_width = background->img_width, img_height = background->img_height, img_pitch = background->pitch, colsize = background->bpp / 8; switch (background->type) { case IMAGE_TILED: 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; 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; if (image_x++ == img_width) image_x = 0; // image_x = x % img_width, but modulo is too expensive } } break; case IMAGE_CENTERED: 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; 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; } } else { /* internal part */ for (size_t x = xstart; x < xend; x++) { size_t image_x = (x - background->x_displacement); 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; } } } break; // For every pixel, ratio = img_width / gterm_width, img_x = x * ratio, x = (xstart + i) // hence x = xstart * ratio + i * ratio // 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 off = img_pitch * (img_height - 1 - img_y); size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y; size_t ratio = int_to_fixedp6(img_width) / gterm_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; img_x += ratio; } } break; } } static uint32_t blend_external(size_t x, size_t y, uint32_t orig) { (void)x; (void)y; return orig; } static uint32_t blend_internal(size_t x, size_t y, uint32_t orig) { (void)x; (void)y; return colour_blend(default_bg, orig); } static uint32_t blend_margin(size_t x, size_t y, uint32_t orig) { return blend_gradient_from_box(x, y, orig, default_bg); } static void loop_external(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_external); } 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) { if (background) { 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; loop_external(0, gterm_width, 0, margin_no_gradient); loop_external(0, gterm_width, scan_stop_y, gterm_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); size_t gradient_stop_x = gterm_width - margin; size_t gradient_stop_y = gterm_height - margin; if (margin_gradient) { loop_margin(margin_no_gradient, scan_stop_x, margin_no_gradient, margin); loop_margin(margin_no_gradient, scan_stop_x, gradient_stop_y, scan_stop_y); loop_margin(margin_no_gradient, margin, margin, gradient_stop_y); loop_margin(gradient_stop_x, scan_stop_x, margin, gradient_stop_y); } 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_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; } 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(); } } static bool last_serial = false; bool gterm_init(size_t *_rows, size_t *_cols, size_t width, size_t height) { if (current_video_mode >= 0 #if bios == 1 && current_video_mode != 0x03 #endif && fbinfo.default_res == true && width == 0 && height == 0 && fbinfo.framebuffer_bpp == 32 && !early_term && serial == last_serial) { *_rows = rows; *_cols = cols; gterm_clear(true); return true; } if (current_video_mode >= 0 #if bios == 1 && current_video_mode != 0x03 #endif && fbinfo.framebuffer_width == width && fbinfo.framebuffer_height == height && fbinfo.framebuffer_bpp == 32 && !early_term && serial == last_serial) { *_rows = rows; *_cols = cols; gterm_clear(true); return true; } early_term = false; // We force bpp to 32 if (!fb_init(&fbinfo, width, height, 32)) return false; last_serial = serial; cursor_status = true; scroll_enabled = true; // default scheme margin = 64; margin_gradient = 4; ansi_colours[0] = 0x00000000; // black ansi_colours[1] = 0x00aa0000; // red ansi_colours[2] = 0x0000aa00; // green ansi_colours[3] = 0x00aa5500; // brown ansi_colours[4] = 0x000000aa; // blue ansi_colours[5] = 0x00aa00aa; // magenta ansi_colours[6] = 0x0000aaaa; // cyan ansi_colours[7] = 0x00aaaaaa; // grey char *colours = config_get_value(NULL, 0, "TERM_PALETTE"); if (colours != NULL) { const char *first = colours; size_t i; for (i = 0; i < 8; i++) { const char *last; uint32_t col = strtoui(first, &last, 16); if (first == last) break; ansi_colours[i] = col & 0xffffff; if (*last == 0) break; first = last + 1; } } ansi_bright_colours[0] = 0x00555555; // black ansi_bright_colours[1] = 0x00ff5555; // red ansi_bright_colours[2] = 0x0055ff55; // green ansi_bright_colours[3] = 0x00ffff55; // brown ansi_bright_colours[4] = 0x005555ff; // blue ansi_bright_colours[5] = 0x00ff55ff; // magenta ansi_bright_colours[6] = 0x0055ffff; // cyan ansi_bright_colours[7] = 0x00ffffff; // grey char *bright_colours = config_get_value(NULL, 0, "TERM_PALETTE_BRIGHT"); if (bright_colours != NULL) { const char *first = bright_colours; size_t i; for (i = 0; i < 8; i++) { const char *last; uint32_t col = strtoui(first, &last, 16); if (first == last) break; ansi_bright_colours[i] = col & 0xffffff; if (*last == 0) break; first = last + 1; } } default_bg = 0x00000000; // background (black) default_fg = 0x00aaaaaa; // foreground (grey) char *theme_background = config_get_value(NULL, 0, "TERM_BACKGROUND"); if (theme_background != NULL) { default_bg = strtoui(theme_background, NULL, 16); } char *theme_foreground = config_get_value(NULL, 0, "TERM_FOREGROUND"); if (theme_foreground != NULL) { default_fg = strtoui(theme_foreground, NULL, 16) & 0xffffff; } text_fg = default_fg; text_bg = 0xffffffff; background = NULL; char *background_path = config_get_value(NULL, 0, "TERM_WALLPAPER"); if (background_path != NULL) { struct file_handle *bg_file; if ((bg_file = uri_open(background_path)) != NULL) { background = image_open(bg_file); fclose(bg_file); } } if (background == NULL) { margin = 0; margin_gradient = 0; } char *theme_margin = config_get_value(NULL, 0, "TERM_MARGIN"); if (theme_margin != NULL) { margin = strtoui(theme_margin, NULL, 10); } char *theme_margin_gradient = config_get_value(NULL, 0, "TERM_MARGIN_GRADIENT"); if (theme_margin_gradient != NULL) { margin_gradient = strtoui(theme_margin_gradient, NULL, 10); } if (background != NULL) { char *background_layout = config_get_value(NULL, 0, "TERM_WALLPAPER_STYLE"); if (background_layout != NULL && strcmp(background_layout, "centered") == 0) { char *background_colour = config_get_value(NULL, 0, "TERM_BACKDROP"); if (background_colour == NULL) background_colour = "0"; uint32_t bg_col = strtoui(background_colour, NULL, 16); image_make_centered(background, fbinfo.framebuffer_width, fbinfo.framebuffer_height, bg_col); } else if (background_layout != NULL && strcmp(background_layout, "tiled") == 0) { } else { image_make_stretched(background, fbinfo.framebuffer_width, fbinfo.framebuffer_height); } } // 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; vga_font_bits = ext_mem_alloc(VGA_FONT_MAX); memcpy(vga_font_bits, (void *)_binary_font_bin_start, VGA_FONT_MAX); size_t tmp_font_width, tmp_font_height; char *menu_font_size = config_get_value(NULL, 0, "TERM_FONT_SIZE"); 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; 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); goto no_load_font; } font_bytes = tmp_font_bytes; } char *menu_font = config_get_value(NULL, 0, "TERM_FONT"); if (menu_font != NULL) { struct file_handle *f; if ((f = uri_open(menu_font)) == NULL) { print("menu: Could not open font file.\n"); } else { fread(f, vga_font_bits, 0, font_bytes); if (menu_font_size != NULL) { vga_font_width = tmp_font_width; vga_font_height = tmp_font_height; } fclose(f); } } no_load_font:; size_t font_spacing = 1; char *font_spacing_str = config_get_value(NULL, 0, "TERM_FONT_SPACING"); if (font_spacing_str != NULL) { 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; char *menu_font_scale = config_get_value(NULL, 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; } } 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; offset_x = margin + ((gterm_width - margin * 2) % glyph_width) / 2; offset_y = margin + ((gterm_height - margin * 2) % glyph_height) / 2; 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(); 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(); } }