384 lines
12 KiB
C
384 lines
12 KiB
C
// This file is part of the CircuitPython project: https://circuitpython.org
|
|
//
|
|
// SPDX-FileCopyrightText: Copyright (c) 2019 Scott Shawcroft for Adafruit Industries
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "supervisor/shared/display.h"
|
|
|
|
#include <string.h>
|
|
#include "supervisor/port.h"
|
|
|
|
#include "py/mpstate.h"
|
|
#include "py/gc.h"
|
|
#include "shared-bindings/displayio/Bitmap.h"
|
|
#include "shared-bindings/displayio/Group.h"
|
|
#include "shared-bindings/displayio/Palette.h"
|
|
#include "shared-bindings/displayio/TileGrid.h"
|
|
|
|
#if CIRCUITPY_RGBMATRIX
|
|
#include "shared-module/displayio/__init__.h"
|
|
#endif
|
|
|
|
#if CIRCUITPY_SHARPDISPLAY
|
|
#include "shared-module/displayio/__init__.h"
|
|
#include "shared-bindings/sharpdisplay/SharpMemoryFramebuffer.h"
|
|
#include "shared-module/sharpdisplay/SharpMemoryFramebuffer.h"
|
|
#endif
|
|
|
|
#if CIRCUITPY_AURORA_EPAPER
|
|
#include "shared-module/displayio/__init__.h"
|
|
#include "shared-bindings/aurora_epaper/aurora_framebuffer.h"
|
|
#include "shared-module/aurora_epaper/aurora_framebuffer.h"
|
|
#endif
|
|
|
|
#if CIRCUITPY_STATUS_BAR
|
|
#include "supervisor/shared/status_bar.h"
|
|
#endif
|
|
|
|
#if CIRCUITPY_TERMINALIO
|
|
#include "supervisor/port.h"
|
|
#if CIRCUITPY_OS_GETENV
|
|
#include "shared-module/os/__init__.h"
|
|
#endif
|
|
#if CIRCUITPY_LVFONTIO
|
|
#include "shared-bindings/lvfontio/OnDiskFont.h"
|
|
#include "supervisor/filesystem.h"
|
|
#include "extmod/vfs_fat.h"
|
|
#include "lib/oofatfs/ff.h"
|
|
|
|
#include "supervisor/shared/serial.h"
|
|
|
|
// Check if a custom font file exists and return its path if found
|
|
// Returns true if font file exists, false otherwise
|
|
static bool check_for_custom_font(const char **font_path_out) {
|
|
if (!filesystem_present()) {
|
|
return false;
|
|
}
|
|
|
|
fs_user_mount_t *vfs = filesystem_circuitpy();
|
|
if (vfs == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Use FATFS directly to check if file exists
|
|
FILINFO file_info;
|
|
const char *default_font_path = "/fonts/terminal.lvfontbin";
|
|
const char *font_path = default_font_path;
|
|
|
|
#if CIRCUITPY_OS_GETENV
|
|
// Buffer for storing custom font path
|
|
static char custom_font_path[128];
|
|
if (common_hal_os_getenv_str("CIRCUITPY_TERMINAL_FONT", custom_font_path, sizeof(custom_font_path)) == GETENV_OK) {
|
|
// Use custom font path from environment variable
|
|
font_path = custom_font_path;
|
|
}
|
|
#endif
|
|
|
|
FRESULT result = f_stat(&vfs->fatfs, font_path, &file_info);
|
|
if (result == FR_OK) {
|
|
if (font_path_out != NULL) {
|
|
*font_path_out = font_path;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// If custom font path doesn't exist, use default font
|
|
font_path = default_font_path;
|
|
result = f_stat(&vfs->fatfs, font_path, &file_info);
|
|
|
|
if (result == FR_OK) {
|
|
if (font_path_out != NULL) {
|
|
*font_path_out = font_path;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Initialize a BuiltinFont object with the specified font file and max_slots
|
|
// Returns true on success, false on failure
|
|
static bool init_lvfont(lvfontio_ondiskfont_t *font, const char *font_path, uint16_t max_slots) {
|
|
if (font == NULL) {
|
|
return false;
|
|
}
|
|
|
|
font->base.type = &lvfontio_ondiskfont_type;
|
|
|
|
// Pass false for use_gc_allocator during startup when garbage collector isn't fully initialized
|
|
common_hal_lvfontio_ondiskfont_construct(font, font_path, max_slots, false);
|
|
|
|
return !common_hal_lvfontio_ondiskfont_deinited(font);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if CIRCUITPY_REPL_LOGO
|
|
extern uint32_t blinka_bitmap_data[];
|
|
extern displayio_bitmap_t blinka_bitmap;
|
|
#endif
|
|
extern displayio_group_t circuitpython_splash;
|
|
|
|
#if CIRCUITPY_TERMINALIO
|
|
static uint8_t *tilegrid_tiles = NULL;
|
|
static size_t tilegrid_tiles_size = 0;
|
|
#endif
|
|
|
|
#if CIRCUITPY_LVFONTIO
|
|
static lvfontio_ondiskfont_t *lvfont = NULL;
|
|
#endif
|
|
|
|
void supervisor_start_terminal(uint16_t width_px, uint16_t height_px) {
|
|
if (supervisor_terminal_started()) {
|
|
return;
|
|
}
|
|
|
|
#if CIRCUITPY_TERMINALIO
|
|
// Default the scale to 2 because we may show blinka without the terminal for
|
|
// languages that don't have font support.
|
|
mp_int_t scale = 2;
|
|
|
|
displayio_tilegrid_t *scroll_area = &supervisor_terminal_scroll_area_text_grid;
|
|
displayio_tilegrid_t *status_bar = &supervisor_terminal_status_bar_text_grid;
|
|
bool reset_tiles = false;
|
|
|
|
uint16_t glyph_width = 0;
|
|
uint16_t glyph_height = 0;
|
|
|
|
#if CIRCUITPY_LVFONTIO
|
|
// Check if we have a custom terminal font in the filesystem
|
|
bool use_lv_font = false;
|
|
const char *font_path = NULL;
|
|
|
|
if (check_for_custom_font(&font_path)) {
|
|
// Initialize a temporary font just to get dimensions
|
|
lvfontio_ondiskfont_t temp_font;
|
|
if (init_lvfont(&temp_font, font_path, 1)) {
|
|
// Get the font dimensions
|
|
common_hal_lvfontio_ondiskfont_get_dimensions(&temp_font, &glyph_width, &glyph_height);
|
|
|
|
// Clean up the temp font - we'll create a proper one later
|
|
common_hal_lvfontio_ondiskfont_deinit(&temp_font);
|
|
use_lv_font = true;
|
|
reset_tiles = true;
|
|
// TODO: We may want to detect when the files modified time hasn't changed.
|
|
}
|
|
}
|
|
#endif
|
|
#if CIRCUITPY_FONTIO
|
|
if (glyph_width == 0) {
|
|
glyph_width = supervisor_terminal_font.width;
|
|
glyph_height = supervisor_terminal_font.height;
|
|
}
|
|
#endif
|
|
|
|
uint16_t width_in_tiles = width_px / glyph_width;
|
|
|
|
// determine scale based on width
|
|
if (width_in_tiles <= 120) {
|
|
scale = 1;
|
|
}
|
|
#if CIRCUITPY_OS_GETENV
|
|
(void)common_hal_os_getenv_int("CIRCUITPY_TERMINAL_SCALE", &scale);
|
|
#endif
|
|
|
|
width_in_tiles = MAX(1, width_px / (glyph_width * scale));
|
|
uint16_t height_in_tiles = MAX(2, height_px / (glyph_height * scale));
|
|
|
|
uint16_t total_tiles = width_in_tiles * height_in_tiles;
|
|
|
|
// check if the terminal tile dimensions are the same
|
|
if ((scroll_area->width_in_tiles != width_in_tiles) ||
|
|
(scroll_area->height_in_tiles != height_in_tiles - 1)) {
|
|
reset_tiles = true;
|
|
}
|
|
|
|
circuitpython_splash.scale = scale;
|
|
if (!reset_tiles) {
|
|
return;
|
|
}
|
|
|
|
// Adjust the display dimensions to account for scale of the outer group.
|
|
width_px /= scale;
|
|
height_px /= scale;
|
|
|
|
// Number of tiles from the left edge to inset the status bar.
|
|
size_t min_left_padding = 0;
|
|
status_bar->tile_width = glyph_width;
|
|
status_bar->tile_height = glyph_height;
|
|
#if CIRCUITPY_REPL_LOGO
|
|
// Blinka + 1 px padding minimum
|
|
min_left_padding = supervisor_blinka_sprite.pixel_width + 1;
|
|
// Align the status bar to the bottom of the logo.
|
|
status_bar->y = supervisor_blinka_sprite.pixel_height - status_bar->tile_height;
|
|
#else
|
|
status_bar->y = 0;
|
|
#endif
|
|
status_bar->width_in_tiles = (width_px - min_left_padding) / status_bar->tile_width;
|
|
status_bar->height_in_tiles = 1;
|
|
status_bar->pixel_width = status_bar->width_in_tiles * status_bar->tile_width;
|
|
status_bar->pixel_height = status_bar->tile_height;
|
|
// Right align the status bar.
|
|
status_bar->x = width_px - status_bar->pixel_width;
|
|
status_bar->top_left_y = 0;
|
|
status_bar->full_change = true;
|
|
|
|
scroll_area->tile_width = glyph_width;
|
|
scroll_area->tile_height = glyph_height;
|
|
scroll_area->width_in_tiles = width_in_tiles;
|
|
// Leave space for the status bar, no matter if we have logo or not.
|
|
scroll_area->height_in_tiles = height_in_tiles - 1;
|
|
scroll_area->pixel_width = scroll_area->width_in_tiles * scroll_area->tile_width;
|
|
scroll_area->pixel_height = scroll_area->height_in_tiles * scroll_area->tile_height;
|
|
// Right align the scroll area to give margin to the start of each line.
|
|
scroll_area->x = width_px - scroll_area->pixel_width;
|
|
scroll_area->top_left_y = 0;
|
|
// Align the scroll area to the bottom so that the newest line isn't cutoff. The top line
|
|
// may be clipped by the status bar and that's ok.
|
|
scroll_area->y = height_px - scroll_area->pixel_height;
|
|
scroll_area->full_change = true;
|
|
|
|
mp_obj_t new_bitmap = mp_const_none;
|
|
mp_obj_t new_font = mp_const_none;
|
|
|
|
#if CIRCUITPY_LVFONTIO
|
|
if (lvfont != NULL) {
|
|
common_hal_lvfontio_ondiskfont_deinit(lvfont);
|
|
// This will also free internal buffers that may change size.
|
|
port_free(lvfont);
|
|
lvfont = NULL;
|
|
}
|
|
|
|
if (use_lv_font) {
|
|
// We found a custom terminal font file, use it instead of the built-in font
|
|
|
|
lvfont = port_malloc(sizeof(lvfontio_ondiskfont_t), false);
|
|
if (lvfont != NULL) {
|
|
// Use the number of tiles in the terminal and status bar for the number of slots
|
|
// This ensures we have enough slots to display all characters that could appear on screen
|
|
uint16_t num_slots = width_in_tiles * height_in_tiles;
|
|
|
|
// Initialize the font with our helper function
|
|
if (init_lvfont(lvfont, font_path, num_slots)) {
|
|
// Get the bitmap from the font
|
|
new_bitmap = common_hal_lvfontio_ondiskfont_get_bitmap(lvfont);
|
|
new_font = MP_OBJ_FROM_PTR(lvfont);
|
|
} else {
|
|
// If font initialization failed, free the memory and fall back to built-in font
|
|
port_free(lvfont);
|
|
lvfont = NULL;
|
|
use_lv_font = false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#if CIRCUITPY_FONTIO
|
|
if (new_font == mp_const_none) {
|
|
new_bitmap = MP_OBJ_FROM_PTR(supervisor_terminal_font.bitmap);
|
|
new_font = MP_OBJ_FROM_PTR(&supervisor_terminal_font);
|
|
}
|
|
#endif
|
|
|
|
if (new_font != mp_const_none) {
|
|
size_t total_values = common_hal_displayio_bitmap_get_width(new_bitmap) / glyph_width;
|
|
if (tilegrid_tiles) {
|
|
port_free(tilegrid_tiles);
|
|
tilegrid_tiles = NULL;
|
|
}
|
|
size_t bytes_per_tile = 1;
|
|
if (total_tiles > 255) {
|
|
// Two bytes per tile.
|
|
bytes_per_tile = 2;
|
|
}
|
|
tilegrid_tiles = port_malloc(total_tiles * bytes_per_tile, false);
|
|
if (!tilegrid_tiles) {
|
|
return;
|
|
}
|
|
status_bar->tiles = tilegrid_tiles;
|
|
status_bar->tiles_in_bitmap = total_values;
|
|
status_bar->bitmap_width_in_tiles = total_values;
|
|
scroll_area->tiles = tilegrid_tiles + width_in_tiles * bytes_per_tile;
|
|
scroll_area->tiles_in_bitmap = total_values;
|
|
scroll_area->bitmap_width_in_tiles = total_values;
|
|
|
|
common_hal_displayio_tilegrid_set_bitmap(scroll_area, new_bitmap);
|
|
common_hal_displayio_tilegrid_set_bitmap(status_bar, new_bitmap);
|
|
common_hal_terminalio_terminal_construct(&supervisor_terminal, scroll_area,
|
|
new_font, status_bar);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void supervisor_stop_terminal(void) {
|
|
#if CIRCUITPY_TERMINALIO
|
|
if (tilegrid_tiles != NULL) {
|
|
port_free(tilegrid_tiles);
|
|
tilegrid_tiles = NULL;
|
|
tilegrid_tiles_size = 0;
|
|
supervisor_terminal_scroll_area_text_grid.tiles = NULL;
|
|
supervisor_terminal_status_bar_text_grid.tiles = NULL;
|
|
supervisor_terminal.scroll_area = NULL;
|
|
supervisor_terminal.status_bar = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool supervisor_terminal_started(void) {
|
|
#if CIRCUITPY_TERMINALIO
|
|
return tilegrid_tiles != NULL;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#if CIRCUITPY_TERMINALIO
|
|
#if CIRCUITPY_REPL_LOGO
|
|
mp_obj_t members[] = { &supervisor_terminal_scroll_area_text_grid, &supervisor_blinka_sprite, &supervisor_terminal_status_bar_text_grid, };
|
|
mp_obj_list_t splash_children = {
|
|
.base = {.type = &mp_type_list },
|
|
.alloc = 3,
|
|
.len = 3,
|
|
.items = members,
|
|
};
|
|
#else
|
|
mp_obj_t members[] = { &supervisor_terminal_scroll_area_text_grid, &supervisor_terminal_status_bar_text_grid, };
|
|
mp_obj_list_t splash_children = {
|
|
.base = {.type = &mp_type_list },
|
|
.alloc = 2,
|
|
.len = 2,
|
|
.items = members,
|
|
};
|
|
#endif
|
|
#else
|
|
#if CIRCUITPY_REPL_LOGO
|
|
mp_obj_t members[] = { &supervisor_blinka_sprite };
|
|
mp_obj_list_t splash_children = {
|
|
.base = {.type = &mp_type_list },
|
|
.alloc = 1,
|
|
.len = 1,
|
|
.items = members,
|
|
};
|
|
#else
|
|
mp_obj_t members[] = {};
|
|
mp_obj_list_t splash_children = {
|
|
.base = {.type = &mp_type_list },
|
|
.alloc = 0,
|
|
.len = 0,
|
|
.items = members,
|
|
};
|
|
#endif
|
|
#endif
|
|
|
|
displayio_group_t circuitpython_splash = {
|
|
.base = {.type = &displayio_group_type },
|
|
.x = 0,
|
|
.y = 0,
|
|
.scale = 2,
|
|
.members = &splash_children,
|
|
.item_removed = false,
|
|
.in_group = false,
|
|
.hidden = false,
|
|
.hidden_by_parent = false,
|
|
.readonly = true,
|
|
};
|