├── .gitignore ├── images ├── st-img1.jpeg ├── st-img2.jpeg ├── st-img3.jpeg └── st-img1-trimuisp.jpg ├── vercel.json ├── embedded-font-editor ├── README.md ├── index.html ├── style.css ├── app.js └── fonts.js ├── .clang-format ├── LEGACY ├── .vscode └── c_cpp_properties.json ├── src ├── font.h ├── config.h ├── keyboard.h ├── vt100.h ├── keyboard.c ├── main.c └── vt100.c ├── Makefile ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | simple-terminal -------------------------------------------------------------------------------- /images/st-img1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haoict/SimpleTerminal/HEAD/images/st-img1.jpeg -------------------------------------------------------------------------------- /images/st-img2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haoict/SimpleTerminal/HEAD/images/st-img2.jpeg -------------------------------------------------------------------------------- /images/st-img3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haoict/SimpleTerminal/HEAD/images/st-img3.jpeg -------------------------------------------------------------------------------- /images/st-img1-trimuisp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haoict/SimpleTerminal/HEAD/images/st-img1-trimuisp.jpg -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/(.*)", 5 | "destination": "/embedded-font-editor/$1" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /embedded-font-editor/README.md: -------------------------------------------------------------------------------- 1 | # Bitmap Font Editor 2 | 3 | A web-based editor for bitmap fonts that allows you to visually edit character glyphs in a pixel grid. 4 | 5 | Usage: Open index.html -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | TabWidth: 4 4 | UseTab: Never 5 | ColumnLimit: 240 6 | BreakBeforeBraces: Attach 7 | AccessModifierOffset: -4 8 | PointerAlignment: Right 9 | -------------------------------------------------------------------------------- /LEGACY: -------------------------------------------------------------------------------- 1 | A STATEMENT ON LEGACY SUPPORT 2 | 3 | In the terminal world there is much cruft that comes from old and unsup‐ 4 | ported terminals that inherit incompatible modes and escape sequences 5 | which noone is able to know, except when he/she comes from that time and 6 | developed a graphical vt100 emulator at that time. 7 | 8 | One goal of st is to only support what is really needed. When you en‐ 9 | counter a sequence which you really need, implement it. But while you 10 | are at it, do not add the other cruft you might encounter while sneek‐ 11 | ing at other terminal emulators. History has bloated them and there is 12 | no real evidence that most of the sequences are used today. 13 | 14 | 15 | Christoph Lohmann <20h@r-36.net> 16 | 2012-09-13T07:00:36.081271045+02:00 -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/src", 7 | "${workspaceFolder}/src/**", 8 | "/home/haoict/TiniLinux/output.arm64/host/aarch64-buildroot-linux-gnu/sysroot/usr/include" 9 | ], 10 | "compilerPath": "/usr/bin/gcc", 11 | "cStandard": "gnu11", 12 | "cppStandard": "gnu++17", 13 | "intelliSenseMode": "linux-gcc-x64", 14 | "browse": { 15 | "path": [ 16 | "${workspaceFolder}/src" 17 | ], 18 | "limitSymbolsToIncludedHeaders": true, 19 | "databaseFilename": "" 20 | }, 21 | "defines": [ 22 | "BR2", 23 | "H700", 24 | "RGB30" 25 | ] 26 | } 27 | ], 28 | "version": 4 29 | } -------------------------------------------------------------------------------- /src/font.h: -------------------------------------------------------------------------------- 1 | #ifndef __FONT_H__ 2 | #define __FONT_H__ 3 | 4 | #include 5 | #include 6 | 7 | /* Bitmap font functions */ 8 | void draw_char(SDL_Surface *surface, unsigned char symbol, int x, int y, unsigned short color, int embedded_font_name); 9 | void draw_string(SDL_Surface *surface, const char *text, int x, int y, unsigned short color, int embedded_font_name); 10 | int get_embedded_font_char_width(int embedded_font_name); 11 | int get_embedded_font_char_height(int embedded_font_name); 12 | 13 | /* TTF font functions */ 14 | int init_ttf_font(const char *font_path, int font_size, int font_shaded); 15 | void cleanup_ttf_font(void); 16 | void draw_string_ttf(SDL_Surface *surface, const char *text, int x, int y, SDL_Color fg, SDL_Color bg); 17 | void draw_string_ttf_with_linebreak(SDL_Surface *surface, const char *text, int x, int y, SDL_Color fg, SDL_Color bg); 18 | int get_ttf_char_width(void); 19 | int get_ttf_char_height(void); 20 | int is_ttf_loaded(void); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION ?= 2.1.0 2 | # compiler and linker 3 | CROSS_COMPILE ?= 4 | CC = ${CROSS_COMPILE}gcc 5 | SYSROOT ?= $(shell ${CC} --print-sysroot) 6 | # flags 7 | CFLAGS = -Os -Wall -I. -I${SYSROOT}/usr/include -DVERSION=\"${VERSION}\" -D_GNU_SOURCE=1 -D_REENTRANT -std=gnu11 -flto -Wno-unused-result 8 | LDFLAGS = -L${SYSROOT}/usr/lib -lSDL2 -lSDL2_ttf -lpthread -lutil -s 9 | 10 | 11 | ifeq ($(PLATFORM),rgb30) 12 | CFLAGS += -DBR2 -DRGB30 13 | else ifeq ($(PLATFORM),h700) 14 | CFLAGS += -DBR2 -DH700 15 | else ifeq ($(PLATFORM),pi) 16 | CFLAGS += -DBR2 -DRPI 17 | endif 18 | 19 | SRC = $(wildcard src/*.c) 20 | 21 | build: 22 | @echo st build options: 23 | @echo "PLATFORM = ${PLATFORM}" 24 | @echo "CROSS_COMPILE = ${CROSS_COMPILE}" 25 | @echo "SYSROOT = ${SYSROOT}" 26 | @echo "CFLAGS = ${CFLAGS}" 27 | @echo "LDFLAGS = ${LDFLAGS}" 28 | @echo "CC = ${CC}" 29 | @echo "SRC = ${SRC}" 30 | @echo "VERSION = ${VERSION}" 31 | ${CC} -o simple-terminal ${SRC} ${CFLAGS} ${LDFLAGS} 32 | 33 | clean: 34 | @echo cleaning 35 | rm -f simple-terminal 36 | 37 | .PHONY: build clean 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2024-2025 haoict 4 | © 2009-2012 Aurélien APTEL 5 | © 2012 Roberto E. Vargas Caballero 6 | © 2012 Christoph Lohmann <20h at r-36 dot net> 7 | © 2009 Anselm R Garbe 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a 10 | copy of this software and associated documentation files (the "Software"), 11 | to deal in the Software without restriction, including without limitation 12 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | and/or sell copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-terminal 2 | Simple Terminal Emulator for embedded Linux handhelds, migrated from SDL1.2 to SDL2 (by haoict) 3 | 4 | Image1 5 | Image2 6 | Image3 7 | Image1-TrimuiSP 8 | 9 | # Build 10 | For generic linux: 11 | ```bash 12 | sudo apt install build-essential libsdl2-dev libsdl2-ttf-dev 13 | make 14 | ``` 15 | 16 | # Run 17 | ```bash 18 | ./simple-terminal 19 | ./simple-terminal -font 2 # with alternative embedded font 20 | ./simple-terminal -scale 1 -font /path/to/font.ttf -fontsize 12 -fontshade 1 # with a ttf font 21 | 22 | # run commands when open 23 | ./simple-terminal -e "ls -la" "uname -a" whoami 24 | ``` 25 | 26 | # Build with buildroot toolchain 27 | you can build everything for the target device with buildroot: 28 | https://github.com/haoict/TiniLinux/blob/master/README.md#build 29 | 30 | or build the toolchain only and build simple-terminal separately 31 | ```bash 32 | # build toolchain 33 | cd buildroot 34 | make O=../TiniLinux/output.arm64 BR2_EXTERNAL=../TiniLinux toolchain_arm64_defconfig 35 | cd ../TiniLinux 36 | make -j $(nproc) 37 | 38 | # build simple-terminal 39 | cd package/simple-terminal 40 | export CROSS_COMPILE=/home/haoict/TiniLinux/output.arm64/host/bin/aarch64-buildroot-linux-gnu- 41 | make 42 | ``` 43 | 44 | # To edit embedded bitmap font 45 | 46 | https://simple-terminal-psi.vercel.app 47 | 48 | # License & Credits 49 | MIT License 50 | Based on Aurélien APTEL bt source code. 51 | https://github.com/benob/rs97_st-sdl 52 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | static int borderpx = 2; 4 | char default_shell[] = "/bin/bash"; 5 | 6 | static int initial_width = 320; 7 | static int initial_height = 240; 8 | static float opt_scale = 2.0; 9 | static char *opt_font = NULL; // "1" or "2" for embedded fonts, or path to TTF font file 10 | static int opt_fontsize = 12; // only used if opt_font is set to a TTF font 11 | static int opt_fontshade = 0; // 0=solid, 1=blended, 2=shaded, only used if opt_font is set to a TTF font 12 | static int opt_use_embedded_font_for_keyboard = 0; 13 | 14 | static const Uint32 BUTTON_HELD_DELAY = 150; // milliseconds between button triggers when held 15 | 16 | /* TERM value */ 17 | char termname[] = "xterm"; 18 | 19 | unsigned int tabspaces = 4; 20 | 21 | /* Terminal colors (16 first used in escape sequence) */ 22 | SDL_Color colormap[] = { 23 | /* 8 normal colors */ 24 | {0, 0, 0, 0}, // 0 "black" #000000 25 | {128, 0, 0, 0}, // 1 "red3" #800000 26 | {0, 128, 0, 0}, // 2 "green3" #008000 27 | {128, 128, 0, 0}, // 3 "yellow3" #808000 28 | {0, 0, 128, 0}, // 4 "blue2" #000080 29 | {128, 0, 128, 0}, // 5 "magenta3" #800080 30 | {0, 128, 128, 0}, // 6 "cyan3" #008080 31 | {192, 192, 192, 0}, // 7 "gray90" #C0C0C0 32 | 33 | /* 8 bright colors */ 34 | {128, 128, 128, 0}, // 8 "gray50" #808080 35 | {255, 0, 0, 0}, // 9 "red" #FF0000 36 | {0, 255, 0, 0}, // 10 "green" #00FF00 37 | {255, 255, 0, 0}, // 11 "yellow" #FFFF00 38 | {0, 0, 255, 0}, // 12 "blue" #0000FF 39 | {255, 0, 255, 0}, // 13 "magenta" #FF00FF 40 | {0, 255, 255, 0}, // 14 "cyan" #00FFFF 41 | {255, 255, 255, 0}, // 15 "white" #FFFFFF 42 | 43 | [255] = {0, 0, 0, 0}, 44 | 45 | /* more colors can be added after 255 to use with DefaultXX */ 46 | {204, 204, 204, 0}, // 256 "gray80" #CCCCCC, 47 | {51, 51, 51, 0}, // 257 "gray20" #333333 48 | {16, 16, 16, 0}, // 258 "gray6" #101010 49 | }; 50 | 51 | /* 52 | * Default colors (colorname index) 53 | * foreground, background, cursor, unfocused cursor 54 | */ 55 | unsigned int defaultfg = 7; 56 | unsigned int defaultbg = 0; 57 | unsigned int defaultcs = 256; 58 | unsigned int defaultucs = 257; 59 | -------------------------------------------------------------------------------- /src/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef __KEYBOARD_H__ 2 | #define __KEYBOARD_H__ 3 | 4 | #include 5 | 6 | #define KMOD_SYNTHETIC (1 << 14) 7 | 8 | #if defined(BR2) && !defined(RPI) // Buildroot handhelds with SDL2 (not raspberry pi) 9 | #define JOYBUTTON_UP -13 10 | #define JOYBUTTON_DOWN -14 11 | #define JOYBUTTON_LEFT -15 12 | #define JOYBUTTON_RIGHT -16 13 | #define JOYBUTTON_A -1 14 | #define JOYBUTTON_B -0 15 | #define JOYBUTTON_X -2 16 | #define JOYBUTTON_Y -3 17 | #define JOYBUTTON_SELECT -8 18 | #define JOYBUTTON_START -9 19 | #define JOYBUTTON_L1 -4 20 | #define JOYBUTTON_R1 -5 21 | #define JOYBUTTON_L2 -6 22 | #define JOYBUTTON_R2 -7 23 | #define JOYBUTTON_L3 -11 24 | #define JOYBUTTON_R3 -12 25 | #define JOYBUTTON_MENU -10 26 | 27 | #define KEY_UP JOYBUTTON_UP 28 | #define KEY_DOWN JOYBUTTON_DOWN 29 | #define KEY_LEFT JOYBUTTON_LEFT 30 | #define KEY_RIGHT JOYBUTTON_RIGHT 31 | #define KEY_ENTER JOYBUTTON_A 32 | #define KEY_BACKSPACE JOYBUTTON_B 33 | #define KEY_SHIFT JOYBUTTON_L1 34 | #define KEY_OSKACTIVATE JOYBUTTON_X 35 | #define KEY_OSKLOCATION JOYBUTTON_Y 36 | #define KEY_OSKTOGGLE JOYBUTTON_R1 37 | #define KEY_QUIT JOYBUTTON_MENU 38 | #define KEY_TAB JOYBUTTON_SELECT 39 | #define KEY_RETURN JOYBUTTON_START 40 | #define KEY_ARROW_LEFT JOYBUTTON_L2 41 | #define KEY_ARROW_RIGHT JOYBUTTON_R2 42 | #define KEY_ARROW_UP JOYBUTTON_L3 43 | #define KEY_ARROW_DOWN JOYBUTTON_R3 44 | 45 | #else 46 | // generic Linux PC 47 | #define KEY_UP SDLK_UP 48 | #define KEY_DOWN SDLK_DOWN 49 | #define KEY_LEFT SDLK_LEFT 50 | #define KEY_RIGHT SDLK_RIGHT 51 | #define KEY_ENTER SDLK_F10 52 | #define KEY_BACKSPACE SDLK_BACKSPACE 53 | #define KEY_SHIFT SDLK_LSHIFT 54 | #define KEY_OSKACTIVATE SDLK_F12 55 | #define KEY_OSKLOCATION SDLK_F11 56 | #define KEY_OSKTOGGLE SDLK_F9 57 | #define KEY_QUIT SDLK_UNKNOWN // not used 58 | #define KEY_TAB SDLK_TAB 59 | #define KEY_RETURN SDLK_RETURN 60 | #define KEY_ARROW_LEFT SDLK_UNKNOWN // not used 61 | #define KEY_ARROW_RIGHT SDLK_UNKNOWN // not used 62 | #define KEY_ARROW_UP SDLK_UNKNOWN // not used 63 | #define KEY_ARROW_DOWN SDLK_UNKNOWN // not used 64 | 65 | #endif 66 | 67 | void init_keyboard(); 68 | void draw_keyboard(SDL_Surface *surface); 69 | int handle_keyboard_event(SDL_Event *event); 70 | int handle_narrow_keys_held(int sym); 71 | extern int active; 72 | extern int show_help; 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /embedded-font-editor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bitmap Font Editor 7 | 8 | 9 | 10 |
11 |

Bitmap Font Editor

12 | 13 |
14 |
15 | 16 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 |
43 | 44 | 45 | 46 |
47 |
48 | 49 |
50 |
51 |
52 |

Hex Bytes

53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |

Export Font

62 | 63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/vt100.h: -------------------------------------------------------------------------------- 1 | #ifndef VT100_H 2 | #define VT100_H 3 | 4 | #include 5 | #include 6 | 7 | /* VT100/Terminal related constants */ 8 | #define ESC_BUF_SIZ 256 9 | #define ESC_ARG_SIZ 16 10 | #define STR_BUF_SIZ 256 11 | #define STR_ARG_SIZ 16 12 | #define UTF_SIZ 4 13 | #define VT102ID "\033[?6c" 14 | 15 | /* VT100/Terminal macros */ 16 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 17 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 18 | #define LEN(a) (sizeof(a) / sizeof(a[0])) 19 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 20 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 21 | #define LIMIT(x, a, b) (x) = (x)<(a) ? (a) : (x)>(b) ? (b) : (x) 22 | #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) 23 | #define IS_SET(flag) (term.mode & (flag)) 24 | 25 | /* Type definitions */ 26 | typedef unsigned char uchar; 27 | typedef unsigned int uint; 28 | typedef unsigned long ulong; 29 | typedef unsigned short ushort; 30 | 31 | /* Glyph attributes */ 32 | enum glyph_attribute { 33 | ATTR_NULL = 0, 34 | ATTR_REVERSE = 1, 35 | ATTR_UNDERLINE = 2, 36 | ATTR_BOLD = 4, 37 | ATTR_GFX = 8, 38 | ATTR_ITALIC = 16, 39 | ATTR_BLINK = 32, 40 | }; 41 | 42 | /* Cursor movements */ 43 | enum cursor_movement { CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_SAVE, CURSOR_LOAD }; 44 | 45 | /* Cursor states */ 46 | enum cursor_state { CURSOR_DEFAULT = 0, CURSOR_HIDE = 1, CURSOR_WRAPNEXT = 2 }; 47 | 48 | /* Glyph states */ 49 | enum glyph_state { GLYPH_SET = 1, GLYPH_DIRTY = 2 }; 50 | 51 | /* Terminal modes */ 52 | enum term_mode { MODE_WRAP = 1, MODE_INSERT = 2, MODE_APPKEYPAD = 4, MODE_ALTSCREEN = 8, MODE_CRLF = 16, MODE_MOUSEBTN = 32, MODE_MOUSEMOTION = 64, MODE_MOUSE = 32 | 64, MODE_REVERSE = 128, MODE_KBDLOCK = 256 }; 53 | 54 | /* Escape states */ 55 | enum escape_state { 56 | ESC_START = 1, 57 | ESC_CSI = 2, 58 | ESC_STR = 4, /* DSC, OSC, PM, APC */ 59 | ESC_ALTCHARSET = 8, 60 | ESC_STR_END = 16, /* a final string was encountered */ 61 | ESC_TEST = 32, /* Enter in test mode */ 62 | }; 63 | 64 | /* Bit macros */ 65 | #undef B0 66 | enum { B0 = 1, B1 = 2, B2 = 4, B3 = 8, B4 = 16, B5 = 32, B6 = 64, B7 = 128 }; 67 | 68 | /* Glyph structure */ 69 | typedef struct { 70 | char c[UTF_SIZ]; /* character code */ 71 | uchar mode; /* attribute flags */ 72 | ushort fg; /* foreground */ 73 | ushort bg; /* background */ 74 | uchar state; /* state flags */ 75 | } Glyph; 76 | 77 | typedef Glyph *Line; 78 | 79 | /* Terminal cursor */ 80 | typedef struct { 81 | Glyph attr; /* current char attributes */ 82 | int x; 83 | int y; 84 | char state; 85 | } TCursor; 86 | 87 | /* CSI Escape sequence structs */ 88 | /* ESC '[' [[ [] [;]] ] */ 89 | typedef struct { 90 | char buf[ESC_BUF_SIZ]; /* raw string */ 91 | int len; /* raw string length */ 92 | char priv; 93 | int arg[ESC_ARG_SIZ]; 94 | int narg; /* nb of args */ 95 | char mode; 96 | } CSIEscape; 97 | 98 | /* STR Escape sequence structs */ 99 | /* ESC type [[ [] [;]] ] ESC '\' */ 100 | typedef struct { 101 | char type; /* ESC type ... */ 102 | char buf[STR_BUF_SIZ]; /* raw string */ 103 | int len; /* raw string length */ 104 | char *args[STR_ARG_SIZ]; 105 | int narg; /* nb of args */ 106 | } STREscape; 107 | 108 | /* Internal representation of the screen */ 109 | typedef struct { 110 | int row; /* nb row */ 111 | int col; /* nb col */ 112 | Line *line; /* screen */ 113 | Line *alt; /* alternate screen */ 114 | bool *dirty; /* dirtyness of lines */ 115 | TCursor c; /* cursor */ 116 | int top; /* top scroll limit */ 117 | int bot; /* bottom scroll limit */ 118 | int mode; /* terminal mode flags */ 119 | int esc; /* escape state flags */ 120 | bool *tabs; 121 | } Term; 122 | 123 | /* Global terminal state - extern declarations */ 124 | extern Term term; 125 | extern CSIEscape csiescseq; 126 | extern STREscape strescseq; 127 | extern int cmdfd; 128 | 129 | /* TTY functions */ 130 | void tty_new(void); 131 | void tty_read(void); 132 | void tty_write(const char *s, size_t n); 133 | void tty_resize(void); 134 | 135 | /* Terminal functions */ 136 | void t_clear_region(int x1, int y1, int x2, int y2); 137 | void t_cursor(int mode); 138 | void t_delete_char(int n); 139 | void t_delete_line(int n); 140 | void t_insert_blank(int n); 141 | void t_insert_blank_line(int n); 142 | void t_move_to(int x, int y); 143 | void t_new(int col, int row); 144 | void t_newline(int first_col); 145 | void t_put_tab(bool forward); 146 | void t_putc(char *c, int len); 147 | void t_reset(void); 148 | int t_resize(int col, int row); 149 | void t_scroll_up(int orig, int n); 150 | void t_scroll_down(int orig, int n); 151 | void t_set_attr(int *attr, int l); 152 | void t_set_char(char *c, Glyph *attr, int x, int y); 153 | void t_set_scroll(int t, int b); 154 | void t_swap_screen(void); 155 | void t_set_dirt(int top, int bot); 156 | void t_set_mode(bool priv, bool set, int *args, int narg); 157 | void t_full_dirt(void); 158 | 159 | /* CSI/Escape sequence functions */ 160 | void csi_dump(void); 161 | void csi_handle(void); 162 | void csi_parse(void); 163 | void csi_reset(void); 164 | void str_reset(void); 165 | 166 | /* UTF-8 functions */ 167 | int utf8_decode(char *c, long *u); 168 | int utf8_encode(long *u, char *c); 169 | int utf8_size(char *s); 170 | int is_full_utf8(char *c, int len); 171 | 172 | /* External dependencies from main.c */ 173 | void die(const char *, ...); 174 | void *x_malloc(size_t); 175 | void *x_realloc(void *, size_t); 176 | void *x_calloc(size_t nmemb, size_t size); 177 | size_t x_write(int fd, char *s, size_t len); 178 | void redraw(void); 179 | 180 | #endif /* VT100_H */ -------------------------------------------------------------------------------- /embedded-font-editor/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 9 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 10 | min-height: 100vh; 11 | padding: 20px; 12 | } 13 | 14 | .container { 15 | max-width: 750px; 16 | margin: 0 auto; 17 | background: white; 18 | border-radius: 12px; 19 | box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); 20 | padding: 8px; 21 | } 22 | 23 | h1 { 24 | text-align: center; 25 | color: #333; 26 | margin-bottom: 30px; 27 | font-size: 2em; 28 | } 29 | 30 | h3 { 31 | color: #555; 32 | margin-bottom: 15px; 33 | font-size: 1.2em; 34 | } 35 | 36 | h4 { 37 | color: #666; 38 | margin-bottom: 10px; 39 | font-size: 1em; 40 | } 41 | 42 | /* Controls */ 43 | .controls { 44 | display: flex; 45 | gap: 20px; 46 | flex-wrap: wrap; 47 | margin-bottom: 30px; 48 | padding: 20px; 49 | background: #f8f9fa; 50 | border-radius: 8px; 51 | } 52 | 53 | .control-group { 54 | display: flex; 55 | flex-direction: column; 56 | gap: 8px; 57 | } 58 | 59 | .control-group label { 60 | font-weight: 600; 61 | color: #555; 62 | font-size: 0.9em; 63 | } 64 | 65 | select, input[type="number"] { 66 | padding: 8px 12px; 67 | border: 2px solid #ddd; 68 | border-radius: 6px; 69 | font-size: 1em; 70 | background: white; 71 | cursor: pointer; 72 | transition: border-color 0.3s; 73 | } 74 | 75 | select:hover, input[type="number"]:hover { 76 | border-color: #667eea; 77 | } 78 | 79 | select:focus, input[type="number"]:focus { 80 | outline: none; 81 | border-color: #667eea; 82 | } 83 | 84 | /* Toggle button */ 85 | .toggle-btn { 86 | width: 100%; 87 | margin-top: 15px; 88 | padding: 10px 20px; 89 | border: 2px solid #667eea; 90 | border-radius: 6px; 91 | background: white; 92 | color: #667eea; 93 | font-size: 0.95em; 94 | font-weight: 600; 95 | cursor: pointer; 96 | transition: all 0.3s; 97 | text-align: left; 98 | } 99 | 100 | .toggle-btn:hover { 101 | background: #667eea; 102 | color: white; 103 | } 104 | 105 | /* Character Grid */ 106 | #character-grid { 107 | display: grid; 108 | grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); 109 | gap: 5px; 110 | max-width: 100%; 111 | margin-top: 10px; 112 | max-height: 500px; 113 | overflow-y: auto; 114 | transition: max-height 0.3s ease, opacity 0.3s ease; 115 | opacity: 1; 116 | } 117 | 118 | #character-grid.collapsed { 119 | max-height: 0; 120 | opacity: 0; 121 | overflow: hidden; 122 | margin-top: 0; 123 | } 124 | 125 | .char-grid-break { 126 | grid-column: 1 / -1; 127 | height: 10px; 128 | } 129 | 130 | .char-btn { 131 | width: 40px; 132 | height: 40px; 133 | border: 2px solid #ddd; 134 | border-radius: 4px; 135 | background: white; 136 | font-size: 1.1em; 137 | font-weight: 500; 138 | cursor: pointer; 139 | transition: all 0.2s; 140 | color: #333; 141 | display: flex; 142 | align-items: center; 143 | justify-content: center; 144 | padding: 0; 145 | } 146 | 147 | .char-btn.control-char { 148 | font-size: 0.8em; 149 | color: #999; 150 | } 151 | 152 | .char-btn:hover { 153 | background: #667eea; 154 | color: white; 155 | border-color: #667eea; 156 | transform: scale(1.05); 157 | } 158 | 159 | .char-btn.active { 160 | background: #667eea; 161 | color: white; 162 | border-color: #5568d3; 163 | box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3); 164 | } 165 | 166 | /* Editor Section */ 167 | .editor-section { 168 | display: grid; 169 | grid-template-columns: auto 1fr; 170 | gap: 30px; 171 | margin-bottom: 30px; 172 | } 173 | 174 | @media (max-width: 968px) { 175 | .editor-section { 176 | grid-template-columns: 1fr; 177 | } 178 | } 179 | 180 | /* Matrix Panel */ 181 | .matrix-panel { 182 | background: #f8f9fa; 183 | padding: 20px; 184 | border-radius: 8px; 185 | } 186 | 187 | #matrix-container { 188 | display: inline-grid; 189 | gap: 2px; 190 | padding: 15px; 191 | background: white; 192 | border: 2px solid #ddd; 193 | border-radius: 6px; 194 | margin-bottom: 15px; 195 | } 196 | 197 | .pixel { 198 | width: 30px; 199 | height: 30px; 200 | border: 1px solid #ccc; 201 | cursor: pointer; 202 | transition: all 0.1s; 203 | border-radius: 2px; 204 | } 205 | 206 | .pixel.off { 207 | background: white; 208 | } 209 | 210 | .pixel.on { 211 | background: #333; 212 | } 213 | 214 | .pixel:hover { 215 | transform: scale(1.1); 216 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); 217 | } 218 | 219 | .matrix-controls { 220 | display: flex; 221 | gap: 10px; 222 | flex-wrap: wrap; 223 | } 224 | 225 | /* Buttons */ 226 | button { 227 | padding: 10px 20px; 228 | border: none; 229 | border-radius: 6px; 230 | font-size: 1em; 231 | font-weight: 600; 232 | cursor: pointer; 233 | transition: all 0.3s; 234 | color: white; 235 | } 236 | 237 | #undo-btn { 238 | background: #3498db; 239 | } 240 | 241 | #undo-btn:hover { 242 | background: #2980b9; 243 | transform: translateY(-2px); 244 | box-shadow: 0 4px 12px rgba(52, 152, 219, 0.4); 245 | } 246 | 247 | #redo-btn { 248 | background: #9b59b6; 249 | } 250 | 251 | #redo-btn:hover { 252 | background: #8e44ad; 253 | transform: translateY(-2px); 254 | box-shadow: 0 4px 12px rgba(155, 89, 182, 0.4); 255 | } 256 | 257 | #reset-btn { 258 | background: #e74c3c; 259 | } 260 | 261 | #reset-btn:hover { 262 | background: #c0392b; 263 | transform: translateY(-2px); 264 | box-shadow: 0 4px 12px rgba(231, 76, 60, 0.4); 265 | } 266 | 267 | #copy-hex-btn { 268 | background: #27ae60; 269 | } 270 | 271 | #copy-hex-btn:hover { 272 | background: #229954; 273 | transform: translateY(-2px); 274 | box-shadow: 0 4px 12px rgba(39, 174, 96, 0.4); 275 | } 276 | 277 | #export-c-btn, #export-json-btn { 278 | background: #667eea; 279 | } 280 | 281 | #export-c-btn:hover, #export-json-btn:hover { 282 | background: #5568d3; 283 | transform: translateY(-2px); 284 | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); 285 | } 286 | 287 | button:active { 288 | transform: translateY(0); 289 | } 290 | 291 | /* Data Section */ 292 | .data-section { 293 | background: #f8f9fa; 294 | padding: 20px; 295 | border-radius: 8px; 296 | } 297 | 298 | .data-display { 299 | margin-bottom: 15px; 300 | } 301 | 302 | .hex-data, .binary-data { 303 | background: white; 304 | padding: 15px; 305 | border-radius: 6px; 306 | border: 2px solid #ddd; 307 | } 308 | 309 | code { 310 | display: block; 311 | font-family: 'Courier New', monospace; 312 | font-size: 0.9em; 313 | color: #333; 314 | white-space: pre-wrap; 315 | word-break: break-all; 316 | } 317 | 318 | /* Export Section */ 319 | .export-section { 320 | background: #f8f9fa; 321 | padding: 20px; 322 | border-radius: 8px; 323 | display: flex; 324 | gap: 10px; 325 | flex-wrap: wrap; 326 | } 327 | -------------------------------------------------------------------------------- /embedded-font-editor/app.js: -------------------------------------------------------------------------------- 1 | // Application state 2 | let currentFont = 0; 3 | let currentChar = 65; // Start with 'A' 4 | let currentMatrix = []; 5 | let undoStack = []; 6 | let redoStack = []; 7 | const MAX_HISTORY = 50; 8 | let originalFontData = []; 9 | 10 | // DOM elements 11 | const fontSelect = document.getElementById('font-select'); 12 | const charSelect = document.getElementById('char-select'); 13 | const asciiInput = document.getElementById('ascii-input'); 14 | const matrixContainer = document.getElementById('matrix-container'); 15 | const hexOutput = document.getElementById('hex-output'); 16 | const characterGrid = document.getElementById('character-grid'); 17 | 18 | // Initialize 19 | function init() { 20 | // Store original font data (deep copy) 21 | originalFontData = FONTS.map(font => ({ 22 | ...font, 23 | data: font.data ? [...font.data] : [] 24 | })); 25 | 26 | populateCharacterSelect(); 27 | populateCharacterGrid(); 28 | setupEventListeners(); 29 | loadCharacter(); 30 | } 31 | 32 | // Populate character select dropdown 33 | function populateCharacterSelect() { 34 | charSelect.innerHTML = ''; 35 | for (let i = 0; i < 128; i++) { 36 | const option = document.createElement('option'); 37 | option.value = i; 38 | 39 | if (i < 32) { 40 | option.textContent = `(Control)`; 41 | } else if (i === 32) { 42 | option.textContent = `(Space)`; 43 | } else if (i === 127) { 44 | option.textContent = `(DEL)`; 45 | } else { 46 | option.textContent = `${String.fromCharCode(i)}`; 47 | } 48 | 49 | if (i === currentChar) { 50 | option.selected = true; 51 | } 52 | 53 | charSelect.appendChild(option); 54 | } 55 | } 56 | 57 | // Populate character grid with buttons 58 | function populateCharacterGrid() { 59 | characterGrid.innerHTML = ''; 60 | 61 | // Helper function to create character button 62 | function createCharButton(charCode) { 63 | const btn = document.createElement('button'); 64 | btn.className = 'char-btn'; 65 | btn.dataset.charCode = charCode; 66 | 67 | if (charCode < 32) { 68 | btn.textContent = '␀'; 69 | btn.title = `${charCode}: (Control)`; 70 | btn.classList.add('control-char'); 71 | } else if (charCode === 32) { 72 | btn.textContent = '␣'; 73 | btn.title = `${charCode}: (Space)`; 74 | } else if (charCode === 127) { 75 | btn.textContent = '⌫'; 76 | btn.title = `${charCode}: (DEL)`; 77 | btn.classList.add('control-char'); 78 | } else { 79 | btn.textContent = String.fromCharCode(charCode); 80 | btn.title = `${charCode}: '${String.fromCharCode(charCode)}'`; 81 | } 82 | 83 | if (charCode === currentChar) { 84 | btn.classList.add('active'); 85 | } 86 | 87 | btn.addEventListener('click', () => { 88 | currentChar = charCode; 89 | charSelect.value = charCode; 90 | asciiInput.value = charCode; 91 | updateCharacterGridSelection(); 92 | loadCharacter(); 93 | }); 94 | 95 | return btn; 96 | } 97 | 98 | // Helper to add line break 99 | function addBreak() { 100 | const br = document.createElement('div'); 101 | br.className = 'char-grid-break'; 102 | characterGrid.appendChild(br); 103 | } 104 | 105 | // Control Characters (0-31, 127) 106 | for (let i = 0; i < 32; i++) { 107 | characterGrid.appendChild(createCharButton(i)); 108 | } 109 | addBreak(); 110 | // Numbers & Symbols (32-64) 111 | for (let i = 32; i <= 64; i++) { 112 | characterGrid.appendChild(createCharButton(i)); 113 | } 114 | addBreak(); 115 | // Uppercase (65-90) 116 | for (let i = 65; i <= 90; i++) { 117 | characterGrid.appendChild(createCharButton(i)); 118 | } 119 | addBreak(); 120 | // Lowercase (97-122) 121 | for (let i = 97; i <= 122; i++) { 122 | characterGrid.appendChild(createCharButton(i)); 123 | } 124 | addBreak(); 125 | // Special Characters (91-96, 123-126) 126 | for (let i = 91; i <= 96; i++) { 127 | characterGrid.appendChild(createCharButton(i)); 128 | } 129 | for (let i = 123; i <= 126; i++) { 130 | characterGrid.appendChild(createCharButton(i)); 131 | } 132 | characterGrid.appendChild(createCharButton(127)); 133 | } 134 | 135 | // Update character grid selection 136 | function updateCharacterGridSelection() { 137 | document.querySelectorAll('.char-btn').forEach(btn => { 138 | btn.classList.remove('active'); 139 | if (parseInt(btn.dataset.charCode) === currentChar) { 140 | btn.classList.add('active'); 141 | } 142 | }); 143 | } 144 | 145 | // Setup event listeners 146 | function setupEventListeners() { 147 | fontSelect.addEventListener('change', (e) => { 148 | currentFont = parseInt(e.target.value); 149 | loadCharacter(); 150 | }); 151 | 152 | charSelect.addEventListener('change', (e) => { 153 | currentChar = parseInt(e.target.value); 154 | asciiInput.value = currentChar; 155 | updateCharacterGridSelection(); 156 | loadCharacter(); 157 | }); 158 | 159 | asciiInput.addEventListener('input', (e) => { 160 | let value = parseInt(e.target.value); 161 | if (value >= 0 && value <= 127) { 162 | currentChar = value; 163 | charSelect.value = currentChar; 164 | updateCharacterGridSelection(); 165 | loadCharacter(); 166 | } 167 | }); 168 | 169 | document.getElementById('undo-btn').addEventListener('click', undo); 170 | document.getElementById('redo-btn').addEventListener('click', redo); 171 | document.getElementById('reset-btn').addEventListener('click', resetMatrix); 172 | document.getElementById('copy-hex-btn').addEventListener('click', copyHexData); 173 | document.getElementById('export-c-btn').addEventListener('click', exportAsC); 174 | 175 | // Toggle character grid 176 | document.getElementById('toggle-char-grid').addEventListener('click', function() { 177 | const grid = document.getElementById('character-grid'); 178 | const btn = this; 179 | 180 | if (grid.classList.contains('collapsed')) { 181 | grid.classList.remove('collapsed'); 182 | btn.textContent = '▼ Hide All Characters'; 183 | } else { 184 | grid.classList.add('collapsed'); 185 | btn.textContent = '▶ Show All Characters'; 186 | } 187 | }); 188 | } 189 | 190 | // Load character data into matrix 191 | function loadCharacter() { 192 | const font = FONTS[currentFont]; 193 | const startIdx = currentChar * font.bytesPerChar; 194 | const charBytes = font.data.slice(startIdx, startIdx + font.bytesPerChar); 195 | 196 | // Convert bytes to matrix 197 | currentMatrix = []; 198 | for (let row = 0; row < font.height; row++) { 199 | const rowData = []; 200 | const byte = charBytes[row] || 0; 201 | 202 | for (let col = 0; col < font.width; col++) { 203 | const bitPos = 7 - col; // MSB first 204 | const pixel = (byte >> bitPos) & 1; 205 | rowData.push(pixel); 206 | } 207 | 208 | currentMatrix.push(rowData); 209 | } 210 | 211 | // Clear undo/redo history when loading a new character 212 | undoStack = []; 213 | redoStack = []; 214 | 215 | renderMatrix(); 216 | updateDataDisplay(); 217 | } 218 | 219 | // Render the editable matrix 220 | let isDrawing = false; 221 | let drawState = 0; 222 | 223 | function renderMatrix() { 224 | const font = FONTS[currentFont]; 225 | matrixContainer.innerHTML = ''; 226 | matrixContainer.style.gridTemplateColumns = `repeat(${font.width}, 30px)`; 227 | 228 | for (let row = 0; row < font.height; row++) { 229 | for (let col = 0; col < font.width; col++) { 230 | const pixel = document.createElement('div'); 231 | pixel.className = `pixel ${currentMatrix[row][col] ? 'on' : 'off'}`; 232 | pixel.dataset.row = row; 233 | pixel.dataset.col = col; 234 | 235 | // Mouse down to start drawing 236 | pixel.addEventListener('mousedown', (e) => { 237 | e.preventDefault(); 238 | isDrawing = true; 239 | drawState = currentMatrix[row][col] ? 0 : 1; 240 | saveStateForUndo(); 241 | togglePixel(row, col, drawState); 242 | }); 243 | 244 | // Mouse enter while dragging 245 | pixel.addEventListener('mouseenter', (e) => { 246 | if (isDrawing && e.buttons === 1) { 247 | togglePixel(row, col, drawState); 248 | } 249 | }); 250 | 251 | matrixContainer.appendChild(pixel); 252 | } 253 | } 254 | 255 | // Stop drawing when mouse is released anywhere 256 | document.addEventListener('mouseup', stopDrag); 257 | } 258 | 259 | function stopDrag() { 260 | isDrawing = false; 261 | } 262 | 263 | // Toggle pixel state 264 | function togglePixel(row, col, state) { 265 | if (state !== undefined) { 266 | currentMatrix[row][col] = state; 267 | } else { 268 | currentMatrix[row][col] = currentMatrix[row][col] ? 0 : 1; 269 | } 270 | 271 | saveCharacter(); 272 | renderMatrix(); 273 | updateDataDisplay(); 274 | } 275 | 276 | // Save current matrix back to font data 277 | function saveCharacter() { 278 | const font = FONTS[currentFont]; 279 | const startIdx = currentChar * font.bytesPerChar; 280 | 281 | for (let row = 0; row < font.height; row++) { 282 | let byte = 0; 283 | for (let col = 0; col < font.width; col++) { 284 | const bitPos = 7 - col; // MSB first 285 | if (currentMatrix[row][col]) { 286 | byte |= (1 << bitPos); 287 | } 288 | } 289 | font.data[startIdx + row] = byte; 290 | } 291 | } 292 | 293 | // Update data display 294 | function updateDataDisplay() { 295 | const font = FONTS[currentFont]; 296 | const startIdx = currentChar * font.bytesPerChar; 297 | const charBytes = font.data.slice(startIdx, startIdx + font.bytesPerChar); 298 | 299 | // Hex output 300 | const hexLines = []; 301 | for (let i = 0; i < charBytes.length; i++) { 302 | const byte = charBytes[i]; 303 | const hex = '0x' + byte.toString(16).padStart(2, '0'); 304 | const binary = byte.toString(2).padStart(8, '0'); 305 | const visual = binary.replace(/0/g, ' ').replace(/1/g, '*'); 306 | hexLines.push(`${hex} // ${binary} -- ${visual}`); 307 | } 308 | hexOutput.textContent = hexLines.join('\n'); 309 | } 310 | 311 | // History management 312 | function saveStateForUndo() { 313 | // Deep copy the current matrix state 314 | const stateCopy = currentMatrix.map(row => [...row]); 315 | undoStack.push(stateCopy); 316 | 317 | // Limit history size 318 | if (undoStack.length > MAX_HISTORY) { 319 | undoStack.shift(); 320 | } 321 | 322 | // Clear redo stack when new action is performed 323 | redoStack = []; 324 | } 325 | 326 | function undo() { 327 | if (undoStack.length === 0) return; 328 | 329 | // Save current state to redo stack 330 | const currentState = currentMatrix.map(row => [...row]); 331 | redoStack.push(currentState); 332 | 333 | // Restore previous state 334 | currentMatrix = undoStack.pop(); 335 | 336 | saveCharacter(); 337 | renderMatrix(); 338 | updateDataDisplay(); 339 | } 340 | 341 | function redo() { 342 | if (redoStack.length === 0) return; 343 | 344 | // Save current state to undo stack 345 | const currentState = currentMatrix.map(row => [...row]); 346 | undoStack.push(currentState); 347 | 348 | // Restore next state 349 | currentMatrix = redoStack.pop(); 350 | 351 | saveCharacter(); 352 | renderMatrix(); 353 | updateDataDisplay(); 354 | } 355 | 356 | function resetMatrix() { 357 | saveStateForUndo(); 358 | 359 | // Restore original character data 360 | const originalFont = originalFontData[currentFont]; 361 | const startIdx = currentChar * originalFont.bytesPerChar; 362 | const originalBytes = originalFont.data.slice(startIdx, startIdx + originalFont.bytesPerChar); 363 | 364 | // Convert original bytes back to matrix 365 | currentMatrix = []; 366 | for (let row = 0; row < originalFont.height; row++) { 367 | const rowData = []; 368 | const byte = originalBytes[row] || 0; 369 | 370 | for (let col = 0; col < originalFont.width; col++) { 371 | const bitPos = 7 - col; // MSB first 372 | const pixel = (byte >> bitPos) & 1; 373 | rowData.push(pixel); 374 | } 375 | 376 | currentMatrix.push(rowData); 377 | } 378 | 379 | saveCharacter(); 380 | renderMatrix(); 381 | updateDataDisplay(); 382 | } 383 | 384 | // Copy hex data to clipboard 385 | function copyHexData() { 386 | const font = FONTS[currentFont]; 387 | const startIdx = currentChar * font.bytesPerChar; 388 | const charBytes = font.data.slice(startIdx, startIdx + font.bytesPerChar).map(b => `0x${b.toString(16).padStart(2, '0')}`); 389 | const text = charBytes.join(', ') + ','; 390 | navigator.clipboard.writeText(text).then(() => { 391 | const btn = document.getElementById('copy-hex-btn'); 392 | const originalText = btn.textContent; 393 | btn.textContent = 'Copied!'; 394 | setTimeout(() => { 395 | btn.textContent = originalText; 396 | }, 2000); 397 | }); 398 | } 399 | 400 | // Export as C array 401 | function exportAsC() { 402 | const font = FONTS[currentFont]; 403 | let output = `// ${font.name}: ${font.width}x${font.height} pixels per character\n`; 404 | output += `static const unsigned char embedded_font${currentFont + 1}[] = {\n`; 405 | 406 | for (let charIdx = 0; charIdx < 128; charIdx++) { 407 | const startIdx = charIdx * font.bytesPerChar; 408 | const charBytes = font.data.slice(startIdx, startIdx + font.bytesPerChar); 409 | 410 | const hexValues = charBytes.map(b => '0x' + b.toString(16).padStart(2, '0')).join(', '); 411 | 412 | let charLabel = ''; 413 | if (charIdx < 32) { 414 | charLabel = `${charIdx}: (Control)`; 415 | } else if (charIdx === 32) { 416 | charLabel = `${charIdx}: ' ' (space)`; 417 | } else if (charIdx === 127) { 418 | charLabel = `${charIdx}: DEL`; 419 | } else { 420 | charLabel = `${charIdx}: '${String.fromCharCode(charIdx)}'`; 421 | } 422 | 423 | output += ` ${hexValues}, // ${charLabel}\n`; 424 | } 425 | 426 | output += '};\n'; 427 | 428 | downloadFile(`embedded_font${currentFont + 1}.c`, output); 429 | } 430 | 431 | // Download helper 432 | function downloadFile(filename, content) { 433 | const blob = new Blob([content], { type: 'text/plain' }); 434 | const url = URL.createObjectURL(blob); 435 | const a = document.createElement('a'); 436 | a.href = url; 437 | a.download = filename; 438 | document.body.appendChild(a); 439 | a.click(); 440 | document.body.removeChild(a); 441 | URL.revokeObjectURL(url); 442 | } 443 | 444 | // Start the app 445 | init(); 446 | -------------------------------------------------------------------------------- /src/keyboard.c: -------------------------------------------------------------------------------- 1 | #include "keyboard.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "font.h" 7 | #include "vt100.h" 8 | 9 | #define KEYBOARD_PADDING 16 10 | 11 | #define NUM_ROWS 6 12 | #define NUM_KEYS 18 13 | 14 | static int row_length[NUM_ROWS] = {13, 17, 17, 15, 14, 10}; 15 | 16 | static SDL_Keycode keys[2][NUM_ROWS][NUM_KEYS] = { 17 | {{SDLK_ESCAPE, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_F11, SDLK_F12}, 18 | {SDLK_BACKQUOTE, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_0, SDLK_MINUS, SDLK_EQUALS, SDLK_BACKSPACE, SDLK_INSERT, SDLK_DELETE, SDLK_UP}, 19 | {SDLK_TAB, SDLK_q, SDLK_w, SDLK_e, SDLK_r, SDLK_t, SDLK_y, SDLK_u, SDLK_i, SDLK_o, SDLK_p, SDLK_LEFTBRACKET, SDLK_RIGHTBRACKET, SDLK_BACKSLASH, SDLK_HOME, SDLK_END, SDLK_DOWN}, 20 | {SDLK_CAPSLOCK, SDLK_a, SDLK_s, SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l, SDLK_SEMICOLON, SDLK_QUOTE, SDLK_RETURN, SDLK_PAGEUP, SDLK_LEFT}, 21 | {SDLK_LSHIFT, SDLK_z, SDLK_x, SDLK_c, SDLK_v, SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA, SDLK_PERIOD, SDLK_SLASH, SDLK_RSHIFT, SDLK_PAGEDOWN, SDLK_RIGHT}, 22 | {SDLK_LCTRL, SDLK_LGUI, SDLK_LALT, SDLK_SPACE, SDLK_RALT, SDLK_RGUI, SDLK_RCTRL, SDLK_PRINTSCREEN, KEY_OSKLOCATION, KEY_QUIT}}, 23 | {{SDLK_ESCAPE, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_F11, SDLK_F12}, 24 | {'~', SDLK_EXCLAIM, SDLK_AT, SDLK_HASH, SDLK_DOLLAR, '%', SDLK_CARET, SDLK_AMPERSAND, SDLK_ASTERISK, SDLK_LEFTPAREN, SDLK_RIGHTPAREN, SDLK_UNDERSCORE, SDLK_PLUS, SDLK_BACKSPACE, SDLK_INSERT, SDLK_DELETE, SDLK_UP}, 25 | {SDLK_TAB, SDLK_q, SDLK_w, SDLK_e, SDLK_r, SDLK_t, SDLK_y, SDLK_u, SDLK_i, SDLK_o, SDLK_p, '{', '}', '|', SDLK_HOME, SDLK_END, SDLK_DOWN}, 26 | {SDLK_CAPSLOCK, SDLK_a, SDLK_s, SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l, SDLK_COLON, SDLK_QUOTEDBL, SDLK_RETURN, SDLK_PAGEUP, SDLK_LEFT}, 27 | {SDLK_LSHIFT, SDLK_z, SDLK_x, SDLK_c, SDLK_v, SDLK_b, SDLK_n, SDLK_m, SDLK_LESS, SDLK_GREATER, SDLK_QUESTION, SDLK_RSHIFT, SDLK_PAGEDOWN, SDLK_RIGHT}, 28 | {SDLK_LCTRL, SDLK_LGUI, SDLK_LALT, SDLK_SPACE, SDLK_RALT, SDLK_RGUI, SDLK_RCTRL, SDLK_PRINTSCREEN, KEY_OSKLOCATION, KEY_QUIT}}}; 29 | 30 | static char *syms[2][NUM_ROWS][NUM_KEYS] = {{{"Esc", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", NULL}, 31 | {"` ", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Bsp", "Ins", "Del", " ^ ", NULL}, 32 | {"Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\", "Home", "End", " \xde ", NULL}, 33 | {"Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter", "Pg Up", " < ", NULL}, 34 | {"Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", " Shift", "Pg Dn", " > ", NULL}, 35 | {"Ctl", "", "Alt", " Space ", "Alt", "", "Ctl", "PrS", "Mov", "Exit", NULL}}, 36 | {{"Esc", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", NULL}, 37 | {"~ ", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "Bsp", "Ins", "Del", " ^ ", NULL}, 38 | {"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "|", "Home", "End", " \xde ", NULL}, 39 | {"Caps", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "Enter", "Pg Up", " < ", NULL}, 40 | {"Shift", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", " Shift", "Pg Dn", " > ", NULL}, 41 | {"Ctl", "", "Alt", " Space ", "Alt", "", "Ctl", "PrS", "Mov", "Exit", NULL}}}; 42 | 43 | static unsigned char toggled[NUM_ROWS][NUM_KEYS]; 44 | 45 | static int selected_i = 0, selected_j = 0; 46 | static int visual_offset = 0; 47 | static int shifted = 0; 48 | static int location = 0; 49 | static int mod_state = 0; 50 | 51 | int active = 1; 52 | int show_help = 1; 53 | 54 | static int embedded_font_name = 1; // 1 or 2 55 | static int embedded_font_char_width; 56 | static int embedded_font_char_height; 57 | static int ttf_char_width; 58 | static int ttf_char_height; 59 | 60 | int use_embedded_font_for_keyboard = 0; 61 | 62 | void init_keyboard(int _embedded_font_name, int _use_embedded_font_for_keyboard) { 63 | embedded_font_name = _embedded_font_name; 64 | use_embedded_font_for_keyboard = _use_embedded_font_for_keyboard; 65 | for (int j = 0; j < NUM_ROWS; j++) 66 | for (int i = 0; i < NUM_KEYS; i++) toggled[j][i] = 0; 67 | selected_i = selected_j = shifted = location = 0; 68 | mod_state = 0; 69 | 70 | embedded_font_char_width = get_embedded_font_char_width(embedded_font_name); 71 | embedded_font_char_height = get_embedded_font_char_height(embedded_font_name); 72 | ttf_char_width = get_ttf_char_width(); 73 | ttf_char_height = get_ttf_char_height(); 74 | 75 | if (is_ttf_loaded() && !use_embedded_font_for_keyboard) { 76 | syms[0][2][16] = " v "; 77 | syms[1][2][16] = " v "; 78 | } 79 | } 80 | 81 | char *help1 = 82 | "How to use:\n" 83 | " ARROWS: select key from keyboard\n" 84 | " A: press key\n" 85 | " B: backspace\n" 86 | " L1: shift\n" 87 | " R1: toggle key (for shift/ctrl...)\n" 88 | " Y: change keyboard location\n" 89 | " X: show / hide keyboard\n" 90 | " START: enter\n" 91 | " SELECT: tab\n" 92 | " L2: left\n" 93 | " R2: right\n" 94 | " MENU: quit\n\n" 95 | "Cheatcheet (tutorial at www.shellscript.sh):\n" 96 | " TAB key complete path\n" 97 | " UP/DOWN keys navigate history\n" 98 | " pwd print current directory\n" 99 | " ls list files (-l for file size)\n" 100 | " cd change directory (.. = go up)\n" 101 | " cp copy files (dest can be dir)\n" 102 | " mv move files (dest can be dir)\n" 103 | " rm remove files (use -rf for dir)\n\n"; 104 | 105 | char *help2 = 106 | "How to use:\n" 107 | " ARROWS: select key from keyboard\n" 108 | " A: press key\n" 109 | " B: backspace\n" 110 | " L1: shift\n" 111 | " R1: toggle key (for shift/ctrl...)\n" 112 | " Y: change keyboard location\n" 113 | " X: show / hide keyboard\n" 114 | " START: enter\n" 115 | " SELECT: tab\n" 116 | " L2: left\n" 117 | " R2: right\n" 118 | " MENU: quit\n\n"; 119 | 120 | #define CREDIT "@haoict (c) 2025" 121 | 122 | void draw_keyboard(SDL_Surface *surface) { 123 | unsigned short bg_color = SDL_MapRGB(surface->format, 64, 64, 64); 124 | unsigned short key_color = SDL_MapRGB(surface->format, 128, 128, 128); 125 | unsigned short text_color = SDL_MapRGB(surface->format, 0, 0, 0); 126 | unsigned short sel_color = SDL_MapRGB(surface->format, 128, 255, 128); 127 | unsigned short sel_toggled_color = SDL_MapRGB(surface->format, 255, 255, 128); 128 | unsigned short toggled_color = SDL_MapRGB(surface->format, 192, 192, 0); 129 | if (is_ttf_loaded()) { 130 | show_help = 0; // disable when TTF is available to avoid text overlap 131 | } 132 | if (show_help) { 133 | SDL_FillRect(surface, NULL, text_color); 134 | if (is_ttf_loaded()) { 135 | // Use TTF rendering 136 | draw_string_ttf(surface, "Simple Terminal", 2, 10, (SDL_Color){255, 255, 128, 255}, (SDL_Color){0, 0, 0, 255}); 137 | draw_string_ttf_with_linebreak(surface, embedded_font_name == 2 ? help2 : help1, 8, 30, (SDL_Color){128, 255, 128, 255}, (SDL_Color){0, 0, 0, 255}); 138 | } else { 139 | draw_string(surface, "Simple Terminal", 2, 10, sel_toggled_color, embedded_font_name); 140 | draw_string(surface, embedded_font_name == 2 ? help2 : help1, 8, 30, sel_color, embedded_font_name); 141 | } 142 | #ifdef VERSION 143 | char credit_str[128]; 144 | snprintf(credit_str, sizeof(credit_str), "Version %s - %s", VERSION, CREDIT); 145 | if (is_ttf_loaded()) { 146 | // Use TTF rendering 147 | draw_string_ttf(surface, credit_str, 2, 400, (SDL_Color){255, 255, 128, 255}, (SDL_Color){0, 0, 0, 255}); 148 | } else if (embedded_font_name == 4 || embedded_font_name == 5) { 149 | draw_string(surface, credit_str, 2, 290, sel_toggled_color, embedded_font_name); 150 | } else { 151 | draw_string(surface, credit_str, 2, 220, sel_toggled_color, embedded_font_name); 152 | } 153 | #else 154 | if (is_ttf_loaded()) { 155 | // Use TTF rendering 156 | draw_string_ttf(surface, CREDIT, 2, 220, (SDL_Color){255, 255, 128, 255}, (SDL_Color){0, 0, 0, 255}); 157 | } else if (embedded_font_name == 4 || embedded_font_name == 5) { 158 | draw_string(surface, CREDIT, 2, 290, sel_toggled_color, embedded_font_name); 159 | } else { 160 | draw_string(surface, CREDIT, 2, 220, sel_toggled_color, embedded_font_name); 161 | } 162 | #endif 163 | return; 164 | } 165 | 166 | if (!active) return; 167 | 168 | if (use_embedded_font_for_keyboard || !is_ttf_loaded()) { 169 | int total_length = -1; 170 | for (int i = 0; i < NUM_KEYS && syms[0][0][i]; i++) { 171 | total_length += (1 + strlen(syms[0][0][i])) * embedded_font_char_width; 172 | } 173 | int center_x = (surface->w - total_length) / 2; 174 | int x = center_x, y = surface->h - embedded_font_char_height * (NUM_ROWS)-KEYBOARD_PADDING; 175 | if (location == 1) y = KEYBOARD_PADDING; 176 | 177 | SDL_Rect keyboard_rect = {x - 4, y - 3, total_length + 3, NUM_ROWS * (embedded_font_name == 3 ? embedded_font_char_height + 2 : embedded_font_char_height) + 3}; 178 | SDL_FillRect(surface, &keyboard_rect, bg_color); 179 | 180 | for (int j = 0; j < NUM_ROWS; j++) { 181 | x = center_x; 182 | for (int i = 0; i < row_length[j]; i++) { 183 | int length = strlen(syms[shifted][j][i]); 184 | SDL_Rect key_rect = {x - 2, y - 1, length * embedded_font_char_width + embedded_font_char_width - 2, embedded_font_name == 3 ? embedded_font_char_height + 1 : embedded_font_char_height - 1}; 185 | if (toggled[j][i]) { 186 | if (selected_i == i && selected_j == j) { 187 | SDL_FillRect(surface, &key_rect, sel_toggled_color); 188 | } else { 189 | SDL_FillRect(surface, &key_rect, toggled_color); 190 | } 191 | } else if (selected_i == i && selected_j == j) { 192 | SDL_FillRect(surface, &key_rect, sel_color); 193 | } else { 194 | SDL_FillRect(surface, &key_rect, key_color); 195 | } 196 | draw_string(surface, syms[shifted][j][i], x, y, text_color, embedded_font_name); 197 | x += embedded_font_char_width * (length + 1); 198 | } 199 | y += embedded_font_name == 3 ? embedded_font_char_height + 2 : embedded_font_char_height; 200 | } 201 | } else { 202 | int total_length = -1; 203 | for (int i = 0; i < NUM_KEYS && syms[0][0][i]; i++) { 204 | total_length += (1 + strlen(syms[0][0][i])) * ttf_char_width; 205 | } 206 | int center_x = (surface->w - total_length) / 2; 207 | int x = center_x; 208 | int y = surface->h - ttf_char_height * (NUM_ROWS)-KEYBOARD_PADDING; 209 | if (location == 1) y = KEYBOARD_PADDING; 210 | 211 | SDL_Rect keyboard_rect = {x - 4, y - 3, total_length + 3, NUM_ROWS * ttf_char_height + 3}; 212 | SDL_FillRect(surface, &keyboard_rect, bg_color); 213 | 214 | for (int j = 0; j < NUM_ROWS; j++) { 215 | x = center_x; 216 | for (int i = 0; i < row_length[j]; i++) { 217 | SDL_Color ttf_shaded_bg; 218 | int length = strlen(syms[shifted][j][i]); 219 | SDL_Rect key_rect = {x - 2, y - 1, length * ttf_char_width + ttf_char_width - 2, ttf_char_height - 1}; 220 | if (toggled[j][i]) { 221 | if (selected_i == i && selected_j == j) { 222 | ttf_shaded_bg = (SDL_Color){255, 255, 128, 255}; 223 | SDL_FillRect(surface, &key_rect, sel_toggled_color); 224 | } else { 225 | ttf_shaded_bg = (SDL_Color){192, 192, 0, 255}; 226 | SDL_FillRect(surface, &key_rect, toggled_color); 227 | } 228 | } else if (selected_i == i && selected_j == j) { 229 | ttf_shaded_bg = (SDL_Color){128, 255, 128, 255}; 230 | SDL_FillRect(surface, &key_rect, sel_color); 231 | } else { 232 | ttf_shaded_bg = (SDL_Color){128, 128, 128, 255}; 233 | SDL_FillRect(surface, &key_rect, key_color); 234 | } 235 | draw_string_ttf(surface, syms[shifted][j][i], x, y - 2, (SDL_Color){0, 0, 0, 255}, ttf_shaded_bg); 236 | x += ttf_char_width * (length + 1); 237 | } 238 | y += ttf_char_height; 239 | } 240 | } 241 | } 242 | 243 | enum { STATE_TYPED, STATE_UP, STATE_DOWN }; 244 | 245 | void update_modstate(int key, int state) { 246 | // SDLMod mod_state = SDL_GetModState(); 247 | if (state == STATE_DOWN) { 248 | if (key == SDLK_LSHIFT) 249 | mod_state |= KMOD_LSHIFT; 250 | else if (key == SDLK_RSHIFT) 251 | mod_state |= KMOD_RSHIFT; 252 | else if (key == SDLK_LCTRL) 253 | mod_state |= KMOD_LCTRL; 254 | else if (key == SDLK_RCTRL) 255 | mod_state |= KMOD_RCTRL; 256 | else if (key == SDLK_LALT) 257 | mod_state |= KMOD_LALT; 258 | else if (key == SDLK_RALT) 259 | mod_state |= KMOD_RALT; 260 | else if (key == SDLK_LGUI) 261 | mod_state |= KMOD_LGUI; 262 | else if (key == SDLK_RGUI) 263 | mod_state |= KMOD_RGUI; 264 | else if (key == SDLK_NUMLOCKCLEAR) 265 | mod_state |= KMOD_NUM; 266 | else if (key == SDLK_CAPSLOCK) 267 | mod_state |= KMOD_CAPS; 268 | else if (key == SDLK_MODE) 269 | mod_state |= KMOD_MODE; 270 | } else if (state == STATE_UP) { 271 | if (key == SDLK_LSHIFT) 272 | mod_state &= ~KMOD_LSHIFT; 273 | else if (key == SDLK_RSHIFT) 274 | mod_state &= ~KMOD_RSHIFT; 275 | else if (key == SDLK_LCTRL) 276 | mod_state &= ~KMOD_LCTRL; 277 | else if (key == SDLK_RCTRL) 278 | mod_state &= ~KMOD_RCTRL; 279 | else if (key == SDLK_LALT) 280 | mod_state &= ~KMOD_LALT; 281 | else if (key == SDLK_RALT) 282 | mod_state &= ~KMOD_RALT; 283 | else if (key == SDLK_LGUI) 284 | mod_state &= ~KMOD_LGUI; 285 | else if (key == SDLK_RGUI) 286 | mod_state &= ~KMOD_RGUI; 287 | else if (key == SDLK_NUMLOCKCLEAR) 288 | mod_state &= ~KMOD_NUM; 289 | else if (key == SDLK_CAPSLOCK) 290 | mod_state &= ~KMOD_CAPS; 291 | else if (key == SDLK_MODE) 292 | mod_state &= ~KMOD_MODE; 293 | } 294 | SDL_SetModState(mod_state); 295 | } 296 | 297 | void simulate_key(int key, int state) { 298 | update_modstate(key, state); 299 | SDL_Event event = {.key = {.type = SDL_KEYDOWN, .state = SDL_PRESSED, .keysym = {.scancode = 0, .sym = key, .mod = KMOD_SYNTHETIC}}}; 300 | if (state == STATE_TYPED) { 301 | SDL_PushEvent(&event); 302 | event.key.type = SDL_KEYUP; 303 | event.key.state = SDL_RELEASED; 304 | } else if (state == STATE_UP) { 305 | event.key.type = SDL_KEYUP; 306 | event.key.state = SDL_RELEASED; 307 | } 308 | SDL_PushEvent(&event); 309 | // printf("%d\n", key); 310 | } 311 | 312 | int compute_visual_offset(int col, int row) { 313 | int sum = 0; 314 | for (int i = 0; i < col; i++) sum += 1 + strlen(syms[0][row][i]); 315 | sum += (1 + strlen(syms[0][row][col])) / 2; 316 | return sum; 317 | } 318 | 319 | int compute_new_col(int visual_offset, int old_row, int new_row) { 320 | // For the short last row (the row has the space key), we manually adjust the mapping to make navigation feel more natural 321 | if (new_row == 5) { 322 | if (old_row == 0) { 323 | if (strncmp(syms[0][0][selected_i], "F5", 2) == 0 || strncmp(syms[0][0][selected_i], "F6", 2) == 0) { 324 | return 3; // space 325 | } 326 | } else if (old_row == 4) { 327 | if (strncmp(syms[0][4][selected_i], "n", 1) == 0 || strncmp(syms[0][4][selected_i], "m", 1) == 0 || strncmp(syms[0][4][selected_i], ",", 1) == 0) { 328 | return 3; // space 329 | } 330 | } 331 | } 332 | 333 | // Original logic for other cases 334 | int new_sum = 0; 335 | int new_col = 0; 336 | while (new_col < row_length[new_row] - 1 && new_sum + (1 + strlen(syms[0][new_row][new_col])) / 2 < visual_offset) { 337 | new_sum += 1 + strlen(syms[0][new_row][new_col]); 338 | new_col++; 339 | } 340 | return new_col; 341 | } 342 | 343 | #if defined(RGB30) 344 | static int rgb30_first_jbutton10_pressed = 0; // TODO: temp fix for RGB30, for unknown reason, Joystick jbutton 10 (KEY_QUIT) always triggers at startup, so we must ignore it 345 | #endif 346 | int handle_keyboard_event(SDL_Event *event) { 347 | if (event->key.type == SDL_KEYDOWN && event->key.keysym.sym == KEY_QUIT) { 348 | #if defined(RGB30) 349 | if (!rgb30_first_jbutton10_pressed) { 350 | rgb30_first_jbutton10_pressed = 1; 351 | return 1; 352 | } 353 | #endif 354 | printf("Exit event requested by Exit button\n"); 355 | SDL_Event quit_event; 356 | quit_event.type = SDL_QUIT; 357 | SDL_PushEvent(&quit_event); 358 | return 1; 359 | } 360 | 361 | if (event->key.type == SDL_KEYDOWN && !(event->key.keysym.mod & KMOD_SYNTHETIC) && event->key.keysym.sym == KEY_OSKACTIVATE) { 362 | if (show_help) { 363 | show_help = 0; 364 | return 1; 365 | } 366 | active = !active; 367 | return 1; 368 | } 369 | 370 | if ((event->key.type == SDL_KEYUP || event->key.type == SDL_KEYDOWN) && event->key.keysym.mod & KMOD_SYNTHETIC) { 371 | if (event->key.type == SDL_KEYDOWN) { 372 | switch (event->key.keysym.sym) { 373 | case SDLK_PRINTSCREEN: 374 | printf("Screenshot event requested\n"); 375 | SDL_Event screenshotEvent; 376 | screenshotEvent.type = SDL_USEREVENT; 377 | screenshotEvent.user.code = 1; 378 | SDL_PushEvent(&screenshotEvent); 379 | return 1; 380 | case KEY_OSKLOCATION: 381 | // printf("Change keyboard location button pressed, sym: %d\n", event->key.keysym.sym); 382 | location = !location; 383 | return 1; 384 | default: 385 | break; 386 | } 387 | } 388 | // printf("handle_keyboard_event: type: %s, sym: %d (%s), scancode:%d\n", event->key.type == SDL_KEYDOWN ? "keydown" : "keyup", event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym), event->key.keysym.scancode); 389 | return 0; 390 | } 391 | 392 | if (!active) { 393 | #if defined(BR2) && !defined(RPI) 394 | // handle joystick button directly when OSK is inactive 395 | if (event->key.type == SDL_KEYDOWN && event->key.state == SDL_PRESSED) { 396 | if (event->key.keysym.sym == JOYBUTTON_UP) { 397 | simulate_key(SDLK_UP, STATE_TYPED); 398 | } else if (event->key.keysym.sym == JOYBUTTON_DOWN) { 399 | simulate_key(SDLK_DOWN, STATE_TYPED); 400 | } else if (event->key.keysym.sym == JOYBUTTON_LEFT) { 401 | simulate_key(SDLK_LEFT, STATE_TYPED); 402 | } else if (event->key.keysym.sym == JOYBUTTON_RIGHT) { 403 | simulate_key(SDLK_RIGHT, STATE_TYPED); 404 | } else if (event->key.keysym.sym == JOYBUTTON_START || event->key.keysym.sym == JOYBUTTON_A) { 405 | simulate_key(SDLK_RETURN, STATE_TYPED); 406 | } else if (event->key.keysym.sym == JOYBUTTON_SELECT) { 407 | simulate_key(SDLK_TAB, STATE_TYPED); 408 | } else if (event->key.keysym.sym == JOYBUTTON_B) { 409 | tty_write("\003", 1); // Ctrl+C 410 | } 411 | return 1; 412 | } 413 | #endif 414 | return 0; 415 | } 416 | 417 | if (event->key.type == SDL_KEYDOWN && event->key.state == SDL_PRESSED) { 418 | // printf("handle_keyboard_event: type: %s, sym: %d (%s), scancode:%d\n", event->key.type == SDL_KEYDOWN ? "keydown" : "keyup", event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym), event->key.keysym.scancode); 419 | if (show_help) { 420 | // do nothing 421 | } else if (event->key.keysym.sym == KEY_SHIFT) { 422 | shifted = 1; 423 | toggled[4][0] = 1; 424 | update_modstate(SDLK_LSHIFT, STATE_DOWN); 425 | } else if (event->key.keysym.sym == KEY_OSKLOCATION) { 426 | location = !location; 427 | } else if (event->key.keysym.sym == KEY_BACKSPACE) { 428 | simulate_key(SDLK_BACKSPACE, STATE_TYPED); 429 | } else if (event->key.keysym.sym == KEY_ARROW_UP || event->key.keysym.sym == SDLK_VOLUMEUP) { 430 | simulate_key(SDLK_UP, STATE_TYPED); 431 | } else if (event->key.keysym.sym == KEY_ARROW_DOWN || event->key.keysym.sym == SDLK_VOLUMEDOWN) { 432 | simulate_key(SDLK_DOWN, STATE_TYPED); 433 | } else if (event->key.keysym.sym == KEY_ARROW_LEFT) { 434 | simulate_key(SDLK_LEFT, STATE_TYPED); 435 | } else if (event->key.keysym.sym == KEY_ARROW_RIGHT) { 436 | simulate_key(SDLK_RIGHT, STATE_TYPED); 437 | } else if (event->key.keysym.sym == KEY_TAB) { 438 | simulate_key(SDLK_TAB, STATE_TYPED); 439 | } else if (event->key.keysym.sym == KEY_RETURN) { 440 | simulate_key(SDLK_RETURN, STATE_TYPED); 441 | } else if (event->key.keysym.sym == KEY_OSKTOGGLE) { 442 | toggled[selected_j][selected_i] = 1 - toggled[selected_j][selected_i]; 443 | if (toggled[selected_j][selected_i]) 444 | simulate_key(keys[shifted][selected_j][selected_i], STATE_DOWN); 445 | else 446 | simulate_key(keys[shifted][selected_j][selected_i], STATE_UP); 447 | if (selected_j == 4 && (selected_i == 0 || selected_i == 11)) shifted = toggled[selected_j][selected_i]; 448 | } else if (event->key.keysym.sym == KEY_ENTER) { 449 | int key = keys[shifted][selected_j][selected_i]; 450 | if (mod_state & KMOD_CTRL) { 451 | if (key >= 64 && key < 64 + 32) 452 | simulate_key(key - 64, STATE_DOWN); 453 | else if (key >= 97 && key < 97 + 31) 454 | simulate_key(key - 96, STATE_DOWN); 455 | } else if (mod_state & KMOD_SHIFT && key >= SDLK_a && key <= SDLK_z) { 456 | simulate_key(key - SDLK_a + 'A', STATE_TYPED); 457 | } else { 458 | simulate_key(key, STATE_TYPED); 459 | } 460 | } else { 461 | // fprintf(stderr,"unrecognized key: %d\n",event->key.keysym.sym); 462 | // return 0; 463 | } 464 | } else if (event->key.type == SDL_KEYUP || event->key.state == SDL_RELEASED) { 465 | if (show_help) { 466 | show_help = 0; 467 | } else if (event->key.keysym.sym == KEY_SHIFT) { 468 | shifted = 0; 469 | toggled[4][0] = 0; 470 | update_modstate(SDLK_LSHIFT, STATE_UP); 471 | } 472 | } 473 | return 1; 474 | } 475 | 476 | int handle_narrow_keys_held(int sym) { 477 | if (!active || show_help) { 478 | return 0; 479 | } 480 | if (sym == KEY_LEFT) { 481 | if (selected_i > 0) 482 | selected_i--; 483 | else 484 | selected_i = row_length[selected_j] - 1; 485 | visual_offset = compute_visual_offset(selected_i, selected_j); 486 | } else if (sym == KEY_RIGHT) { 487 | if (selected_i < row_length[selected_j] - 1) 488 | selected_i++; 489 | else 490 | selected_i = 0; 491 | visual_offset = compute_visual_offset(selected_i, selected_j); 492 | } else if (sym == KEY_UP) { 493 | if (selected_j > 0) { 494 | selected_i = compute_new_col(visual_offset, selected_j, selected_j - 1); 495 | selected_j--; 496 | } else { 497 | selected_i = compute_new_col(visual_offset, selected_j, NUM_ROWS - 1); 498 | selected_j = NUM_ROWS - 1; 499 | } 500 | if (selected_i >= row_length[selected_j]) { 501 | selected_i = row_length[selected_j] - 1; 502 | } 503 | } else if (sym == KEY_DOWN) { 504 | if (selected_j < NUM_ROWS - 1) { 505 | selected_i = compute_new_col(visual_offset, selected_j, selected_j + 1); 506 | selected_j++; 507 | } else { 508 | selected_i = compute_new_col(visual_offset, selected_j, 0); 509 | selected_j = 0; 510 | } 511 | if (selected_i < 0) { 512 | selected_i = 0; 513 | } 514 | } 515 | return 1; 516 | } 517 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "config.h" 28 | #include "font.h" 29 | #include "keyboard.h" 30 | #include "vt100.h" 31 | 32 | #define USAGE "Simple Terminal\nusage: simple-terminal [-h] [-scale 2.0] [-font font.ttf] [-fontsize 14] [-fontshade 0|1|2] [-o file] [-q] [-r command ...]\n" 33 | 34 | /* Arbitrary sizes */ 35 | #define DRAW_BUF_SIZ 20 * 1024 36 | 37 | #define REDRAW_TIMEOUT (80 * 1000) /* 80 ms */ 38 | 39 | /* macros */ 40 | #define TIMEDIFF(t1, t2) ((t1.tv_sec - t2.tv_sec) * 1000 + (t1.tv_usec - t2.tv_usec) / 1000) 41 | 42 | enum WindowState { WIN_VISIBLE = 1, WIN_REDRAW = 2, WIN_FOCUSED = 4 }; 43 | 44 | /* Purely graphic info */ 45 | typedef struct { 46 | // Colormap cmap; 47 | SDL_Window *window; 48 | SDL_Renderer *renderer; 49 | SDL_Texture *texture; 50 | SDL_Surface *surface; 51 | int width, height; /* window width and height */ 52 | int tty_width, tty_height; /* tty width and height */ 53 | int char_height, char_width; /* char height and width */ 54 | char state; /* focus, redraw, visible */ 55 | } MainWindow; 56 | 57 | /* Drawing Context */ 58 | typedef struct { 59 | SDL_Color colors[LEN(colormap) < 256 ? 256 : LEN(colormap)]; 60 | // TTF_Font *font, *ifont, *bfont, *ibfont; 61 | } DrawingContext; 62 | 63 | /* 64 | * Special keys (change & recompile accordingly) 65 | * Keep in mind that kpress() in main.c hardcodes some keys. 66 | * Mask value: 67 | * * Use XK_NO_MOD to match the key alone (no modifiers) 68 | */ 69 | #define XK_ANY_MOD 0 70 | 71 | typedef struct { 72 | SDL_Keycode k; // key 73 | Uint16 mask; // modifier mask: KMOD_ALT, KMOD_CTRL, KMOD_SHIFT, KMOD_CAPS, ... 74 | char s[ESC_BUF_SIZ]; // output string 75 | } NonPrintingKeyboardKey; 76 | 77 | static NonPrintingKeyboardKey non_printing_keyboard_keys[] = { 78 | {SDLK_ESCAPE, XK_ANY_MOD, "\033"}, // Escape 79 | {SDLK_TAB, KMOD_LSHIFT, "\033[Z"}, // Shift+Tab 80 | {SDLK_TAB, KMOD_RSHIFT, "\033[Z"}, // Shift+Tab 81 | {SDLK_TAB, XK_ANY_MOD, "\t"}, // Tab 82 | {SDLK_RETURN, KMOD_LALT, "\033\r"}, // Alt+Enter 83 | {SDLK_RETURN, KMOD_RALT, "\033\r"}, // Alt+Enter 84 | {SDLK_RETURN, XK_ANY_MOD, "\r"}, // Enter 85 | {SDLK_LEFT, XK_ANY_MOD, "\033[D"}, // Left 86 | {SDLK_RIGHT, XK_ANY_MOD, "\033[C"}, // Right 87 | {SDLK_UP, XK_ANY_MOD, "\033[A"}, // Up 88 | {SDLK_DOWN, XK_ANY_MOD, "\033[B"}, // Down 89 | {SDLK_BACKSPACE, XK_ANY_MOD, "\177"}, // Backspace 90 | {SDLK_HOME, XK_ANY_MOD, "\033[1~"}, // Home 91 | {SDLK_INSERT, XK_ANY_MOD, "\033[2~"}, // Insert 92 | {SDLK_DELETE, XK_ANY_MOD, "\033[3~"}, // Delete 93 | {SDLK_END, XK_ANY_MOD, "\033[4~"}, // End 94 | {SDLK_PAGEUP, XK_ANY_MOD, "\033[5~"}, // Page Up 95 | {SDLK_PAGEDOWN, XK_ANY_MOD, "\033[6~"}, // Page Down 96 | {SDLK_F1, XK_ANY_MOD, "\033OP"}, // F1 97 | {SDLK_F2, XK_ANY_MOD, "\033OQ"}, // F2 98 | {SDLK_F3, XK_ANY_MOD, "\033OR"}, // F3 99 | {SDLK_F4, XK_ANY_MOD, "\033OS"}, // F4 100 | {SDLK_F5, XK_ANY_MOD, "\033[15~"}, // F5 101 | {SDLK_F6, XK_ANY_MOD, "\033[17~"}, // F6 102 | {SDLK_F7, XK_ANY_MOD, "\033[18~"}, // F7 103 | {SDLK_F8, XK_ANY_MOD, "\033[19~"}, // F8 104 | {SDLK_F9, XK_ANY_MOD, "\033[20~"}, // F9 105 | {SDLK_F10, XK_ANY_MOD, "\033[21~"}, // F10 106 | {SDLK_F11, XK_ANY_MOD, "\033[23~"}, // F11 107 | {SDLK_F12, XK_ANY_MOD, "\033[24~"}, // F12 108 | }; 109 | 110 | /* SDL Surfaces */ 111 | SDL_Surface *screen; 112 | SDL_Surface *osk_screen; 113 | 114 | static void draw(void); 115 | static void draw_region(int, int, int, int); 116 | static void main_loop(void); 117 | int tty_thread(void *unused); 118 | 119 | static void x_draws(char *, Glyph, int, int, int, int); 120 | static void x_clear(int, int, int, int); 121 | static void x_draw_cursor(void); 122 | static void sdl_init(void); 123 | static void create_tty_thread(); 124 | static void init_color_map(void); 125 | static void sdl_term_clear(int, int, int, int); 126 | static void x_resize(int, int); 127 | static void scale_to_size(int, int); 128 | static char *k_map(SDL_Keycode, Uint16); 129 | static void k_press(SDL_Event *); 130 | static void text_input(SDL_Event *); 131 | static void window_event_handler(SDL_Event *); 132 | 133 | static void update_render(void); 134 | static Uint32 clear_popup_timer(Uint32 interval, void *param); 135 | 136 | static void (*event_handler[SDL_LASTEVENT])(SDL_Event *) = {[SDL_KEYDOWN] = k_press, [SDL_TEXTINPUT] = text_input, [SDL_WINDOWEVENT] = window_event_handler}; 137 | 138 | /* Globals */ 139 | static DrawingContext drawing_ctx; 140 | static MainWindow main_window; 141 | static SDL_Joystick *joystick; 142 | 143 | SDL_Thread *thread = NULL; 144 | 145 | char **opt_cmd = NULL; 146 | int opt_cmd_size = 0; 147 | char *opt_io = NULL; 148 | 149 | static int embedded_font_name = 1; // 1 or 2 150 | static volatile int thread_should_exit = 0; 151 | static int shutdown_called = 0; 152 | 153 | char popup_message[256]; 154 | 155 | size_t x_write(int fd, char *s, size_t len) { 156 | size_t aux = len; 157 | 158 | while (len > 0) { 159 | ssize_t r = write(fd, s, len); 160 | if (r < 0) return r; 161 | len -= r; 162 | s += r; 163 | } 164 | return aux; 165 | } 166 | 167 | void *x_malloc(size_t len) { 168 | void *p = malloc(len); 169 | if (!p) die("Out of memory\n"); 170 | return p; 171 | } 172 | 173 | void *x_realloc(void *p, size_t len) { 174 | if ((p = realloc(p, len)) == NULL) die("Out of memory\n"); 175 | return p; 176 | } 177 | 178 | void *x_calloc(size_t nmemb, size_t size) { 179 | void *p = calloc(nmemb, size); 180 | if (!p) die("Out of memory\n"); 181 | return p; 182 | } 183 | 184 | void sdl_load_fonts() { 185 | // Try to load TTF font if opt_font is set 186 | if (opt_font && init_ttf_font(opt_font, opt_fontsize, opt_fontshade)) { 187 | main_window.char_width = get_ttf_char_width(); 188 | main_window.char_height = get_ttf_char_height(); 189 | } else { 190 | // Fallback to bitmap font 191 | main_window.char_width = get_embedded_font_char_width(embedded_font_name); 192 | main_window.char_height = get_embedded_font_char_height(embedded_font_name); 193 | fprintf(stderr, "Using embedded bitmap font %d (%dx%d)\n", embedded_font_name, main_window.char_width, main_window.char_height); 194 | } 195 | } 196 | 197 | void sdl_shutdown(void) { 198 | if (SDL_WasInit(SDL_INIT_EVERYTHING) != 0 && !shutdown_called) { 199 | shutdown_called = 1; 200 | fprintf(stderr, "SDL shutting down\n"); 201 | if (thread) { 202 | printf("Signaling ttythread to exit...\n"); 203 | thread_should_exit = 1; 204 | // tty_write n key to answer y/n question if blocked on ttyread 205 | tty_write("n", 1); 206 | 207 | tty_write("\033[?1000l", 7); // disable mouse tracking to unblock ttyread 208 | SDL_WaitThread(thread, NULL); // Wait for thread to exit cleanly 209 | // SDL_KillThread(thread); 210 | thread = NULL; 211 | } 212 | 213 | // Cleanup TTF font 214 | cleanup_ttf_font(); 215 | 216 | if (main_window.surface) SDL_FreeSurface(main_window.surface); 217 | if (osk_screen) SDL_FreeSurface(osk_screen); 218 | main_window.surface = NULL; 219 | SDL_JoystickClose(joystick); 220 | SDL_Quit(); 221 | } 222 | } 223 | 224 | void window_event_handler(SDL_Event *event) { 225 | #ifdef BR2 226 | return; // no resize for BR2 handheld devices builds because of kms video driver 227 | #endif 228 | switch (event->window.event) { 229 | case SDL_WINDOWEVENT_RESIZED: 230 | scale_to_size(event->window.data1, event->window.data2); 231 | break; 232 | default: 233 | break; 234 | } 235 | } 236 | 237 | void scale_to_size(int width, int height) { 238 | if (width <= 0 || height <= 0 || width > 8192 || height > 8192) return; 239 | main_window.width = width; 240 | main_window.height = height; 241 | printf("Set scale to size: %dx%d (x%.1f)\n", main_window.width, main_window.height, opt_scale); 242 | 243 | // Recreate texture for new size 244 | if (main_window.texture) { 245 | SDL_DestroyTexture(main_window.texture); 246 | } 247 | main_window.texture = SDL_CreateTexture(main_window.renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, main_window.width, main_window.height); 248 | if (!main_window.texture) { 249 | fprintf(stderr, "Unable to recreate texture: %s\n", SDL_GetError()); 250 | exit(EXIT_FAILURE); 251 | } 252 | 253 | // Recreate surfaces 254 | if (main_window.surface) SDL_FreeSurface(main_window.surface); 255 | main_window.surface = SDL_CreateRGBSurface(0, main_window.width, main_window.height, 16, 0xF800, 0x7E0, 0x1F, 0); // console screen 256 | if (osk_screen) SDL_FreeSurface(osk_screen); 257 | osk_screen = SDL_CreateRGBSurface(0, main_window.width, main_window.height, 16, 0xF800, 0x7E0, 0x1F, 0); // for keyboard mix 258 | 259 | // Recreate screen surface for compatibility 260 | if (screen) SDL_FreeSurface(screen); 261 | screen = SDL_CreateRGBSurface(0, 640, 480, 16, 0xF800, 0x7E0, 0x1F, 0); 262 | 263 | // resize terminal to fit window 264 | int col, row; 265 | col = (main_window.width - 2 * borderpx) / main_window.char_width; 266 | row = (main_window.height - 2 * borderpx) / main_window.char_height; 267 | t_resize(col, row); 268 | x_resize(col, row); 269 | tty_resize(); 270 | } 271 | 272 | void sdl_init(void) { 273 | fprintf(stderr, "SDL init\n"); 274 | 275 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { 276 | fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError()); 277 | exit(EXIT_FAILURE); 278 | } 279 | 280 | SDL_ShowCursor(0); 281 | SDL_StartTextInput(); 282 | 283 | /* font */ 284 | sdl_load_fonts(); 285 | 286 | /* colors */ 287 | init_color_map(); 288 | 289 | int display_index = 0; // usually 0 unless you have multiple screens 290 | SDL_DisplayMode mode; 291 | if (SDL_GetCurrentDisplayMode(display_index, &mode) != 0) { 292 | printf("SDL_GetCurrentDisplayMode failed: %s\n", SDL_GetError()); 293 | main_window.width = initial_width; 294 | main_window.height = initial_height; 295 | } else { 296 | printf("Detected screen: %dx%d @ %dHz\n", mode.w, mode.h, mode.refresh_rate); 297 | main_window.width = mode.w; 298 | main_window.height = mode.h; 299 | #ifndef BR2 300 | main_window.width = initial_width * 2; 301 | main_window.height = initial_height * 2; 302 | #endif 303 | printf("Setting resolution to: %dx%d\n", main_window.width, main_window.height); 304 | } 305 | 306 | main_window.window = SDL_CreateWindow("Simple Terminal", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, main_window.width, main_window.height, SDL_WINDOW_SHOWN); 307 | if (!main_window.window) { 308 | fprintf(stderr, "Unable to create window: %s\n", SDL_GetError()); 309 | exit(EXIT_FAILURE); 310 | } 311 | 312 | main_window.renderer = SDL_CreateRenderer(main_window.window, -1, SDL_RENDERER_ACCELERATED); 313 | if (!main_window.renderer) { 314 | main_window.renderer = SDL_CreateRenderer(main_window.window, -1, SDL_RENDERER_SOFTWARE); 315 | if (!main_window.renderer) { 316 | fprintf(stderr, "Unable to create renderer: %s\n", SDL_GetError()); 317 | exit(EXIT_FAILURE); 318 | } 319 | } 320 | 321 | // SDL_RenderSetLogicalSize(main_window.renderer, main_window.width, main_window.height); 322 | 323 | main_window.texture = SDL_CreateTexture(main_window.renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, main_window.width, main_window.height); 324 | if (!main_window.texture) { 325 | fprintf(stderr, "Unable to create texture: %s\n", SDL_GetError()); 326 | exit(EXIT_FAILURE); 327 | } 328 | main_window.surface = SDL_CreateRGBSurface(0, main_window.width, main_window.height, 16, 0xF800, 0x7E0, 0x1F, 0); // console screen 329 | osk_screen = SDL_CreateRGBSurface(0, main_window.width, main_window.height, 16, 0xF800, 0x7E0, 0x1F, 0); // for keyboard mix 330 | 331 | // Create a temporary surface for the screen to maintain compatibility 332 | screen = SDL_CreateRGBSurface(0, main_window.width, main_window.height, 16, 0xF800, 0x7E0, 0x1F, 0); 333 | 334 | main_window.state |= WIN_VISIBLE | WIN_REDRAW; 335 | 336 | joystick = SDL_JoystickOpen(0); 337 | } 338 | 339 | void create_tty_thread() { 340 | // TODO: might need to use system threads 341 | if (!(thread = SDL_CreateThread(tty_thread, "ttythread", NULL))) { 342 | fprintf(stderr, "Unable to create thread: %s\n", SDL_GetError()); 343 | exit(EXIT_FAILURE); 344 | } 345 | } 346 | 347 | void update_render(void) { 348 | if (main_window.surface == NULL) return; 349 | 350 | memcpy(osk_screen->pixels, main_window.surface->pixels, main_window.width * main_window.height * 2); 351 | if (popup_message[0] != '\0') { 352 | SDL_Rect rect = {borderpx, main_window.height / 2 - main_window.char_height / 2 - 4, main_window.width - borderpx * 2, main_window.char_height + 6}; 353 | SDL_Color popup_box_bg = drawing_ctx.colors[8]; 354 | SDL_Color popup_box_str = drawing_ctx.colors[11]; 355 | SDL_FillRect(osk_screen, &rect, SDL_MapRGB(osk_screen->format, popup_box_bg.r, popup_box_bg.g, popup_box_bg.b)); 356 | draw_string(osk_screen, popup_message, rect.x + 2, rect.y + 4, SDL_MapRGB(osk_screen->format, popup_box_str.r, popup_box_str.g, popup_box_str.b), embedded_font_name); 357 | } 358 | draw_keyboard(osk_screen); // osk_screen(SW) = console + keyboard 359 | // Update texture with screen pixels and render 360 | SDL_UpdateTexture(main_window.texture, NULL, osk_screen->pixels, osk_screen->pitch); 361 | SDL_RenderClear(main_window.renderer); 362 | SDL_RenderCopy(main_window.renderer, main_window.texture, NULL, NULL); 363 | SDL_RenderPresent(main_window.renderer); 364 | } 365 | 366 | void die(const char *errstr, ...) { 367 | va_list ap; 368 | va_start(ap, errstr); 369 | vfprintf(stderr, errstr, ap); 370 | va_end(ap); 371 | sdl_shutdown(); 372 | } 373 | 374 | void x_resize(int col, int row) { 375 | main_window.tty_width = MAX(1, 2 * borderpx + col * main_window.char_width); 376 | main_window.tty_height = MAX(1, 2 * borderpx + row * main_window.char_height); 377 | } 378 | 379 | void init_color_map(void) { 380 | int i, r, g, b; 381 | 382 | // TODO: allow these to override the xterm ones somehow? 383 | memcpy(drawing_ctx.colors, colormap, sizeof(drawing_ctx.colors)); 384 | 385 | /* init colors [16-255] ; same colors as xterm */ 386 | for (i = 16, r = 0; r < 6; r++) { 387 | for (g = 0; g < 6; g++) { 388 | for (b = 0; b < 6; b++) { 389 | drawing_ctx.colors[i].r = r == 0 ? 0 : 0x3737 + 0x2828 * r; 390 | drawing_ctx.colors[i].g = g == 0 ? 0 : 0x3737 + 0x2828 * g; 391 | drawing_ctx.colors[i].b = b == 0 ? 0 : 0x3737 + 0x2828 * b; 392 | i++; 393 | } 394 | } 395 | } 396 | 397 | for (r = 0; r < 24; r++, i++) { 398 | b = 0x0808 + 0x0a0a * r; 399 | drawing_ctx.colors[i].r = b; 400 | drawing_ctx.colors[i].g = b; 401 | drawing_ctx.colors[i].b = b; 402 | } 403 | } 404 | 405 | void sdl_term_clear(int col1, int row1, int col2, int row2) { 406 | if (main_window.surface == NULL) return; 407 | SDL_Rect r = {borderpx + col1 * main_window.char_width, borderpx + row1 * main_window.char_height, (col2 - col1 + 1) * main_window.char_width, (row2 - row1 + 1) * main_window.char_height}; 408 | SDL_Color c = drawing_ctx.colors[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg]; 409 | SDL_FillRect(main_window.surface, &r, SDL_MapRGB(main_window.surface->format, c.r, c.g, c.b)); 410 | } 411 | 412 | /* 413 | * Absolute coordinates. 414 | */ 415 | void x_clear(int x1, int y1, int x2, int y2) { 416 | if (main_window.surface == NULL) return; 417 | SDL_Rect r = {x1, y1, x2 - x1, y2 - y1}; 418 | SDL_Color c = drawing_ctx.colors[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg]; 419 | SDL_FillRect(main_window.surface, &r, SDL_MapRGB(main_window.surface->format, c.r, c.g, c.b)); 420 | } 421 | 422 | void x_draws(char *s, Glyph base, int x, int y, int charlen, int bytelen) { 423 | int winx = borderpx + x * main_window.char_width, winy = borderpx + y * main_window.char_height, width = charlen * main_window.char_width; 424 | // TTF_Font *font = drawing_ctx.font; 425 | SDL_Color *fg = &drawing_ctx.colors[base.fg], *bg = &drawing_ctx.colors[base.bg], *temp, revfg, revbg; 426 | 427 | s[bytelen] = '\0'; 428 | 429 | if (base.mode & ATTR_BOLD) { 430 | if (BETWEEN(base.fg, 0, 7)) { 431 | /* basic system colors */ 432 | fg = &drawing_ctx.colors[base.fg + 8]; 433 | } else if (BETWEEN(base.fg, 16, 195)) { 434 | /* 256 colors */ 435 | fg = &drawing_ctx.colors[base.fg + 36]; 436 | } else if (BETWEEN(base.fg, 232, 251)) { 437 | /* greyscale */ 438 | fg = &drawing_ctx.colors[base.fg + 4]; 439 | } 440 | /* 441 | * Those ranges will not be brightened: 442 | * 8 - 15 – bright system colors 443 | * 196 - 231 – highest 256 color cube 444 | * 252 - 255 – brightest colors in greyscale 445 | */ 446 | // font = drawing_ctx.bfont; 447 | } 448 | 449 | /*if(base.mode & ATTR_ITALIC) 450 | font = drawing_ctx.ifont; 451 | if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) 452 | font = drawing_ctx.ibfont;*/ 453 | 454 | if (IS_SET(MODE_REVERSE)) { 455 | if (fg == &drawing_ctx.colors[defaultfg]) { 456 | fg = &drawing_ctx.colors[defaultbg]; 457 | } else { 458 | revfg.r = ~fg->r; 459 | revfg.g = ~fg->g; 460 | revfg.b = ~fg->b; 461 | fg = &revfg; 462 | } 463 | 464 | if (bg == &drawing_ctx.colors[defaultbg]) { 465 | bg = &drawing_ctx.colors[defaultfg]; 466 | } else { 467 | revbg.r = ~bg->r; 468 | revbg.g = ~bg->g; 469 | revbg.b = ~bg->b; 470 | bg = &revbg; 471 | } 472 | } 473 | 474 | if (base.mode & ATTR_REVERSE) temp = fg, fg = bg, bg = temp; 475 | 476 | /* Intelligent cleaning up of the borders. */ 477 | if (x == 0) { 478 | x_clear(0, (y == 0) ? 0 : winy, borderpx, winy + main_window.char_height + (y == term.row - 1) ? main_window.height : 0); 479 | } 480 | if (x + charlen >= term.col - 1) { 481 | x_clear(winx + width, (y == 0) ? 0 : winy, main_window.width, (y == term.row - 1) ? main_window.height : (winy + main_window.char_height)); 482 | } 483 | if (y == 0) x_clear(winx, 0, winx + width, borderpx); 484 | if (y == term.row - 1) x_clear(winx, winy + main_window.char_height, winx + width, main_window.height); 485 | 486 | // SDL_Surface *text_surface; 487 | SDL_Rect r = {winx, winy, width, main_window.char_height}; 488 | 489 | if (main_window.surface != NULL) { 490 | SDL_FillRect(main_window.surface, &r, SDL_MapRGB(main_window.surface->format, bg->r, bg->g, bg->b)); 491 | // TODO: find a better way to draw cursor box y + 1 492 | int ys = r.y + 1; 493 | if (is_ttf_loaded()) { 494 | // Use TTF rendering 495 | draw_string_ttf(main_window.surface, s, winx, winy, *fg, *bg); 496 | } else { 497 | // Use bitmap rendering 498 | draw_string(main_window.surface, s, winx, ys, SDL_MapRGB(main_window.surface->format, fg->r, fg->g, fg->b), embedded_font_name); 499 | } 500 | } 501 | 502 | if (base.mode & ATTR_UNDERLINE) { 503 | // r.y += TTF_FontAscent(font) + 1; 504 | r.y += main_window.char_height; 505 | r.h = 1; 506 | if (main_window.surface != NULL) SDL_FillRect(main_window.surface, &r, SDL_MapRGB(main_window.surface->format, fg->r, fg->g, fg->b)); 507 | } 508 | } 509 | 510 | void x_draw_cursor(void) { 511 | static int oldx = 0, oldy = 0; 512 | int sl; 513 | Glyph g = {{' '}, ATTR_NULL, defaultbg, defaultcs, 0}; 514 | 515 | LIMIT(oldx, 0, term.col - 1); 516 | LIMIT(oldy, 0, term.row - 1); 517 | 518 | if (term.line[term.c.y][term.c.x].state & GLYPH_SET) memcpy(g.c, term.line[term.c.y][term.c.x].c, UTF_SIZ); 519 | 520 | /* remove the old cursor */ 521 | if (term.line[oldy][oldx].state & GLYPH_SET) { 522 | sl = utf8_size(term.line[oldy][oldx].c); 523 | x_draws(term.line[oldy][oldx].c, term.line[oldy][oldx], oldx, oldy, 1, sl); 524 | } else { 525 | sdl_term_clear(oldx, oldy, oldx, oldy); 526 | } 527 | 528 | /* draw the new one */ 529 | if (!(term.c.state & CURSOR_HIDE)) { 530 | if (!(main_window.state & WIN_FOCUSED)) g.bg = defaultucs; 531 | 532 | if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE, g.fg = defaultcs, g.bg = defaultfg; 533 | 534 | sl = utf8_size(g.c); 535 | x_draws(g.c, g, term.c.x, term.c.y, 1, sl); 536 | oldx = term.c.x, oldy = term.c.y; 537 | } 538 | } 539 | 540 | void redraw(void) { 541 | struct timespec tv = {0, REDRAW_TIMEOUT * 1000}; 542 | 543 | t_full_dirt(); 544 | draw(); 545 | nanosleep(&tv, NULL); 546 | } 547 | 548 | void draw(void) { 549 | draw_region(0, 0, term.col, term.row); 550 | update_render(); 551 | } 552 | 553 | void draw_region(int x1, int y1, int x2, int y2) { 554 | int ic, ib, x, y, ox, sl; 555 | Glyph base, new; 556 | char buf[DRAW_BUF_SIZ]; 557 | 558 | if (!(main_window.state & WIN_VISIBLE)) return; 559 | 560 | for (y = y1; y < y2; y++) { 561 | if (!term.dirty[y]) continue; 562 | 563 | sdl_term_clear(0, y, term.col, y); 564 | term.dirty[y] = 0; 565 | base = term.line[y][0]; 566 | ic = ib = ox = 0; 567 | for (x = x1; x < x2; x++) { 568 | new = term.line[y][x]; 569 | if (ib > 0 && (!(new.state & GLYPH_SET) || ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ - UTF_SIZ)) { 570 | x_draws(buf, base, ox, y, ic, ib); 571 | ic = ib = 0; 572 | } 573 | if (new.state & GLYPH_SET) { 574 | if (ib == 0) { 575 | ox = x; 576 | base = new; 577 | } 578 | sl = utf8_size(new.c); 579 | memcpy(buf + ib, new.c, sl); 580 | ib += sl; 581 | ++ic; 582 | } 583 | } 584 | if (ib > 0) x_draws(buf, base, ox, y, ic, ib); 585 | } 586 | x_draw_cursor(); 587 | } 588 | 589 | char *k_map(SDL_Keycode k, Uint16 state) { 590 | int i; 591 | SDL_Keymod mask; 592 | 593 | for (i = 0; i < LEN(non_printing_keyboard_keys); i++) { 594 | mask = non_printing_keyboard_keys[i].mask; 595 | 596 | if (non_printing_keyboard_keys[i].k == k && ((state & mask) == mask || (mask == 0 && !state))) { 597 | return (char *)non_printing_keyboard_keys[i].s; 598 | } 599 | } 600 | return NULL; 601 | } 602 | 603 | void print_non_printing_key_for_debug(char *non_printing_key, SDL_KeyboardEvent *e) { 604 | char escaped_seq[16] = {0}; 605 | int idx = 0; 606 | for (int i = 0; non_printing_key[i] != '\0'; i++) { 607 | unsigned char ch = non_printing_key[i]; 608 | if (ch == 27) { // '\033' 609 | strcpy(&escaped_seq[idx], "\\033"); 610 | idx += 4; 611 | } else if (ch >= 32 && ch <= 126) { 612 | escaped_seq[idx++] = ch; 613 | } else { 614 | sprintf(&escaped_seq[idx], "\\x%02X", ch); 615 | idx += 4; 616 | } 617 | } 618 | escaped_seq[idx] = '\0'; 619 | printf("Custom key mapped: %s - ksym=%d, scancode=%d, mod=%d\n", escaped_seq, e->keysym.sym, e->keysym.scancode, e->keysym.mod); 620 | } 621 | 622 | void k_press(SDL_Event *ev) { 623 | SDL_KeyboardEvent *e = &ev->key; 624 | char *non_printing_key; 625 | int meta, shift, ctrl, synth; 626 | SDL_Keycode ksym = e->keysym.sym; 627 | 628 | if (IS_SET(MODE_KBDLOCK)) return; 629 | 630 | meta = e->keysym.mod & KMOD_ALT; 631 | shift = e->keysym.mod & KMOD_SHIFT; 632 | ctrl = e->keysym.mod & KMOD_CTRL; 633 | synth = e->keysym.mod & KMOD_SYNTHETIC; 634 | 635 | // printf("kpress: keysym=%d scancode=%d mod=%d\n", ksym, e->keysym.scancode, e->keysym.mod); 636 | 637 | if ((non_printing_key = k_map(ksym, e->keysym.mod))) { /* 1. non printing keys from vt100.h */ 638 | // print_non_printing_key_for_debug(non_printing_key, e); 639 | tty_write(non_printing_key, strlen(non_printing_key)); 640 | } else if (ctrl && !meta && !shift) { /* 2. handle ctrl key */ 641 | switch (ksym) { 642 | case SDLK_a: 643 | tty_write("\001", 1); 644 | break; 645 | case SDLK_b: 646 | tty_write("\002", 1); 647 | break; 648 | case SDLK_c: 649 | tty_write("\003", 1); 650 | break; 651 | case SDLK_d: 652 | tty_write("\004", 1); 653 | break; 654 | case SDLK_e: 655 | tty_write("\005", 1); 656 | break; 657 | case SDLK_f: 658 | tty_write("\006", 1); 659 | break; 660 | case SDLK_g: 661 | tty_write("\007", 1); 662 | break; 663 | case SDLK_h: 664 | tty_write("\010", 1); 665 | break; 666 | case SDLK_i: 667 | tty_write("\011", 1); 668 | break; 669 | case SDLK_j: 670 | tty_write("\012", 1); 671 | break; 672 | case SDLK_k: 673 | tty_write("\013", 1); 674 | break; 675 | case SDLK_l: 676 | tty_write("\014", 1); 677 | break; 678 | case SDLK_m: 679 | tty_write("\015", 1); 680 | break; 681 | case SDLK_n: 682 | tty_write("\016", 1); 683 | break; 684 | case SDLK_o: 685 | tty_write("\017", 1); 686 | break; 687 | case SDLK_p: 688 | tty_write("\020", 1); 689 | break; 690 | case SDLK_q: 691 | tty_write("\021", 1); 692 | break; 693 | case SDLK_r: 694 | tty_write("\022", 1); 695 | break; 696 | case SDLK_s: 697 | tty_write("\023", 1); 698 | break; 699 | case SDLK_t: 700 | tty_write("\024", 1); 701 | break; 702 | case SDLK_u: 703 | tty_write("\025", 1); 704 | break; 705 | case SDLK_v: 706 | tty_write("\026", 1); 707 | break; 708 | case SDLK_w: 709 | tty_write("\027", 1); 710 | break; 711 | case SDLK_x: 712 | tty_write("\030", 1); 713 | break; 714 | case SDLK_y: 715 | tty_write("\031", 1); 716 | break; 717 | case SDLK_z: 718 | tty_write("\032", 1); 719 | break; 720 | default: 721 | break; 722 | } 723 | } else { 724 | // special volumeup/down/powerkey handling 725 | if (e->keysym.scancode == 128) { 726 | printf("Volume Up key pressed\n"); 727 | } else if (e->keysym.scancode == 129) { 728 | printf("Volume Down key pressed\n"); 729 | } else if (e->keysym.scancode == 102) { 730 | printf("Power key pressed\n"); 731 | } 732 | 733 | // keys pressed by on-screen keyboard 734 | if (synth) { 735 | // printf("Synthetic key event: %s\n", SDL_GetKeyName(e->keysym.sym)); 736 | if (e->keysym.sym <= 128) { 737 | char ch = (char)e->keysym.sym; 738 | if (meta) { 739 | tty_write("\033", 1); 740 | } 741 | tty_write(&ch, 1); 742 | } 743 | } 744 | } 745 | /* For printable keys, we handle text input separately with SDL_TEXTINPUT events */ 746 | } 747 | 748 | void text_input(SDL_Event *ev) { 749 | SDL_TextInputEvent *e = &ev->text; 750 | tty_write(e->text, strlen(e->text)); 751 | } 752 | 753 | int tty_thread(void *unused) { 754 | int i; 755 | fd_set rfd; 756 | struct timeval drawtimeout, *tv = NULL; 757 | SDL_Event event; 758 | (void)unused; 759 | 760 | event.type = SDL_USEREVENT; 761 | event.user.code = 0; 762 | event.user.data1 = NULL; 763 | event.user.data2 = NULL; 764 | 765 | for (i = 0;; i++) { 766 | if (thread_should_exit) break; 767 | FD_ZERO(&rfd); 768 | FD_SET(cmdfd, &rfd); 769 | if (select(cmdfd + 1, &rfd, NULL, NULL, tv) < 0) { 770 | if (errno == EINTR) continue; 771 | die("select failed: %s\n", strerror(errno)); 772 | } 773 | 774 | /* 775 | * Stop after a certain number of reads so the user does not 776 | * feel like the system is stuttering. 777 | */ 778 | if (i < 1000 && FD_ISSET(cmdfd, &rfd)) { 779 | tty_read(); 780 | 781 | /* 782 | * Just wait a bit so it isn't disturbing the 783 | * user and the system is able to write something. 784 | */ 785 | drawtimeout.tv_sec = 0; 786 | drawtimeout.tv_usec = 5; 787 | tv = &drawtimeout; 788 | continue; 789 | } 790 | i = 0; 791 | tv = NULL; 792 | 793 | SDL_PushEvent(&event); 794 | } 795 | 796 | return 0; 797 | } 798 | 799 | static Uint32 clear_popup_timer(Uint32 interval, void *param) { 800 | popup_message[0] = '\0'; 801 | return 0; // one-shot timer 802 | } 803 | 804 | void take_screenshot() { 805 | char filename[64]; 806 | time_t now = time(NULL); 807 | struct tm *t = localtime(&now); 808 | strftime(filename, sizeof(filename), "st-%y%m%d_%H%M%S.bmp", t); 809 | // get home directory 810 | const char *home_dir = getenv("HOME"); 811 | if (home_dir != NULL) { 812 | char filepath[256]; 813 | snprintf(filepath, sizeof(filepath), "%s/%s", home_dir, filename); 814 | strcpy(filename, filepath); 815 | } 816 | 817 | if (main_window.surface) { 818 | if (SDL_SaveBMP(main_window.surface, filename) == 0) { 819 | sprintf(popup_message, "Screenshot saved to %s", filename); 820 | } else { 821 | sprintf(popup_message, "Failed to save screenshot: %s", SDL_GetError()); 822 | } 823 | } 824 | 825 | // Clear the popup message after 3 seconds 826 | SDL_AddTimer(3000, clear_popup_timer, NULL); 827 | } 828 | 829 | void main_loop(void) { 830 | SDL_Event ev; 831 | int running = 1; 832 | int button_up_held = 0, button_down_held = 0, button_left_held = 0, button_right_held = 0; 833 | Uint32 last_button_held_time = 0; 834 | while (running) { 835 | while (SDL_PollEvent(&ev)) 836 | // while (SDL_WaitEvent(&ev)) 837 | { 838 | if (ev.type == SDL_QUIT) { 839 | running = 0; 840 | break; 841 | } 842 | 843 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { 844 | // printf("Keyboard event received - key: %d (%s), state: %s\n", ev.key.keysym.sym, SDL_GetKeyName(ev.key.keysym.sym), (ev.type == SDL_KEYDOWN) ? "DOWN" : "UP"); 845 | int keyboard_event = handle_keyboard_event(&ev); 846 | if (keyboard_event == 1) { 847 | // printf("OSK handled the event.\n"); 848 | } else { 849 | // printf("OSK passing event to default handler.\n"); 850 | if (event_handler[ev.type]) (event_handler[ev.type])(&ev); 851 | } 852 | 853 | int held = (ev.type == SDL_KEYDOWN); 854 | switch (ev.key.keysym.sym) { 855 | case KEY_LEFT: 856 | button_left_held = held; 857 | break; 858 | case KEY_RIGHT: 859 | button_right_held = held; 860 | break; 861 | case KEY_UP: 862 | button_up_held = held; 863 | break; 864 | case KEY_DOWN: 865 | button_down_held = held; 866 | break; 867 | default: 868 | break; 869 | } 870 | } else if (ev.type == SDL_JOYBUTTONDOWN || ev.type == SDL_JOYBUTTONUP) { 871 | // printf("Joystick event received type: %s - %d\n", (ev.jbutton.state == SDL_PRESSED) ? "down" : "up", ev.jbutton.button); 872 | SDL_Event sdl_event = {.key = {.type = (ev.jbutton.state == SDL_PRESSED) ? SDL_KEYDOWN : SDL_KEYUP, 873 | .state = (ev.jbutton.state == SDL_PRESSED) ? SDL_PRESSED : SDL_RELEASED, 874 | .keysym = { 875 | .scancode = -ev.jbutton.button, 876 | .sym = -ev.jbutton.button, 877 | .mod = 0, 878 | }}}; 879 | 880 | SDL_PushEvent(&sdl_event); 881 | } else { 882 | if (event_handler[ev.type]) (event_handler[ev.type])(&ev); 883 | } 884 | 885 | switch (ev.type) { 886 | case SDL_USEREVENT: 887 | if (ev.user.code == 0) { // redraw terminal 888 | draw(); 889 | } else if (ev.user.code == 1) { // Take a screenshot 890 | take_screenshot(); 891 | } 892 | } 893 | } 894 | 895 | Uint32 now = SDL_GetTicks(); 896 | int key = 0; 897 | if (button_down_held) 898 | key = KEY_DOWN; 899 | else if (button_up_held) 900 | key = KEY_UP; 901 | else if (button_left_held) 902 | key = KEY_LEFT; 903 | else if (button_right_held) 904 | key = KEY_RIGHT; 905 | 906 | if (key && now - last_button_held_time > BUTTON_HELD_DELAY) { 907 | handle_narrow_keys_held(key); 908 | last_button_held_time = now; 909 | } 910 | 911 | update_render(); // redraw the screen 912 | SDL_Delay(33); // ~30 FPS 913 | } 914 | 915 | sdl_shutdown(); 916 | } 917 | 918 | int main(int argc, char *argv[]) { 919 | setenv("SDL_NOMOUSE", "1", 1); 920 | int is_scale_set_by_user = 0; 921 | 922 | for (int i = 1; i < argc; i++) { 923 | // Handle multi-character options first 924 | if (strcmp(argv[i], "-scale") == 0) { 925 | if (++i < argc) { 926 | opt_scale = atof(argv[i]); 927 | if (opt_scale <= 0) { 928 | fprintf(stderr, "Invalid scale: %s (must be positive)\n", argv[i]); 929 | opt_scale = 2.0; 930 | } 931 | is_scale_set_by_user = 1; 932 | } else { 933 | fprintf(stderr, "Missing argument for -scale\n"); 934 | die(USAGE); 935 | } 936 | continue; 937 | } 938 | if (strcmp(argv[i], "-font") == 0) { 939 | if (++i < argc) { 940 | opt_font = argv[i]; 941 | if (!is_scale_set_by_user) { 942 | opt_scale = 1.0; // if custom font is set, default scale to 1.0 943 | } 944 | 945 | if (strcmp(opt_font, "1") == 0) { 946 | opt_font = NULL; 947 | embedded_font_name = 1; 948 | opt_scale = 2.0; 949 | } else if (strcmp(opt_font, "2") == 0) { 950 | opt_font = NULL; 951 | embedded_font_name = 2; 952 | opt_scale = 2.0; 953 | } else if (strcmp(opt_font, "3") == 0) { 954 | opt_font = NULL; 955 | embedded_font_name = 3; 956 | opt_scale = 2.0; 957 | } else if (strcmp(opt_font, "4") == 0) { 958 | opt_font = NULL; 959 | embedded_font_name = 4; 960 | opt_scale = 1.0; 961 | } else if (strcmp(opt_font, "5") == 0) { 962 | opt_font = NULL; 963 | embedded_font_name = 5; 964 | opt_scale = 1.0; 965 | } 966 | } else { 967 | fprintf(stderr, "Missing argument for -font\n"); 968 | die(USAGE); 969 | } 970 | continue; 971 | } 972 | if (strcmp(argv[i], "-fontsize") == 0) { 973 | if (++i < argc) { 974 | opt_fontsize = atoi(argv[i]); 975 | if (opt_fontsize <= 0) { 976 | fprintf(stderr, "Invalid fontsize: %s (must be positive)\n", argv[i]); 977 | opt_fontsize = 0; 978 | } 979 | } else { 980 | fprintf(stderr, "Missing argument for -fontsize\n"); 981 | die(USAGE); 982 | } 983 | continue; 984 | } 985 | if (strcmp(argv[i], "-fontshade") == 0) { 986 | if (++i < argc) { 987 | opt_fontshade = atoi(argv[i]); 988 | } else { 989 | fprintf(stderr, "Missing argument for -fontshade\n"); 990 | die(USAGE); 991 | } 992 | continue; 993 | } 994 | if (strcmp(argv[i], "-useEmbeddedFontForKeyboard") == 0) { 995 | if (++i < argc) { 996 | opt_use_embedded_font_for_keyboard = atoi(argv[i]); 997 | } else { 998 | fprintf(stderr, "Missing argument for -useEmbeddedFontForKeyboard\n"); 999 | die(USAGE); 1000 | } 1001 | continue; 1002 | } 1003 | 1004 | switch (argv[i][0] != '-' || argv[i][2] ? -1 : argv[i][1]) { 1005 | case 'r': // run commands from arguments, must be at the end of argv 1006 | if (++i < argc) { 1007 | opt_cmd = &argv[i]; 1008 | opt_cmd_size = argc - i; 1009 | for (int j = 0; j < opt_cmd_size; j++) { 1010 | printf("Command to execute: %s\n", opt_cmd[j]); 1011 | } 1012 | show_help = 0; 1013 | } 1014 | break; 1015 | case 'o': // save output commands to file 1016 | if (++i < argc) opt_io = argv[i]; 1017 | break; 1018 | case 'q': // quiet mode 1019 | active = show_help = 0; 1020 | break; 1021 | case 'h': // print help 1022 | default: 1023 | die(USAGE); 1024 | } 1025 | } 1026 | 1027 | if (atexit(sdl_shutdown)) { 1028 | fprintf(stderr, "Unable to register SDL_Quit atexit\n"); 1029 | } 1030 | 1031 | sdl_init(); 1032 | t_new((main_window.width - borderpx) / main_window.char_width, (main_window.height - borderpx) / main_window.char_height); 1033 | tty_new(); 1034 | create_tty_thread(); 1035 | scale_to_size((int)(main_window.width / opt_scale), (int)(main_window.height / opt_scale)); 1036 | init_keyboard(embedded_font_name, opt_use_embedded_font_for_keyboard); 1037 | main_loop(); 1038 | return 0; 1039 | } 1040 | -------------------------------------------------------------------------------- /embedded-font-editor/fonts.js: -------------------------------------------------------------------------------- 1 | // Font configurations 2 | const FONTS = [ 3 | { 4 | name: 'Embedded Font 1', 5 | width: 6, 6 | height: 6, 7 | bytesPerChar: 6, 8 | data: null 9 | }, 10 | { 11 | name: 'Embedded Font 2', 12 | width: 8, 13 | height: 8, 14 | bytesPerChar: 8, 15 | data: null 16 | }, 17 | { 18 | name: 'Embedded Font 3', 19 | width: 3, 20 | height: 5, 21 | bytesPerChar: 5, 22 | data: null 23 | }, 24 | { 25 | name: 'Embedded Font 4', 26 | width: 8, 27 | height: 10, 28 | bytesPerChar: 10, 29 | data: null 30 | }, 31 | { 32 | name: 'Embedded Font 5', 33 | width: 8, 34 | height: 10, 35 | bytesPerChar: 10, 36 | data: null 37 | } 38 | ]; 39 | 40 | const embedded_font1 = [ 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: (Control) 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1: (Control) 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2: (Control) 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3: (Control) 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4: (Control) 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 5: (Control) 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6: (Control) 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7: (Control) 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8: (Control) 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9: (Control) 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10: (Control) 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 11: (Control) 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12: (Control) 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 13: (Control) 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 14: (Control) 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 15: (Control) 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 16: (Control) 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 17: (Control) 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 18: (Control) 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 19: (Control) 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20: (Control) 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 21: (Control) 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 22: (Control) 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 23: (Control) 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 24: (Control) 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 25: (Control) 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 26: (Control) 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 27: (Control) 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 28: (Control) 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 29: (Control) 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 30: (Control) 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 31: (Control) 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32: ' ' (space) 74 | 0x30, 0x30, 0x30, 0x00, 0x30, 0x00, // 33: '!' 75 | 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, // 34: '"' 76 | 0x50, 0xf8, 0x50, 0xf8, 0x50, 0x00, // 35: '#' 77 | 0x78, 0xa0, 0x70, 0x28, 0xf0, 0x00, // 36: '$' 78 | 0x88, 0x10, 0x20, 0x40, 0x88, 0x00, // 37: '%' 79 | 0x40, 0xa0, 0x68, 0x90, 0x68, 0x00, // 38: '&' 80 | 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, // 39: ''' 81 | 0x20, 0x40, 0x40, 0x40, 0x20, 0x00, // 40: '(' 82 | 0x40, 0x20, 0x20, 0x20, 0x40, 0x00, // 41: ')' 83 | 0x20, 0xa8, 0x70, 0xa8, 0x20, 0x00, // 42: '*' 84 | 0x00, 0x20, 0x70, 0x20, 0x00, 0x00, // 43: '+' 85 | 0x00, 0x00, 0x00, 0x60, 0x20, 0x40, // 44: ',' 86 | 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, // 45: '-' 87 | 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, // 46: '.' 88 | 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, // 47: '/' 89 | 0x70, 0xc8, 0xc8, 0xc8, 0x70, 0x00, // 48: '0' 90 | 0x30, 0x70, 0x30, 0x30, 0x78, 0x00, // 49: '1' 91 | 0xf0, 0x18, 0x70, 0xc0, 0xf8, 0x00, // 50: '2' 92 | 0xf8, 0x18, 0x30, 0x98, 0x70, 0x00, // 51: '3' 93 | 0x30, 0x70, 0xd0, 0xf8, 0x10, 0x00, // 52: '4' 94 | 0xf8, 0xc0, 0xf0, 0x18, 0xf0, 0x00, // 53: '5' 95 | 0x70, 0xc0, 0xf0, 0xc8, 0x70, 0x00, // 54: '6' 96 | 0xf8, 0x18, 0x30, 0x60, 0xc0, 0x00, // 55: '7' 97 | 0x70, 0xc8, 0x70, 0xc8, 0x70, 0x00, // 56: '8' 98 | 0x70, 0xc8, 0x78, 0x08, 0x70, 0x00, // 57: '9' 99 | 0x60, 0x60, 0x00, 0x60, 0x60, 0x00, // 58: ':' 100 | 0x60, 0x60, 0x00, 0x60, 0x20, 0x40, // 59: ';' 101 | 0x10, 0x20, 0x40, 0x20, 0x10, 0x00, // 60: '<' 102 | 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, // 61: '=' 103 | 0x40, 0x20, 0x10, 0x20, 0x40, 0x00, // 62: '>' 104 | 0x78, 0x18, 0x30, 0x00, 0x30, 0x00, // 63: '?' 105 | 0x70, 0xa8, 0xb8, 0x80, 0x70, 0x00, // 64: '@' 106 | 0x70, 0xc8, 0xc8, 0xf8, 0xc8, 0x00, // 65: 'A' 107 | 0xf0, 0xc8, 0xf0, 0xc8, 0xf0, 0x00, // 66: 'B' 108 | 0x70, 0xc8, 0xc0, 0xc8, 0x70, 0x00, // 67: 'C' 109 | 0xf0, 0xc8, 0xc8, 0xc8, 0xf0, 0x00, // 68: 'D' 110 | 0xf8, 0xc0, 0xf0, 0xc0, 0xf8, 0x00, // 69: 'E' 111 | 0xf8, 0xc0, 0xf0, 0xc0, 0xc0, 0x00, // 70: 'F' 112 | 0x78, 0xc0, 0xd8, 0xc8, 0x78, 0x00, // 71: 'G' 113 | 0xc8, 0xc8, 0xf8, 0xc8, 0xc8, 0x00, // 72: 'H' 114 | 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, // 73: 'I' 115 | 0xf8, 0x18, 0x18, 0xd8, 0x70, 0x00, // 74: 'J' 116 | 0xc8, 0xd0, 0xe0, 0xd0, 0xc8, 0x00, // 75: 'K' 117 | 0xc0, 0xc0, 0xc0, 0xc0, 0xf8, 0x00, // 76: 'L' 118 | 0xd8, 0xf8, 0xf8, 0xa8, 0x88, 0x00, // 77: 'M' 119 | 0xc8, 0xe8, 0xf8, 0xd8, 0xc8, 0x00, // 78: 'N' 120 | 0x70, 0xc8, 0xc8, 0xc8, 0x70, 0x00, // 79: 'O' 121 | 0xf0, 0xc8, 0xc8, 0xf0, 0xc0, 0x00, // 80: 'P' 122 | 0x70, 0xc8, 0xc8, 0xc8, 0x70, 0x08, // 81: 'Q' 123 | 0xf0, 0xc8, 0xc8, 0xf0, 0xc8, 0x00, // 82: 'R' 124 | 0x78, 0xe0, 0x70, 0x38, 0xf0, 0x00, // 83: 'S' 125 | 0x78, 0x30, 0x30, 0x30, 0x30, 0x00, // 84: 'T' 126 | 0xc8, 0xc8, 0xc8, 0xc8, 0x70, 0x00, // 85: 'U' 127 | 0xc8, 0xc8, 0xc8, 0x70, 0x20, 0x00, // 86: 'V' 128 | 0x88, 0xa8, 0xf8, 0xf8, 0xd8, 0x00, // 87: 'W' 129 | 0xc8, 0xc8, 0x70, 0xc8, 0xc8, 0x00, // 88: 'X' 130 | 0x68, 0x68, 0x78, 0x30, 0x30, 0x00, // 89: 'Y' 131 | 0xf8, 0x30, 0x60, 0xc0, 0xf8, 0x00, // 90: 'Z' 132 | 0x60, 0x40, 0x40, 0x40, 0x60, 0x00, // 91: '[' 133 | 0x80, 0x40, 0x20, 0x10, 0x08, 0x00, // 92: '\' 134 | 0x60, 0x20, 0x20, 0x20, 0x60, 0x00, // 93: ']' 135 | 0x20, 0x50, 0x88, 0x00, 0x00, 0x00, // 94: '^' 136 | 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, // 95: '_' 137 | 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, // 96: '`' 138 | 0x00, 0x78, 0x98, 0x98, 0x78, 0x00, // 97: 'a' 139 | 0xc0, 0xf0, 0xc8, 0xc8, 0xf0, 0x00, // 98: 'b' 140 | 0x00, 0x78, 0xe0, 0xe0, 0x78, 0x00, // 99: 'c' 141 | 0x18, 0x78, 0x98, 0x98, 0x78, 0x00, // 100: 'd' 142 | 0x00, 0x70, 0xd8, 0xe0, 0x70, 0x00, // 101: 'e' 143 | 0x38, 0x60, 0xf8, 0x60, 0x60, 0x00, // 102: 'f' 144 | 0x00, 0x70, 0x98, 0xf8, 0x18, 0x70, // 103: 'g' 145 | 0xc0, 0xf0, 0xc8, 0xc8, 0xc8, 0x00, // 104: 'h' 146 | 0x30, 0x00, 0x70, 0x30, 0x78, 0x00, // 105: 'i' 147 | 0x18, 0x00, 0x18, 0x18, 0x98, 0x70, // 106: 'j' 148 | 0xc0, 0xc8, 0xf0, 0xc8, 0xc8, 0x00, // 107: 'k' 149 | 0x60, 0x60, 0x60, 0x60, 0x38, 0x00, // 108: 'l' 150 | 0x00, 0xd0, 0xf8, 0xa8, 0xa8, 0x00, // 109: 'm' 151 | 0x00, 0xf0, 0xc8, 0xc8, 0xc8, 0x00, // 110: 'n' 152 | 0x00, 0x70, 0xc8, 0xc8, 0x70, 0x00, // 111: 'o' 153 | 0x00, 0xf0, 0xc8, 0xc8, 0xf0, 0xc0, // 112: 'p' 154 | 0x00, 0x78, 0x98, 0x98, 0x78, 0x18, // 113: 'q' 155 | 0x00, 0xf0, 0xc8, 0xc0, 0xc0, 0x00, // 114: 'r' 156 | 0x00, 0x78, 0xe0, 0x38, 0xf0, 0x00, // 115: 's' 157 | 0x60, 0xf8, 0x60, 0x60, 0x38, 0x00, // 116: 't' 158 | 0x00, 0x98, 0x98, 0x98, 0x78, 0x00, // 117: 'u' 159 | 0x00, 0xc8, 0xc8, 0xd0, 0xe0, 0x00, // 118: 'v' 160 | 0x00, 0x88, 0xa8, 0xf8, 0xd8, 0x00, // 119: 'w' 161 | 0x00, 0xd8, 0x70, 0x70, 0xd8, 0x00, // 120: 'x' 162 | 0x00, 0x98, 0x98, 0x78, 0x18, 0x70, // 121: 'y' 163 | 0x00, 0xf8, 0x30, 0x60, 0xf8, 0x00, // 122: 'z' 164 | 0x30, 0x20, 0x60, 0x20, 0x30, 0x00, // 123: '{' 165 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, // 124: '|' 166 | 0x60, 0x20, 0x30, 0x20, 0x60, 0x00, // 125: '}' 167 | 0x00, 0x28, 0x50, 0x00, 0x00, 0x00, // 126: '~' 168 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 127: DEL 169 | ]; 170 | 171 | const embedded_font2 = [ 172 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: (Control) 173 | 0x3c, 0x42, 0xa5, 0x81, 0xa5, 0x99, 0x42, 0x3c, // 1: (Control) 174 | 0x3c, 0x7e, 0xdb, 0xff, 0xff, 0xdb, 0x66, 0x3c, // 2: (Control) 175 | 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, // 3: (Control) 176 | 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00, // 4: (Control) 177 | 0x10, 0x38, 0x54, 0xfe, 0x54, 0x10, 0x38, 0x00, // 5: (Control) 178 | 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0x10, 0x38, 0x00, // 6: (Control) 179 | 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, // 7: (Control) 180 | 0xff, 0xff, 0xff, 0xe7, 0xe7, 0xff, 0xff, 0xff, // 8: (Control) 181 | 0x38, 0x44, 0x82, 0x82, 0x82, 0x44, 0x38, 0x00, // 9: (Control) 182 | 0xc7, 0xbb, 0x7d, 0x7d, 0x7d, 0xbb, 0xc7, 0xff, // 10: (Control) 183 | 0x0f, 0x03, 0x05, 0x79, 0x88, 0x88, 0x88, 0x70, // 11: (Control) 184 | 0x38, 0x44, 0x44, 0x44, 0x38, 0x10, 0x7c, 0x10, // 12: (Control) 185 | 0x30, 0x28, 0x24, 0x24, 0x28, 0x20, 0xe0, 0xc0, // 13: (Control) 186 | 0x3c, 0x24, 0x3c, 0x24, 0x24, 0xe4, 0xdc, 0x18, // 14: (Control) 187 | 0x10, 0x54, 0x38, 0xee, 0x38, 0x54, 0x10, 0x00, // 15: (Control) 188 | 0x80, 0xc0, 0xe0, 0xf0, 0xe0, 0xc0, 0x80, 0x00, // 16: (Control) 189 | 0x01, 0x03, 0x07, 0x0f, 0x07, 0x03, 0x01, 0x00, // 17: (Control) 190 | 0x10, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x10, 0x10, // 18: (Control) 191 | 0xfc, 0x48, 0x48, 0x48, 0xe8, 0x08, 0x50, 0x20, // 19: (Control) 192 | 0x7c, 0xa8, 0xa8, 0x68, 0x28, 0x28, 0x28, 0x00, // 20: (Control) 193 | 0x38, 0x40, 0x30, 0x48, 0x48, 0x30, 0x08, 0x70, // 21: (Control) 194 | 0x00, 0x00, 0x00, 0x3c, 0x3c, 0x00, 0x00, 0x00, // 22: (Control) 195 | 0x20, 0x20, 0x70, 0x20, 0x70, 0x20, 0x20, 0x00, // 23: (Control) 196 | 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 24: (Control) 197 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, // 25: (Control) 198 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 26: (Control) 199 | 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, // 27: (Control) 200 | 0x00, 0x10, 0x10, 0xff, 0x10, 0x10, 0x00, 0x00, // 28: (Control) 201 | 0x00, 0x20, 0x50, 0x88, 0x50, 0x20, 0x00, 0x00, // 29: (Control) 202 | 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, // 30: (Control) 203 | 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 31: (Control) 204 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32: ' ' (space) 205 | 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x20, 0x00, // 33: '!' 206 | 0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // 34: '"' 207 | 0x50, 0x50, 0xf8, 0x50, 0xf8, 0x50, 0x50, 0x00, // 35: '#' 208 | 0x20, 0x78, 0xa0, 0x70, 0x28, 0xf0, 0x20, 0x00, // 36: '$' 209 | 0xc0, 0xc8, 0x10, 0x20, 0x40, 0x98, 0x18, 0x00, // 37: '%' 210 | 0x40, 0xa0, 0x40, 0xa8, 0x90, 0x98, 0x60, 0x00, // 38: '&' 211 | 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 39: ''' 212 | 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00, // 40: '(' 213 | 0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, // 41: ')' 214 | 0x20, 0xa8, 0x70, 0x20, 0x70, 0xa8, 0x20, 0x00, // 42: '*' 215 | 0x00, 0x20, 0x20, 0xf8, 0x20, 0x20, 0x00, 0x00, // 43: '+' 216 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x40, // 44: ',' 217 | 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, // 45: '-' 218 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, // 46: '.' 219 | 0x00, 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, // 47: '/' 220 | 0x70, 0x88, 0x98, 0xa8, 0xc8, 0x88, 0x70, 0x00, // 48: '0' 221 | 0x20, 0x60, 0xa0, 0x20, 0x20, 0x20, 0xf8, 0x00, // 49: '1' 222 | 0x70, 0x88, 0x08, 0x10, 0x60, 0x80, 0xf8, 0x00, // 50: '2' 223 | 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70, 0x00, // 51: '3' 224 | 0x10, 0x30, 0x50, 0x90, 0xf8, 0x10, 0x10, 0x00, // 52: '4' 225 | 0xf8, 0x80, 0xe0, 0x10, 0x08, 0x10, 0xe0, 0x00, // 53: '5' 226 | 0x30, 0x40, 0x80, 0xf0, 0x88, 0x88, 0x70, 0x00, // 54: '6' 227 | 0xf8, 0x88, 0x10, 0x20, 0x20, 0x20, 0x20, 0x00, // 55: '7' 228 | 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00, // 56: '8' 229 | 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60, 0x00, // 57: '9' 230 | 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, // 58: ':' 231 | 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x20, 0x40, // 59: ';' 232 | 0x00, 0x10, 0x20, 0x40, 0x20, 0x10, 0x00, 0x00, // 60: '<' 233 | 0x00, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0x00, 0x00, // 61: '=' 234 | 0x00, 0x40, 0x20, 0x10, 0x20, 0x40, 0x00, 0x00, // 62: '>' 235 | 0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20, 0x00, // 63: '?' 236 | 0x70, 0x88, 0x08, 0x68, 0xa8, 0xa8, 0x70, 0x00, // 64: '@' 237 | 0x20, 0x50, 0x88, 0x88, 0xf8, 0x88, 0x88, 0x00, // 65: 'A' 238 | 0xf0, 0x48, 0x48, 0x70, 0x48, 0x48, 0xf0, 0x00, // 66: 'B' 239 | 0x30, 0x48, 0x80, 0x80, 0x80, 0x48, 0x30, 0x00, // 67: 'C' 240 | 0xe0, 0x50, 0x48, 0x48, 0x48, 0x50, 0xe0, 0x00, // 68: 'D' 241 | 0xf8, 0x80, 0x80, 0xf0, 0x80, 0x80, 0xf8, 0x00, // 69: 'E' 242 | 0xf8, 0x80, 0x80, 0xf0, 0x80, 0x80, 0x80, 0x00, // 70: 'F' 243 | 0x70, 0x88, 0x80, 0xb8, 0x88, 0x88, 0x70, 0x00, // 71: 'G' 244 | 0x88, 0x88, 0x88, 0xf8, 0x88, 0x88, 0x88, 0x00, // 72: 'H' 245 | 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, // 73: 'I' 246 | 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60, 0x00, // 74: 'J' 247 | 0x88, 0x90, 0xa0, 0xc0, 0xa0, 0x90, 0x88, 0x00, // 75: 'K' 248 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xf8, 0x00, // 76: 'L' 249 | 0x88, 0xd8, 0xa8, 0xa8, 0x88, 0x88, 0x88, 0x00, // 77: 'M' 250 | 0x88, 0xc8, 0xc8, 0xa8, 0x98, 0x98, 0x88, 0x00, // 78: 'N' 251 | 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, // 79: 'O' 252 | 0xf0, 0x88, 0x88, 0xf0, 0x80, 0x80, 0x80, 0x00, // 80: 'P' 253 | 0x70, 0x88, 0x88, 0x88, 0xa8, 0x90, 0x68, 0x00, // 81: 'Q' 254 | 0xf0, 0x88, 0x88, 0xf0, 0xa0, 0x90, 0x88, 0x00, // 82: 'R' 255 | 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70, 0x00, // 83: 'S' 256 | 0xf8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, // 84: 'T' 257 | 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, // 85: 'U' 258 | 0x88, 0x88, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00, // 86: 'V' 259 | 0x88, 0x88, 0x88, 0xa8, 0xa8, 0xd8, 0x88, 0x00, // 87: 'W' 260 | 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00, // 88: 'X' 261 | 0x88, 0x88, 0x88, 0x70, 0x20, 0x20, 0x20, 0x00, // 89: 'Y' 262 | 0xf8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xf8, 0x00, // 90: 'Z' 263 | 0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00, // 91: '[' 264 | 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00, // 92: '\' 265 | 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00, // 93: ']' 266 | 0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, // 94: '^' 267 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, // 95: '_' 268 | 0x40, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 96: '`' 269 | 0x00, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00, // 97: 'a' 270 | 0x80, 0x80, 0xb0, 0xc8, 0x88, 0xc8, 0xb0, 0x00, // 98: 'b' 271 | 0x00, 0x00, 0x70, 0x88, 0x80, 0x88, 0x70, 0x00, // 99: 'c' 272 | 0x08, 0x08, 0x68, 0x98, 0x88, 0x98, 0x68, 0x00, // 100: 'd' 273 | 0x00, 0x00, 0x70, 0x88, 0xf8, 0x80, 0x70, 0x00, // 101: 'e' 274 | 0x10, 0x28, 0x20, 0xf8, 0x20, 0x20, 0x20, 0x00, // 102: 'f' 275 | 0x00, 0x00, 0x68, 0x98, 0x98, 0x68, 0x08, 0x70, // 103: 'g' 276 | 0x80, 0x80, 0xf0, 0x88, 0x88, 0x88, 0x88, 0x00, // 104: 'h' 277 | 0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0x70, 0x00, // 105: 'i' 278 | 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x90, 0x60, // 106: 'j' 279 | 0x40, 0x40, 0x48, 0x50, 0x60, 0x50, 0x48, 0x00, // 107: 'k' 280 | 0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, // 108: 'l' 281 | 0x00, 0x00, 0xd0, 0xa8, 0xa8, 0xa8, 0xa8, 0x00, // 109: 'm' 282 | 0x00, 0x00, 0xb0, 0xc8, 0x88, 0x88, 0x88, 0x00, // 110: 'n' 283 | 0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00, // 111: 'o' 284 | 0x00, 0x00, 0xb0, 0xc8, 0xc8, 0xb0, 0x80, 0x80, // 112: 'p' 285 | 0x00, 0x00, 0x68, 0x98, 0x98, 0x68, 0x08, 0x08, // 113: 'q' 286 | 0x00, 0x00, 0xb0, 0xc8, 0x80, 0x80, 0x80, 0x00, // 114: 'r' 287 | 0x00, 0x00, 0x78, 0x80, 0xf0, 0x08, 0xf0, 0x00, // 115: 's' 288 | 0x40, 0x40, 0xf0, 0x40, 0x40, 0x48, 0x30, 0x00, // 116: 't' 289 | 0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x68, 0x00, // 117: 'u' 290 | 0x00, 0x00, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, // 118: 'v' 291 | 0x00, 0x00, 0x88, 0xa8, 0xa8, 0xa8, 0x50, 0x00, // 119: 'w' 292 | 0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00, // 120: 'x' 293 | 0x00, 0x00, 0x88, 0x88, 0x98, 0x68, 0x08, 0x70, // 121: 'y' 294 | 0x00, 0x00, 0xf8, 0x10, 0x20, 0x40, 0xf8, 0x00, // 122: 'z' 295 | 0x18, 0x20, 0x20, 0x40, 0x20, 0x20, 0x18, 0x00, // 123: '{' 296 | 0x20, 0x20, 0x20, 0x00, 0x20, 0x20, 0x20, 0x00, // 124: '|' 297 | 0xc0, 0x20, 0x20, 0x10, 0x20, 0x20, 0xc0, 0x00, // 125: '}' 298 | 0x40, 0xa8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 126: '~' 299 | 0x00, 0x00, 0x20, 0x50, 0xf8, 0x00, 0x00, 0x00, // 127: DEL 300 | ]; 301 | 302 | const embedded_font3 = [ 303 | 0x00, 0x00, 0x00, 0x00, 0x00, // 0: (Control) 304 | 0x00, 0x00, 0x00, 0x00, 0x00, // 1: (Control) 305 | 0x00, 0x00, 0x00, 0x00, 0x00, // 2: (Control) 306 | 0x00, 0x00, 0x00, 0x00, 0x00, // 3: (Control) 307 | 0x00, 0x00, 0x00, 0x00, 0x00, // 4: (Control) 308 | 0x00, 0x00, 0x00, 0x00, 0x00, // 5: (Control) 309 | 0x00, 0x00, 0x00, 0x00, 0x00, // 6: (Control) 310 | 0x00, 0x00, 0x00, 0x00, 0x00, // 7: (Control) 311 | 0x00, 0x00, 0x00, 0x00, 0x00, // 8: (Control) 312 | 0x00, 0x00, 0x00, 0x00, 0x00, // 9: (Control) 313 | 0x00, 0x00, 0x00, 0x00, 0x00, // 10: (Control) 314 | 0x00, 0x00, 0x00, 0x00, 0x00, // 11: (Control) 315 | 0x00, 0x00, 0x00, 0x00, 0x00, // 12: (Control) 316 | 0x00, 0x00, 0x00, 0x00, 0x00, // 13: (Control) 317 | 0x00, 0x00, 0x00, 0x00, 0x00, // 14: (Control) 318 | 0x00, 0x00, 0x00, 0x00, 0x00, // 15: (Control) 319 | 0x00, 0x00, 0x00, 0x00, 0x00, // 16: (Control) 320 | 0x00, 0x00, 0x00, 0x00, 0x00, // 17: (Control) 321 | 0x00, 0x00, 0x00, 0x00, 0x00, // 18: (Control) 322 | 0x00, 0x00, 0x00, 0x00, 0x00, // 19: (Control) 323 | 0x00, 0x00, 0x00, 0x00, 0x00, // 20: (Control) 324 | 0x00, 0x00, 0x00, 0x00, 0x00, // 21: (Control) 325 | 0x00, 0x00, 0x00, 0x00, 0x00, // 22: (Control) 326 | 0x00, 0x00, 0x00, 0x00, 0x00, // 23: (Control) 327 | 0x00, 0x00, 0x00, 0x00, 0x00, // 24: (Control) 328 | 0x00, 0x00, 0x00, 0x00, 0x00, // 25: (Control) 329 | 0x00, 0x00, 0x00, 0x00, 0x00, // 26: (Control) 330 | 0x00, 0x00, 0x00, 0x00, 0x00, // 27: (Control) 331 | 0x00, 0x00, 0x00, 0x00, 0x00, // 28: (Control) 332 | 0x00, 0x00, 0x00, 0x00, 0x00, // 29: (Control) 333 | 0x00, 0x00, 0x00, 0x00, 0x00, // 30: (Control) 334 | 0x00, 0x00, 0x00, 0x00, 0x00, // 31: (Control) 335 | 0x00, 0x00, 0x00, 0x00, 0x00, // 32: ' ' (space) 336 | 0x40, 0x40, 0x40, 0x00, 0x40, // 33: '!' 337 | 0xa0, 0xa0, 0x00, 0x00, 0x00, // 34: '"' 338 | 0xa0, 0xe0, 0xa0, 0xe0, 0xa0, // 35: '#' 339 | 0x60, 0xc0, 0x60, 0xc0, 0x60, // 36: '$' 340 | 0xa0, 0x20, 0x40, 0x80, 0xa0, // 37: '%' 341 | 0x40, 0xa0, 0x40, 0xa0, 0x60, // 38: '&' 342 | 0x40, 0x40, 0x00, 0x00, 0x00, // 39: ''' 343 | 0x20, 0x40, 0x40, 0x40, 0x20, // 40: '(' 344 | 0x80, 0x40, 0x40, 0x40, 0x80, // 41: ')' 345 | 0xa0, 0x40, 0xe0, 0x40, 0xa0, // 42: '*' 346 | 0x00, 0x40, 0xe0, 0x40, 0x00, // 43: '+' 347 | 0x00, 0x00, 0x00, 0x40, 0x80, // 44: ',' 348 | 0x00, 0x00, 0xe0, 0x00, 0x00, // 45: '-' 349 | 0x00, 0x00, 0x00, 0x00, 0x40, // 46: '.' 350 | 0x20, 0x20, 0x40, 0x80, 0x80, // 47: '/' 351 | 0x60, 0xa0, 0xa0, 0xa0, 0xc0, // 48: '0' 352 | 0x40, 0xc0, 0x40, 0x40, 0xe0, // 49: '1' 353 | 0xc0, 0x20, 0x40, 0x80, 0xe0, // 50: '2' 354 | 0xc0, 0x20, 0x40, 0x20, 0xc0, // 51: '3' 355 | 0xa0, 0xa0, 0xe0, 0x20, 0x20, // 52: '4' 356 | 0xe0, 0x80, 0xc0, 0x20, 0xc0, // 53: '5' 357 | 0x60, 0x80, 0xe0, 0xa0, 0xe0, // 54: '6' 358 | 0xe0, 0x20, 0x40, 0x80, 0x80, // 55: '7' 359 | 0xe0, 0xa0, 0xe0, 0xa0, 0xe0, // 56: '8' 360 | 0xe0, 0xa0, 0xe0, 0x20, 0xc0, // 57: '9' 361 | 0x00, 0x40, 0x00, 0x40, 0x00, // 58: ':' 362 | 0x00, 0x40, 0x00, 0x40, 0x80, // 59: ';' 363 | 0x20, 0x40, 0x80, 0x40, 0x20, // 60: '<' 364 | 0x00, 0xe0, 0x00, 0xe0, 0x00, // 61: '=' 365 | 0x80, 0x40, 0x20, 0x40, 0x80, // 62: '>' 366 | 0xc0, 0x20, 0x40, 0x00, 0x40, // 63: '?' 367 | 0xe0, 0xa0, 0xe0, 0x80, 0x60, // 64: '@' 368 | 0x40, 0xa0, 0xe0, 0xa0, 0xa0, // 65: 'A' 369 | 0xc0, 0xa0, 0xc0, 0xa0, 0xc0, // 66: 'B' 370 | 0x60, 0x80, 0x80, 0x80, 0x60, // 67: 'C' 371 | 0xc0, 0xa0, 0xa0, 0xa0, 0xc0, // 68: 'D' 372 | 0xe0, 0x80, 0xc0, 0x80, 0xe0, // 69: 'E' 373 | 0xe0, 0x80, 0xc0, 0x80, 0x80, // 70: 'F' 374 | 0x60, 0x80, 0xa0, 0xa0, 0x60, // 71: 'G' 375 | 0xa0, 0xa0, 0xe0, 0xa0, 0xa0, // 72: 'H' 376 | 0xe0, 0x40, 0x40, 0x40, 0xe0, // 73: 'I' 377 | 0xe0, 0x20, 0x20, 0xa0, 0x40, // 74: 'J' 378 | 0xa0, 0xa0, 0xc0, 0xa0, 0xa0, // 75: 'K' 379 | 0x80, 0x80, 0x80, 0x80, 0xe0, // 76: 'L' 380 | 0xa0, 0xe0, 0xa0, 0xa0, 0xa0, // 77: 'M' 381 | 0xc0, 0xa0, 0xa0, 0xa0, 0xa0, // 78: 'N' 382 | 0x40, 0xa0, 0xa0, 0xa0, 0x40, // 79: 'O' 383 | 0xc0, 0xa0, 0xc0, 0x80, 0x80, // 80: 'P' 384 | 0x40, 0xa0, 0xa0, 0xa0, 0x60, // 81: 'Q' 385 | 0xc0, 0xa0, 0xc0, 0xa0, 0xa0, // 82: 'R' 386 | 0x60, 0x80, 0x40, 0x20, 0xc0, // 83: 'S' 387 | 0xe0, 0x40, 0x40, 0x40, 0x40, // 84: 'T' 388 | 0xa0, 0xa0, 0xa0, 0xa0, 0x60, // 85: 'U' 389 | 0xa0, 0xa0, 0xa0, 0xa0, 0x40, // 86: 'V' 390 | 0xa0, 0xa0, 0xa0, 0xe0, 0xa0, // 87: 'W' 391 | 0xa0, 0xa0, 0x40, 0xa0, 0xa0, // 88: 'X' 392 | 0xa0, 0xa0, 0x40, 0x40, 0x40, // 89: 'Y' 393 | 0xe0, 0x20, 0x40, 0x80, 0xe0, // 90: 'Z' 394 | 0x60, 0x40, 0x40, 0x40, 0x60, // 91: '[' 395 | 0x80, 0x80, 0x40, 0x20, 0x20, // 92: '\' 396 | 0xc0, 0x40, 0x40, 0x40, 0xc0, // 93: ']' 397 | 0x40, 0xa0, 0x00, 0x00, 0x00, // 94: '^' 398 | 0x00, 0x00, 0x00, 0x00, 0xe0, // 95: '_' 399 | 0x80, 0x40, 0x00, 0x00, 0x00, // 96: '`' 400 | 0x00, 0xc0, 0x60, 0xa0, 0x60, // 97: 'a' 401 | 0x80, 0xc0, 0xa0, 0xa0, 0xc0, // 98: 'b' 402 | 0x00, 0x60, 0x80, 0x80, 0x60, // 99: 'c' 403 | 0x20, 0x60, 0xa0, 0xa0, 0x60, // 100: 'd' 404 | 0x00, 0x60, 0xe0, 0x80, 0x60, // 101: 'e' 405 | 0x20, 0x40, 0xe0, 0x40, 0x40, // 102: 'f' 406 | 0x00, 0x60, 0xa0, 0x60, 0xc0, // 103: 'g' 407 | 0x80, 0xc0, 0xa0, 0xa0, 0xa0, // 104: 'h' 408 | 0x40, 0x00, 0x40, 0x40, 0x40, // 105: 'i' 409 | 0x20, 0x00, 0x20, 0x20, 0xe0, // 106: 'j' 410 | 0x80, 0xa0, 0xc0, 0xa0, 0xa0, // 107: 'k' 411 | 0xc0, 0x40, 0x40, 0x40, 0xe0, // 108: 'l' 412 | 0x00, 0xa0, 0xe0, 0xa0, 0xa0, // 109: 'm' 413 | 0x00, 0xc0, 0xa0, 0xa0, 0xa0, // 110: 'n' 414 | 0x00, 0x40, 0xa0, 0xa0, 0x40, // 111: 'o' 415 | 0x00, 0xc0, 0xa0, 0xc0, 0x80, // 112: 'p' 416 | 0x00, 0x60, 0xa0, 0x60, 0x20, // 113: 'q' 417 | 0x00, 0xa0, 0xc0, 0x80, 0x80, // 114: 'r' 418 | 0x00, 0x60, 0xc0, 0x60, 0xc0, // 115: 's' 419 | 0x40, 0xe0, 0x40, 0x40, 0x60, // 116: 't' 420 | 0x00, 0xa0, 0xa0, 0xa0, 0x60, // 117: 'u' 421 | 0x00, 0xa0, 0xa0, 0xa0, 0x40, // 118: 'v' 422 | 0x00, 0xa0, 0xa0, 0xe0, 0xa0, // 119: 'w' 423 | 0x00, 0xa0, 0x40, 0xa0, 0xa0, // 120: 'x' 424 | 0x00, 0xa0, 0xa0, 0x60, 0xc0, // 121: 'y' 425 | 0x00, 0xe0, 0x20, 0x40, 0xe0, // 122: 'z' 426 | 0x20, 0x40, 0xc0, 0x40, 0x20, // 123: '{' 427 | 0x40, 0x40, 0x00, 0x40, 0x40, // 124: '|' 428 | 0x80, 0x40, 0x60, 0x40, 0x80, // 125: '}' 429 | 0x00, 0x60, 0xc0, 0x00, 0x00, // 126: '~' 430 | 0x00, 0x00, 0x00, 0x00, 0x00, // 127: DEL 431 | ]; 432 | 433 | const embedded_font4 = [ 434 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: (Control) 435 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1: (Control) 436 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2: (Control) 437 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3: (Control) 438 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4: (Control) 439 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 5: (Control) 440 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6: (Control) 441 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7: (Control) 442 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8: (Control) 443 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9: (Control) 444 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10: (Control) 445 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 11: (Control) 446 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12: (Control) 447 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 13: (Control) 448 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 14: (Control) 449 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 15: (Control) 450 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 16: (Control) 451 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 17: (Control) 452 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 18: (Control) 453 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 19: (Control) 454 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20: (Control) 455 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 21: (Control) 456 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 22: (Control) 457 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 23: (Control) 458 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 24: (Control) 459 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 25: (Control) 460 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 26: (Control) 461 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 27: (Control) 462 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 28: (Control) 463 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 29: (Control) 464 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 30: (Control) 465 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 31: (Control) 466 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32: ' ' (space) 467 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, // 33: '!' 468 | 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 34: '"' 469 | 0x36, 0x36, 0x7f, 0x36, 0x36, 0x36, 0x7f, 0x36, 0x36, 0x00, // 35: '#' 470 | 0x18, 0x3e, 0x58, 0x58, 0x3e, 0x1a, 0x1a, 0x7c, 0x18, 0x00, // 36: '$' 471 | 0x60, 0x92, 0x94, 0x68, 0x16, 0x29, 0x49, 0x06, 0x00, 0x00, // 37: '%' 472 | 0x38, 0x44, 0x44, 0x38, 0x60, 0x54, 0x48, 0x34, 0x00, 0x00, // 38: '&' 473 | 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 39: ''' 474 | 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00, // 40: '(' 475 | 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00, // 41: ')' 476 | 0x00, 0x18, 0x5a, 0x3c, 0x3c, 0x5a, 0x18, 0x00, 0x00, 0x00, // 42: '*' 477 | 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, // 43: '+' 478 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, // 44: ',' 479 | 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, // 45: '-' 480 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, // 46: '.' 481 | 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x00, 0x00, 0x00, // 47: '/' 482 | 0x3c, 0x66, 0x66, 0x6e, 0x76, 0x66, 0x66, 0x3c, 0x00, 0x00, // 48: '0' 483 | 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, // 49: '1' 484 | 0x3c, 0x66, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x00, // 50: '2' 485 | 0x3c, 0x66, 0x06, 0x06, 0x1c, 0x06, 0x66, 0x3c, 0x00, 0x00, // 51: '3' 486 | 0x0c, 0x1c, 0x2c, 0x4c, 0x4c, 0x7e, 0x0c, 0x0c, 0x00, 0x00, // 52: '4' 487 | 0x7e, 0x60, 0x60, 0x7c, 0x06, 0x06, 0x66, 0x3c, 0x00, 0x00, // 53: '5' 488 | 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x3c, 0x00, 0x00, // 54: '6' 489 | 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, // 55: '7' 490 | 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x00, // 56: '8' 491 | 0x3c, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x0c, 0x38, 0x00, 0x00, // 57: '9' 492 | 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, // 58: ':' 493 | 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, // 59: ';' 494 | 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, // 60: '<' 495 | 0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, // 61: '=' 496 | 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, // 62: '>' 497 | 0x3c, 0x66, 0x06, 0x0c, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, // 63: '?' 498 | 0x3c, 0x66, 0x6e, 0x6a, 0x6a, 0x6e, 0x60, 0x3e, 0x00, 0x00, // 64: '@' 499 | 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x00, // 65: 'A' 500 | 0x7c, 0x66, 0x66, 0x66, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, // 66: 'B' 501 | 0x3c, 0x66, 0x60, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00, 0x00, // 67: 'C' 502 | 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x00, // 68: 'D' 503 | 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x7e, 0x00, 0x00, // 69: 'E' 504 | 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x00, 0x00, // 70: 'F' 505 | 0x3c, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x66, 0x3c, 0x00, 0x00, // 71: 'G' 506 | 0x66, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x00, // 72: 'H' 507 | 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, // 73: 'I' 508 | 0x1e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x6c, 0x38, 0x00, 0x00, // 74: 'J' 509 | 0x66, 0x6c, 0x78, 0x70, 0x70, 0x78, 0x6c, 0x66, 0x00, 0x00, // 75: 'K' 510 | 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, // 76: 'L' 511 | 0x63, 0x77, 0x7f, 0x6b, 0x6b, 0x63, 0x63, 0x63, 0x00, 0x00, // 77: 'M' 512 | 0x66, 0x66, 0x76, 0x7e, 0x6e, 0x66, 0x66, 0x66, 0x00, 0x00, // 78: 'N' 513 | 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, // 79: 'O' 514 | 0x7c, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x00, 0x00, // 80: 'P' 515 | 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6e, 0x3c, 0x06, 0x00, // 81: 'Q' 516 | 0x7c, 0x66, 0x66, 0x66, 0x7c, 0x78, 0x6c, 0x66, 0x00, 0x00, // 82: 'R' 517 | 0x3c, 0x66, 0x60, 0x30, 0x1c, 0x06, 0x66, 0x3c, 0x00, 0x00, // 83: 'S' 518 | 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, // 84: 'T' 519 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, // 85: 'U' 520 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, // 86: 'V' 521 | 0x63, 0x63, 0x63, 0x6b, 0x6b, 0x7f, 0x77, 0x63, 0x00, 0x00, // 87: 'W' 522 | 0x66, 0x66, 0x3c, 0x18, 0x18, 0x3c, 0x66, 0x66, 0x00, 0x00, // 88: 'X' 523 | 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, // 89: 'Y' 524 | 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, // 90: 'Z' 525 | 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, // 91: '[' 526 | 0x00, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, // 92: '\' 527 | 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, // 93: ']' 528 | 0x18, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 94: '^' 529 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, // 95: '_' 530 | 0x30, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 96: '`' 531 | 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00, 0x00, // 97: 'a' 532 | 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, // 98: 'b' 533 | 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x66, 0x3c, 0x00, 0x00, // 99: 'c' 534 | 0x06, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, // 100: 'd' 535 | 0x00, 0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, 0x00, // 101: 'e' 536 | 0x1c, 0x36, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x00, 0x00, // 102: 'f' 537 | 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x3c, 0x00, // 103: 'g' 538 | 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, // 104: 'h' 539 | 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, // 105: 'i' 540 | 0x0c, 0x0c, 0x00, 0x1c, 0x0c, 0x0c, 0x0c, 0x6c, 0x38, 0x00, // 106: 'j' 541 | 0x60, 0x60, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00, 0x00, // 107: 'k' 542 | 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, // 108: 'l' 543 | 0x00, 0x00, 0x00, 0x76, 0x7f, 0x6b, 0x6b, 0x6b, 0x00, 0x00, // 109: 'm' 544 | 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, // 110: 'n' 545 | 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, // 111: 'o' 546 | 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00, // 112: 'p' 547 | 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x00, // 113: 'q' 548 | 0x00, 0x00, 0x00, 0x6c, 0x76, 0x60, 0x60, 0x60, 0x00, 0x00, // 114: 'r' 549 | 0x00, 0x00, 0x00, 0x3c, 0x60, 0x3c, 0x06, 0x7c, 0x00, 0x00, // 115: 's' 550 | 0x30, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x36, 0x1c, 0x00, 0x00, // 116: 't' 551 | 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, // 117: 'u' 552 | 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, // 118: 'v' 553 | 0x00, 0x00, 0x00, 0x63, 0x6b, 0x6b, 0x7f, 0x36, 0x00, 0x00, // 119: 'w' 554 | 0x00, 0x00, 0x00, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0x00, 0x00, // 120: 'x' 555 | 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x3c, 0x00, // 121: 'y' 556 | 0x00, 0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00, 0x00, // 122: 'z' 557 | 0x0e, 0x18, 0x18, 0x30, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, // 123: '{' 558 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, // 124: '|' 559 | 0x70, 0x18, 0x18, 0x0c, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, // 125: '}' 560 | 0x00, 0x00, 0x00, 0x32, 0x7f, 0x4c, 0x00, 0x00, 0x00, 0x00, // 126: '~' 561 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 127: DEL 562 | ]; 563 | 564 | const embedded_font5 = [ 565 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: (Control) 566 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1: (Control) 567 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2: (Control) 568 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3: (Control) 569 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4: (Control) 570 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 5: (Control) 571 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6: (Control) 572 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7: (Control) 573 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8: (Control) 574 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9: (Control) 575 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10: (Control) 576 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 11: (Control) 577 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12: (Control) 578 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 13: (Control) 579 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 14: (Control) 580 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 15: (Control) 581 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 16: (Control) 582 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 17: (Control) 583 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 18: (Control) 584 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 19: (Control) 585 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20: (Control) 586 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 21: (Control) 587 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 22: (Control) 588 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 23: (Control) 589 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 24: (Control) 590 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 25: (Control) 591 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 26: (Control) 592 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 27: (Control) 593 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 28: (Control) 594 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 29: (Control) 595 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 30: (Control) 596 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 31: (Control) 597 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32: ' ' (space) 598 | 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, // 33: '!' 599 | 0x48, 0x48, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 34: '"' 600 | 0x24, 0x24, 0x7e, 0x24, 0x24, 0x24, 0x7e, 0x24, 0x24, 0x00, // 35: '#' 601 | 0x10, 0x7e, 0x90, 0x90, 0x7e, 0x12, 0x12, 0xfc, 0x10, 0x00, // 36: '$' 602 | 0x60, 0x92, 0x94, 0x68, 0x16, 0x29, 0x49, 0x06, 0x00, 0x00, // 37: '%' 603 | 0x38, 0x44, 0x44, 0x38, 0x60, 0x54, 0x48, 0x34, 0x00, 0x00, // 38: '&' 604 | 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 39: ''' 605 | 0x0c, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x0c, 0x00, // 40: '(' 606 | 0x30, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x30, 0x00, // 41: ')' 607 | 0x00, 0x10, 0x54, 0x38, 0x38, 0x54, 0x10, 0x00, 0x00, 0x00, // 42: '*' 608 | 0x00, 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, // 43: '+' 609 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x30, 0x00, // 44: ',' 610 | 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, // 45: '-' 611 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, // 46: '.' 612 | 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 47: '/' 613 | 0x7c, 0x82, 0x86, 0x8a, 0x92, 0xa2, 0xc2, 0x7c, 0x00, 0x00, // 48: '0' 614 | 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, // 49: '1' 615 | 0x3c, 0x42, 0x42, 0x04, 0x08, 0x10, 0x20, 0x7e, 0x00, 0x00, // 50: '2' 616 | 0x3c, 0x42, 0x02, 0x02, 0x1c, 0x02, 0x42, 0x3c, 0x00, 0x00, // 51: '3' 617 | 0x0c, 0x14, 0x24, 0x44, 0x44, 0x7e, 0x04, 0x04, 0x00, 0x00, // 52: '4' 618 | 0x7e, 0x40, 0x40, 0x7c, 0x02, 0x02, 0x42, 0x3c, 0x00, 0x00, // 53: '5' 619 | 0x1c, 0x20, 0x40, 0x40, 0x7c, 0x42, 0x42, 0x3c, 0x00, 0x00, // 54: '6' 620 | 0x7e, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, // 55: '7' 621 | 0x3c, 0x42, 0x42, 0x42, 0x3c, 0x42, 0x42, 0x3c, 0x00, 0x00, // 56: '8' 622 | 0x3c, 0x42, 0x42, 0x42, 0x3e, 0x02, 0x04, 0x38, 0x00, 0x00, // 57: '9' 623 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // 58: ':' 624 | 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x08, 0x08, 0x30, 0x00, // 59: ';' 625 | 0x04, 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, // 60: '<' 626 | 0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, // 61: '=' 627 | 0x20, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, // 62: '>' 628 | 0x3c, 0x42, 0x02, 0x04, 0x08, 0x10, 0x00, 0x10, 0x10, 0x00, // 63: '?' 629 | 0x3c, 0x46, 0x4e, 0x4a, 0x4a, 0x4e, 0x40, 0x3e, 0x00, 0x00, // 64: '@' 630 | 0x10, 0x28, 0x44, 0x44, 0x82, 0xfe, 0x82, 0x82, 0x00, 0x00, // 65: 'A' 631 | 0xfc, 0x82, 0x82, 0xfc, 0x82, 0x82, 0x82, 0xfc, 0x00, 0x00, // 66: 'B' 632 | 0x7c, 0xc2, 0x80, 0x80, 0x80, 0x80, 0xc2, 0x7c, 0x00, 0x00, // 67: 'C' 633 | 0xf8, 0x84, 0x82, 0x82, 0x82, 0x82, 0x84, 0xf8, 0x00, 0x00, // 68: 'D' 634 | 0xfe, 0x80, 0x80, 0x80, 0xfc, 0x80, 0x80, 0xfe, 0x00, 0x00, // 69: 'E' 635 | 0xfe, 0x80, 0x80, 0x80, 0xfc, 0x80, 0x80, 0x80, 0x00, 0x00, // 70: 'F' 636 | 0x7c, 0x80, 0x80, 0x80, 0x9e, 0x82, 0x82, 0x7e, 0x00, 0x00, // 71: 'G' 637 | 0x82, 0x82, 0x82, 0x82, 0xfe, 0x82, 0x82, 0x82, 0x00, 0x00, // 72: 'H' 638 | 0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xfe, 0x00, 0x00, // 73: 'I' 639 | 0x7e, 0x02, 0x02, 0x02, 0x02, 0x02, 0x82, 0x7c, 0x00, 0x00, // 74: 'J' 640 | 0x82, 0x84, 0x88, 0xb0, 0xe0, 0xb8, 0x8c, 0x86, 0x00, 0x00, // 75: 'K' 641 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfe, 0x00, 0x00, // 76: 'L' 642 | 0x82, 0xc6, 0xaa, 0x92, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, // 77: 'M' 643 | 0x82, 0xc2, 0xa2, 0x92, 0x8a, 0x86, 0x82, 0x82, 0x00, 0x00, // 78: 'N' 644 | 0x7c, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7c, 0x00, 0x00, // 79: 'O' 645 | 0xfc, 0x82, 0x82, 0x82, 0xfc, 0x80, 0x80, 0x80, 0x00, 0x00, // 80: 'P' 646 | 0x7c, 0x82, 0x82, 0x82, 0x92, 0x8a, 0x84, 0x7a, 0x00, 0x00, // 81: 'Q' 647 | 0xfc, 0x82, 0x82, 0x8c, 0xf8, 0x88, 0x84, 0x82, 0x00, 0x00, // 82: 'R' 648 | 0x7c, 0x82, 0x80, 0x80, 0x7c, 0x02, 0x82, 0x7c, 0x00, 0x00, // 83: 'S' 649 | 0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, // 84: 'T' 650 | 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7c, 0x00, 0x00, // 85: 'U' 651 | 0x82, 0x82, 0x82, 0x82, 0x82, 0x44, 0x38, 0x10, 0x00, 0x00, // 86: 'V' 652 | 0x82, 0x82, 0x92, 0x92, 0x92, 0x92, 0x54, 0x28, 0x00, 0x00, // 87: 'W' 653 | 0x82, 0x44, 0x28, 0x10, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, // 88: 'X' 654 | 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, // 89: 'Y' 655 | 0xfe, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xfe, 0x00, 0x00, // 90: 'Z' 656 | 0x3c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x00, 0x00, // 91: '[' 657 | 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, // 92: '\' 658 | 0x3c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x3c, 0x00, 0x00, // 93: ']' 659 | 0x18, 0x24, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 94: '^' 660 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, // 95: '_' 661 | 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 96: '`' 662 | 0x00, 0x00, 0x7e, 0x84, 0x84, 0x84, 0x84, 0x7e, 0x00, 0x00, // 97: 'a' 663 | 0x80, 0x80, 0xfc, 0x82, 0x82, 0x82, 0x82, 0xfc, 0x00, 0x00, // 98: 'b' 664 | 0x00, 0x00, 0x7c, 0x82, 0x80, 0x80, 0x82, 0x7c, 0x00, 0x00, // 99: 'c' 665 | 0x02, 0x02, 0x7e, 0x82, 0x82, 0x82, 0x82, 0x7e, 0x00, 0x00, // 100: 'd' 666 | 0x00, 0x00, 0x7c, 0x82, 0xfe, 0x80, 0x80, 0x7c, 0x00, 0x00, // 101: 'e' 667 | 0x1c, 0x20, 0x20, 0x20, 0xfc, 0x20, 0x20, 0x20, 0x00, 0x00, // 102: 'f' 668 | 0x00, 0x00, 0x7e, 0x82, 0x82, 0x7e, 0x02, 0x82, 0x7c, 0x00, // 103: 'g' 669 | 0x80, 0x80, 0x80, 0xfc, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, // 104: 'h' 670 | 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, // 105: 'i' 671 | 0x04, 0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, // 106: 'j' 672 | 0x80, 0x82, 0x84, 0x88, 0xf0, 0x88, 0x84, 0x82, 0x00, 0x00, // 107: 'k' 673 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24, 0x38, 0x00, 0x00, // 108: 'l' 674 | 0x00, 0x00, 0xec, 0x92, 0x92, 0x92, 0x82, 0x82, 0x00, 0x00, // 109: 'm' 675 | 0x00, 0x00, 0xfc, 0x82, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, // 110: 'n' 676 | 0x00, 0x00, 0x7c, 0x82, 0x82, 0x82, 0x82, 0x7c, 0x00, 0x00, // 111: 'o' 677 | 0x00, 0x00, 0xfc, 0x82, 0x82, 0xfc, 0x80, 0x80, 0x80, 0x00, // 112: 'p' 678 | 0x00, 0x00, 0x7e, 0x82, 0x82, 0x7e, 0x02, 0x02, 0x02, 0x00, // 113: 'q' 679 | 0x00, 0x00, 0xfc, 0x82, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, // 114: 'r' 680 | 0x00, 0x00, 0x7c, 0x82, 0x78, 0x04, 0x82, 0x7c, 0x00, 0x00, // 115: 's' 681 | 0x10, 0x10, 0x7c, 0x10, 0x10, 0x10, 0x14, 0x18, 0x00, 0x00, // 116: 't' 682 | 0x00, 0x00, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7e, 0x00, 0x00, // 117: 'u' 683 | 0x00, 0x00, 0x82, 0x82, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00, // 118: 'v' 684 | 0x00, 0x00, 0x82, 0x82, 0x92, 0x92, 0x54, 0x28, 0x00, 0x00, // 119: 'w' 685 | 0x00, 0x00, 0x84, 0x48, 0x30, 0x30, 0x48, 0x84, 0x00, 0x00, // 120: 'x' 686 | 0x00, 0x00, 0x82, 0x82, 0x82, 0x7e, 0x02, 0x02, 0x7c, 0x00, // 121: 'y' 687 | 0x00, 0x00, 0xfe, 0x08, 0x10, 0x20, 0x40, 0xfe, 0x00, 0x00, // 122: 'z' 688 | 0x0e, 0x10, 0x10, 0x20, 0x10, 0x10, 0x10, 0x0e, 0x00, 0x00, // 123: '{' 689 | 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, // 124: '|' 690 | 0x70, 0x08, 0x08, 0x04, 0x08, 0x08, 0x08, 0x70, 0x00, 0x00, // 125: '}' 691 | 0x00, 0x00, 0x00, 0x62, 0x92, 0x8c, 0x00, 0x00, 0x00, 0x00, // 126: '~' 692 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 127: DEL 693 | ]; 694 | 695 | FONTS[0].data = embedded_font1; 696 | FONTS[1].data = embedded_font2; 697 | FONTS[2].data = embedded_font3; 698 | FONTS[3].data = embedded_font4; 699 | FONTS[4].data = embedded_font5; 700 | -------------------------------------------------------------------------------- /src/vt100.c: -------------------------------------------------------------------------------- 1 | #include "vt100.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* External variables from config.h */ 19 | extern unsigned int defaultfg; 20 | extern unsigned int defaultbg; 21 | extern unsigned int tabspaces; 22 | extern char default_shell[]; 23 | extern char termname[]; 24 | 25 | /* External variables from main.c */ 26 | extern char *opt_io; 27 | extern char **opt_cmd; 28 | extern int opt_cmd_size; 29 | extern int show_help; 30 | 31 | /* VT100/Terminal global variables */ 32 | Term term; 33 | CSIEscape csiescseq; 34 | STREscape strescseq; 35 | int cmdfd; 36 | int iofd = -1; 37 | static pid_t pid; 38 | 39 | /* UTF-8 functions */ 40 | int utf8_decode(char *s, long *u) { 41 | uchar c; 42 | int i, n, rtn; 43 | 44 | rtn = 1; 45 | c = *s; 46 | if (~c & B7) { /* 0xxxxxxx */ 47 | *u = c; 48 | return rtn; 49 | } else if ((c & (B7 | B6 | B5)) == (B7 | B6)) { /* 110xxxxx */ 50 | *u = c & (B4 | B3 | B2 | B1 | B0); 51 | n = 1; 52 | } else if ((c & (B7 | B6 | B5 | B4)) == (B7 | B6 | B5)) { /* 1110xxxx */ 53 | *u = c & (B3 | B2 | B1 | B0); 54 | n = 2; 55 | } else if ((c & (B7 | B6 | B5 | B4 | B3)) == (B7 | B6 | B5 | B4)) { /* 11110xxx */ 56 | *u = c & (B2 | B1 | B0); 57 | n = 3; 58 | } else { 59 | goto invalid; 60 | } 61 | 62 | for (i = n, ++s; i > 0; --i, ++rtn, ++s) { 63 | c = *s; 64 | if ((c & (B7 | B6)) != B7) /* 10xxxxxx */ 65 | goto invalid; 66 | *u <<= 6; 67 | *u |= c & (B5 | B4 | B3 | B2 | B1 | B0); 68 | } 69 | 70 | if ((n == 1 && *u < 0x80) || (n == 2 && *u < 0x800) || (n == 3 && *u < 0x10000) || (*u >= 0xD800 && *u <= 0xDFFF)) { 71 | goto invalid; 72 | } 73 | 74 | return rtn; 75 | invalid: 76 | *u = 0xFFFD; 77 | 78 | return rtn; 79 | } 80 | 81 | int utf8_encode(long *u, char *s) { 82 | uchar *sp; 83 | ulong uc; 84 | int i, n; 85 | 86 | sp = (uchar *)s; 87 | uc = *u; 88 | if (uc < 0x80) { 89 | *sp = uc; /* 0xxxxxxx */ 90 | return 1; 91 | } else if (*u < 0x800) { 92 | *sp = (uc >> 6) | (B7 | B6); /* 110xxxxx */ 93 | n = 1; 94 | } else if (uc < 0x10000) { 95 | *sp = (uc >> 12) | (B7 | B6 | B5); /* 1110xxxx */ 96 | n = 2; 97 | } else if (uc <= 0x10FFFF) { 98 | *sp = (uc >> 18) | (B7 | B6 | B5 | B4); /* 11110xxx */ 99 | n = 3; 100 | } else { 101 | goto invalid; 102 | } 103 | 104 | for (i = n, ++sp; i > 0; --i, ++sp) *sp = ((uc >> 6 * (i - 1)) & (B5 | B4 | B3 | B2 | B1 | B0)) | B7; /* 10xxxxxx */ 105 | 106 | return n + 1; 107 | invalid: 108 | /* U+FFFD */ 109 | *s++ = '\xEF'; 110 | *s++ = '\xBF'; 111 | *s = '\xBD'; 112 | 113 | return 3; 114 | } 115 | 116 | /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode 117 | UTF-8 otherwise return 0 */ 118 | int is_full_utf8(char *s, int b) { 119 | uchar *c1, *c2, *c3; 120 | 121 | c1 = (uchar *)s; 122 | c2 = (uchar *)++s; 123 | c3 = (uchar *)++s; 124 | if (b < 1) { 125 | return 0; 126 | } else if ((*c1 & (B7 | B6 | B5)) == (B7 | B6) && b == 1) { 127 | return 0; 128 | } else if ((*c1 & (B7 | B6 | B5 | B4)) == (B7 | B6 | B5) && ((b == 1) || ((b == 2) && (*c2 & (B7 | B6)) == B7))) { 129 | return 0; 130 | } else if ((*c1 & (B7 | B6 | B5 | B4 | B3)) == (B7 | B6 | B5 | B4) && ((b == 1) || ((b == 2) && (*c2 & (B7 | B6)) == B7) || ((b == 3) && (*c2 & (B7 | B6)) == B7 && (*c3 & (B7 | B6)) == B7))) { 131 | return 0; 132 | } else { 133 | return 1; 134 | } 135 | } 136 | 137 | int utf8_size(char *s) { 138 | uchar c = *s; 139 | 140 | if (~c & B7) { 141 | return 1; 142 | } else if ((c & (B7 | B6 | B5)) == (B7 | B6)) { 143 | return 2; 144 | } else if ((c & (B7 | B6 | B5 | B4)) == (B7 | B6 | B5)) { 145 | return 3; 146 | } else { 147 | return 4; 148 | } 149 | } 150 | 151 | /* External functions needed by VT100 */ 152 | void exec_sh(void) { 153 | char **args; 154 | char *envshell = getenv("SHELL"); 155 | 156 | unsetenv("COLUMNS"); 157 | unsetenv("LINES"); 158 | unsetenv("TERMCAP"); 159 | chdir(getenv("HOME")); 160 | 161 | if (show_help != 0) { 162 | system("uname -a"); 163 | system("echo '\n'"); 164 | } 165 | 166 | signal(SIGCHLD, SIG_DFL); 167 | signal(SIGHUP, SIG_DFL); 168 | signal(SIGINT, SIG_DFL); 169 | signal(SIGQUIT, SIG_DFL); 170 | signal(SIGTERM, SIG_DFL); 171 | signal(SIGALRM, SIG_DFL); 172 | 173 | DEFAULT(envshell, default_shell); 174 | setenv("TERM", termname, 1); 175 | args = (char *[]){envshell, "-i", NULL}; 176 | 177 | // executing opt_cmd 178 | for (int i = 0; i < opt_cmd_size; i++) { 179 | char echo_cmd[255]; 180 | sprintf(echo_cmd, "echo '\n$ %s\n'", opt_cmd[i]); 181 | system(echo_cmd); 182 | system(opt_cmd[i]); 183 | } 184 | 185 | execvp(args[0], args); 186 | exit(EXIT_FAILURE); 187 | } 188 | 189 | void sig_chld(int a) { 190 | int stat = 0; 191 | (void)a; 192 | 193 | if (waitpid(pid, &stat, 0) < 0) die("Waiting for pid %hd failed: %s\n", pid, strerror(errno)); 194 | 195 | if (WIFEXITED(stat)) { 196 | exit(WEXITSTATUS(stat)); 197 | } else { 198 | exit(EXIT_FAILURE); 199 | } 200 | } 201 | 202 | void tty_new(void) { 203 | int m, s; 204 | struct winsize w = {term.row, term.col, 0, 0}; 205 | 206 | /* seems to work fine on linux, openbsd and freebsd */ 207 | if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s\n", strerror(errno)); 208 | 209 | switch (pid = fork()) { 210 | case -1: 211 | die("fork failed\n"); 212 | break; 213 | case 0: 214 | setsid(); /* create a new process group */ 215 | dup2(s, STDIN_FILENO); 216 | dup2(s, STDOUT_FILENO); 217 | dup2(s, STDERR_FILENO); 218 | if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 219 | close(s); 220 | close(m); 221 | exec_sh(); 222 | break; 223 | default: 224 | close(s); 225 | cmdfd = m; 226 | signal(SIGCHLD, sig_chld); 227 | if (opt_io) { 228 | iofd = (!strcmp(opt_io, "-")) ? STDOUT_FILENO : open(opt_io, O_WRONLY | O_CREAT, 0666); 229 | if (iofd < 0) { 230 | fprintf(stderr, "Error opening %s:%s\n", opt_io, strerror(errno)); 231 | } 232 | } 233 | } 234 | } 235 | 236 | void dump(char c) { 237 | static int col; 238 | 239 | fprintf(stderr, " %02x '%c' ", c, isprint(c) ? c : '.'); 240 | if (++col % 10 == 0) fprintf(stderr, "\n"); 241 | } 242 | 243 | void tty_read(void) { 244 | static char buf[BUFSIZ]; 245 | static int buflen = 0; 246 | char *ptr; 247 | char s[UTF_SIZ]; 248 | int charsize; /* size of utf8 char in bytes */ 249 | long utf8c; 250 | int ret; 251 | 252 | /* append read bytes to unprocessed bytes */ 253 | if ((ret = read(cmdfd, buf + buflen, LEN(buf) - buflen)) < 0) die("Couldn't read from shell: %s\n", strerror(errno)); 254 | 255 | /* process every complete utf8 char */ 256 | buflen += ret; 257 | ptr = buf; 258 | while (buflen >= UTF_SIZ || is_full_utf8(ptr, buflen)) { 259 | charsize = utf8_decode(ptr, &utf8c); 260 | utf8_encode(&utf8c, s); 261 | t_putc(s, charsize); 262 | ptr += charsize; 263 | buflen -= charsize; 264 | } 265 | 266 | /* keep any uncomplete utf8 char for the next call */ 267 | memmove(buf, ptr, buflen); 268 | } 269 | 270 | void tty_write(const char *s, size_t n) { 271 | if (write(cmdfd, s, n) == -1) die("write error on tty: %s\n", strerror(errno)); 272 | } 273 | 274 | void tty_resize(void) { 275 | struct winsize w; 276 | 277 | w.ws_row = term.row; 278 | w.ws_col = term.col; 279 | w.ws_xpixel = 0; /* mainwindow.tw */ 280 | w.ws_ypixel = 0; /* mainwindow.th */ 281 | if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 282 | } 283 | 284 | void t_set_dirt(int top, int bot) { 285 | int i; 286 | 287 | LIMIT(top, 0, term.row - 1); 288 | LIMIT(bot, 0, term.row - 1); 289 | 290 | for (i = top; i <= bot; i++) term.dirty[i] = 1; 291 | } 292 | 293 | void t_full_dirt(void) { t_set_dirt(0, term.row - 1); } 294 | 295 | void t_cursor(int mode) { 296 | static TCursor c[2]; // Separate cursor save for primary[0] and alt[1] screens 297 | 298 | if (mode == CURSOR_SAVE) { 299 | int screen_idx = IS_SET(MODE_ALTSCREEN) ? 1 : 0; 300 | c[screen_idx] = term.c; 301 | } else if (mode == CURSOR_LOAD) { 302 | int screen_idx = IS_SET(MODE_ALTSCREEN) ? 1 : 0; 303 | term.c = c[screen_idx]; 304 | t_move_to(c[screen_idx].x, c[screen_idx].y); 305 | } 306 | } 307 | 308 | void t_reset(void) { 309 | uint i; 310 | 311 | term.c = (TCursor){{.mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg}, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 312 | 313 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 314 | for (i = tabspaces; i < term.col; i += tabspaces) term.tabs[i] = 1; 315 | term.top = 0; 316 | term.bot = term.row - 1; 317 | term.mode = MODE_WRAP; 318 | 319 | t_clear_region(0, 0, term.col - 1, term.row - 1); 320 | } 321 | 322 | void t_new(int col, int row) { 323 | /* set screen size */ 324 | term.row = row; 325 | term.col = col; 326 | term.line = x_malloc(term.row * sizeof(Line)); 327 | term.alt = x_malloc(term.row * sizeof(Line)); 328 | term.dirty = x_malloc(term.row * sizeof(*term.dirty)); 329 | term.tabs = x_malloc(term.col * sizeof(*term.tabs)); 330 | 331 | for (row = 0; row < term.row; row++) { 332 | term.line[row] = x_malloc(term.col * sizeof(Glyph)); 333 | term.alt[row] = x_malloc(term.col * sizeof(Glyph)); 334 | term.dirty[row] = 0; 335 | } 336 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 337 | /* setup screen */ 338 | t_reset(); 339 | } 340 | 341 | void t_swap_screen(void) { 342 | Line *tmp = term.line; 343 | 344 | term.line = term.alt; 345 | term.alt = tmp; 346 | term.mode ^= MODE_ALTSCREEN; 347 | t_full_dirt(); 348 | 349 | // Ensure cursor is within bounds after swap 350 | LIMIT(term.c.x, 0, term.col - 1); 351 | LIMIT(term.c.y, 0, term.row - 1); 352 | 353 | redraw(); // Force immediate redraw after screen swap 354 | } 355 | 356 | void t_scroll_down(int orig, int n) { 357 | int i; 358 | Line temp; 359 | 360 | LIMIT(n, 0, term.bot - orig + 1); 361 | 362 | t_clear_region(0, term.bot - n + 1, term.col - 1, term.bot); 363 | 364 | for (i = term.bot; i >= orig + n; i--) { 365 | temp = term.line[i]; 366 | term.line[i] = term.line[i - n]; 367 | term.line[i - n] = temp; 368 | 369 | term.dirty[i] = 1; 370 | term.dirty[i - n] = 1; 371 | } 372 | } 373 | 374 | void t_scroll_up(int orig, int n) { 375 | int i; 376 | Line temp; 377 | LIMIT(n, 0, term.bot - orig + 1); 378 | 379 | t_clear_region(0, orig, term.col - 1, orig + n - 1); 380 | 381 | for (i = orig; i <= term.bot - n; i++) { 382 | temp = term.line[i]; 383 | term.line[i] = term.line[i + n]; 384 | term.line[i + n] = temp; 385 | 386 | term.dirty[i] = 1; 387 | term.dirty[i + n] = 1; 388 | } 389 | } 390 | 391 | void t_newline(int first_col) { 392 | int y = term.c.y; 393 | 394 | if (y == term.bot) { 395 | t_scroll_up(term.top, 1); 396 | } else { 397 | y++; 398 | } 399 | t_move_to(first_col ? 0 : term.c.x, y); 400 | } 401 | 402 | void csi_parse(void) { 403 | /* int noarg = 1; */ 404 | char *p = csiescseq.buf; 405 | 406 | csiescseq.narg = 0; 407 | if (*p == '?') csiescseq.priv = 1, p++; 408 | 409 | while (p < csiescseq.buf + csiescseq.len) { 410 | while (isdigit(*p)) { 411 | csiescseq.arg[csiescseq.narg] *= 10; 412 | csiescseq.arg[csiescseq.narg] += *p++ - '0' /*, noarg = 0 */; 413 | } 414 | if (*p == ';' && csiescseq.narg + 1 < ESC_ARG_SIZ) { 415 | csiescseq.narg++, p++; 416 | } else { 417 | csiescseq.mode = *p; 418 | csiescseq.narg++; 419 | 420 | return; 421 | } 422 | } 423 | } 424 | 425 | void t_move_to(int x, int y) { 426 | LIMIT(x, 0, term.col - 1); 427 | LIMIT(y, 0, term.row - 1); 428 | term.c.state &= ~CURSOR_WRAPNEXT; 429 | term.c.x = x; 430 | term.c.y = y; 431 | } 432 | 433 | void t_set_char(char *c, Glyph *attr, int x, int y) { 434 | static char *vt100_0[62] = { 435 | /* 0x41 - 0x7e */ 436 | "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 437 | 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 438 | 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 439 | 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 440 | "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 441 | "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 442 | "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 443 | "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 444 | }; 445 | 446 | /* 447 | * The table is proudly stolen from rxvt. 448 | */ 449 | if (attr->mode & ATTR_GFX) { 450 | if (c[0] >= 0x41 && c[0] <= 0x7e && vt100_0[c[0] - 0x41]) { 451 | c = vt100_0[c[0] - 0x41]; 452 | } 453 | } 454 | 455 | term.dirty[y] = 1; 456 | term.line[y][x] = *attr; 457 | memcpy(term.line[y][x].c, c, UTF_SIZ); 458 | term.line[y][x].state |= GLYPH_SET; 459 | } 460 | 461 | void t_clear_region(int x1, int y1, int x2, int y2) { 462 | int x, y, temp; 463 | 464 | if (x1 > x2) temp = x1, x1 = x2, x2 = temp; 465 | if (y1 > y2) temp = y1, y1 = y2, y2 = temp; 466 | 467 | LIMIT(x1, 0, term.col - 1); 468 | LIMIT(x2, 0, term.col - 1); 469 | LIMIT(y1, 0, term.row - 1); 470 | LIMIT(y2, 0, term.row - 1); 471 | 472 | for (y = y1; y <= y2; y++) { 473 | term.dirty[y] = 1; 474 | for (x = x1; x <= x2; x++) term.line[y][x].state = 0; 475 | } 476 | } 477 | 478 | void t_delete_char(int n) { 479 | int src = term.c.x + n; 480 | int dst = term.c.x; 481 | int size = term.col - src; 482 | 483 | term.dirty[term.c.y] = 1; 484 | 485 | if (src >= term.col) { 486 | t_clear_region(term.c.x, term.c.y, term.col - 1, term.c.y); 487 | return; 488 | } 489 | 490 | memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph)); 491 | t_clear_region(term.col - n, term.c.y, term.col - 1, term.c.y); 492 | } 493 | 494 | void t_insert_blank(int n) { 495 | int src = term.c.x; 496 | int dst = src + n; 497 | int size = term.col - dst; 498 | 499 | term.dirty[term.c.y] = 1; 500 | 501 | if (dst >= term.col) { 502 | t_clear_region(term.c.x, term.c.y, term.col - 1, term.c.y); 503 | return; 504 | } 505 | 506 | memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph)); 507 | t_clear_region(src, term.c.y, dst - 1, term.c.y); 508 | } 509 | 510 | void t_insert_blank_line(int n) { 511 | if (term.c.y < term.top || term.c.y > term.bot) return; 512 | 513 | t_scroll_down(term.c.y, n); 514 | } 515 | 516 | void t_delete_line(int n) { 517 | if (term.c.y < term.top || term.c.y > term.bot) return; 518 | 519 | t_scroll_up(term.c.y, n); 520 | } 521 | 522 | void t_set_attr(int *attr, int l) { 523 | int i; 524 | 525 | for (i = 0; i < l; i++) { 526 | switch (attr[i]) { 527 | case 0: 528 | term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD | ATTR_ITALIC | ATTR_BLINK); 529 | term.c.attr.fg = defaultfg; 530 | term.c.attr.bg = defaultbg; 531 | break; 532 | case 1: 533 | term.c.attr.mode |= ATTR_BOLD; 534 | break; 535 | case 3: /* enter standout (highlight) */ 536 | term.c.attr.mode |= ATTR_ITALIC; 537 | break; 538 | case 4: 539 | term.c.attr.mode |= ATTR_UNDERLINE; 540 | break; 541 | case 5: 542 | term.c.attr.mode |= ATTR_BLINK; 543 | break; 544 | case 7: 545 | term.c.attr.mode |= ATTR_REVERSE; 546 | break; 547 | case 21: 548 | case 22: 549 | term.c.attr.mode &= ~ATTR_BOLD; 550 | break; 551 | case 23: /* leave standout (highlight) mode */ 552 | term.c.attr.mode &= ~ATTR_ITALIC; 553 | break; 554 | case 24: 555 | term.c.attr.mode &= ~ATTR_UNDERLINE; 556 | break; 557 | case 25: 558 | term.c.attr.mode &= ~ATTR_BLINK; 559 | break; 560 | case 27: 561 | term.c.attr.mode &= ~ATTR_REVERSE; 562 | break; 563 | case 29: /* not crossed out (most terminals don't support crossed out anyway) */ 564 | /* ignore - we don't have a crossed out attribute to unset */ 565 | break; 566 | case 38: 567 | if (i + 2 < l && attr[i + 1] == 5) { 568 | i += 2; 569 | if (BETWEEN(attr[i], 0, 255)) { 570 | term.c.attr.fg = attr[i]; 571 | } else { 572 | fprintf(stderr, "erresc: bad fgcolor %d\n", attr[i]); 573 | } 574 | } else { 575 | fprintf(stderr, "erresc(38): gfx attr %d unknown\n", attr[i]); 576 | } 577 | break; 578 | case 39: 579 | term.c.attr.fg = defaultfg; 580 | break; 581 | case 48: 582 | if (i + 2 < l && attr[i + 1] == 5) { 583 | i += 2; 584 | if (BETWEEN(attr[i], 0, 255)) { 585 | term.c.attr.bg = attr[i]; 586 | } else { 587 | fprintf(stderr, "erresc: bad bgcolor %d\n", attr[i]); 588 | } 589 | } else { 590 | fprintf(stderr, "erresc(48): gfx attr %d unknown\n", attr[i]); 591 | } 592 | break; 593 | case 49: 594 | term.c.attr.bg = defaultbg; 595 | break; 596 | default: 597 | if (BETWEEN(attr[i], 30, 37)) { 598 | term.c.attr.fg = attr[i] - 30; 599 | } else if (BETWEEN(attr[i], 40, 47)) { 600 | term.c.attr.bg = attr[i] - 40; 601 | } else if (BETWEEN(attr[i], 90, 97)) { 602 | term.c.attr.fg = attr[i] - 90 + 8; 603 | } else if (BETWEEN(attr[i], 100, 107)) { 604 | term.c.attr.bg = attr[i] - 100 + 8; 605 | } else { 606 | fprintf(stderr, "erresc(default): gfx attr %d unknown\n", attr[i]), csi_dump(); 607 | } 608 | break; 609 | } 610 | } 611 | } 612 | 613 | void t_set_scroll(int t, int b) { 614 | int temp; 615 | 616 | LIMIT(t, 0, term.row - 1); 617 | LIMIT(b, 0, term.row - 1); 618 | if (t > b) { 619 | temp = t; 620 | t = b; 621 | b = temp; 622 | } 623 | term.top = t; 624 | term.bot = b; 625 | } 626 | 627 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 628 | 629 | void t_set_mode(bool priv, bool set, int *args, int narg) { 630 | int *lim, mode; 631 | 632 | for (lim = args + narg; args < lim; ++args) { 633 | if (priv) { 634 | switch (*args) { 635 | break; 636 | case 1: /* DECCKM -- Cursor key */ 637 | MODBIT(term.mode, set, MODE_APPKEYPAD); 638 | break; 639 | case 5: /* DECSCNM -- Reverse video */ 640 | mode = term.mode; 641 | MODBIT(term.mode, set, MODE_REVERSE); 642 | if (mode != term.mode) redraw(); 643 | break; 644 | case 6: /* XXX: DECOM -- Origin */ 645 | break; 646 | case 7: /* DECAWM -- Auto wrap */ 647 | MODBIT(term.mode, set, MODE_WRAP); 648 | break; 649 | case 8: /* XXX: DECARM -- Auto repeat */ 650 | break; 651 | case 0: /* Error (IGNORED) */ 652 | case 12: /* att610 -- Start blinking cursor (IGNORED) */ 653 | break; 654 | case 25: 655 | MODBIT(term.c.state, !set, CURSOR_HIDE); 656 | break; 657 | case 1000: /* 1000,1002: enable xterm mouse report */ 658 | MODBIT(term.mode, set, MODE_MOUSEBTN); 659 | break; 660 | case 1002: 661 | MODBIT(term.mode, set, MODE_MOUSEMOTION); 662 | break; 663 | case 1049: /* = 1047 and 1048 */ 664 | // Mode 1049 combines screen switching AND cursor save/restore 665 | if (set) { 666 | // Save cursor position on primary screen, then switch to alternate screen 667 | if (!IS_SET(MODE_ALTSCREEN)) { 668 | t_cursor(CURSOR_SAVE); // Save on primary screen 669 | t_swap_screen(); // Switch to alternate 670 | t_move_to(0, 0); // Move to top-left on alternate screen 671 | } 672 | } else { 673 | // Switch back to primary screen, then restore cursor position 674 | if (IS_SET(MODE_ALTSCREEN)) { 675 | t_swap_screen(); // Switch back to primary 676 | t_cursor(CURSOR_LOAD); // Restore on primary screen 677 | } 678 | } 679 | break; 680 | case 47: 681 | case 1047: 682 | // Alternate screen buffer only (no cursor save/restore) 683 | if (set) { 684 | // Switch to alternate screen 685 | if (!IS_SET(MODE_ALTSCREEN)) { 686 | t_swap_screen(); 687 | } 688 | } else { 689 | // Switch back to primary screen 690 | if (IS_SET(MODE_ALTSCREEN)) { 691 | t_swap_screen(); 692 | } 693 | } 694 | break; 695 | case 1048: 696 | // Cursor save/restore only 697 | t_cursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 698 | break; 699 | case 2004: /* bracketed paste mode */ 700 | // MODBIT(term.mode, set, MODE_BRACKETPASTE); 701 | break; 702 | default: 703 | /* case 2: DECANM -- ANSI/VT52 (NOT SUPPOURTED) */ 704 | /* case 3: DECCOLM -- Column (NOT SUPPORTED) */ 705 | /* case 4: DECSCLM -- Scroll (NOT SUPPORTED) */ 706 | /* case 18: DECPFF -- Printer feed (NOT SUPPORTED) */ 707 | /* case 19: DECPEX -- Printer extent (NOT SUPPORTED) */ 708 | /* case 42: DECNRCM -- National characters (NOT SUPPORTED) */ 709 | fprintf(stderr, "erresc: unknown private set/reset mode %d\n", *args); 710 | break; 711 | } 712 | } else { 713 | switch (*args) { 714 | case 0: /* Error (IGNORED) */ 715 | break; 716 | case 2: /* KAM -- keyboard action */ 717 | MODBIT(term.mode, set, MODE_KBDLOCK); 718 | break; 719 | case 4: /* IRM -- Insertion-replacement */ 720 | MODBIT(term.mode, set, MODE_INSERT); 721 | break; 722 | case 12: /* XXX: SRM -- Send/Receive */ 723 | break; 724 | case 20: /* LNM -- Linefeed/new line */ 725 | MODBIT(term.mode, set, MODE_CRLF); 726 | break; 727 | default: 728 | fprintf(stderr, "erresc: unknown set/reset mode %d\n", *args); 729 | break; 730 | } 731 | } 732 | } 733 | } 734 | #undef MODBIT 735 | 736 | void csi_handle(void) { 737 | switch (csiescseq.mode) { 738 | case 't': /* Window manipulation */ 739 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Window-manipulation 740 | if (csiescseq.narg > 0) { 741 | int op = csiescseq.arg[0]; 742 | char buf[64]; 743 | switch (op) { 744 | case 18: // Report window size in pixels 745 | // Response: ESC [ 4 ; height ; width t 746 | snprintf(buf, sizeof(buf), "\033[4;%d;%dt", term.row * 16, term.col * 8); 747 | tty_write(buf, strlen(buf)); 748 | break; 749 | case 19: // Report window size in characters 750 | // Response: ESC [ 8 ; height ; width t 751 | snprintf(buf, sizeof(buf), "\033[8;%d;%dt", term.row, term.col); 752 | tty_write(buf, strlen(buf)); 753 | break; 754 | case 22: // Push window title to stack (ignore for now) 755 | case 23: // Pop window title from stack (ignore for now) 756 | default: 757 | // For other window ops, ignore silently 758 | break; 759 | } 760 | } 761 | break; 762 | default: 763 | unknown: 764 | fprintf(stderr, "erresc: unknown csi "); 765 | csi_dump(); 766 | /* die(""); */ 767 | break; 768 | case '@': /* ICH -- Insert blank char */ 769 | DEFAULT(csiescseq.arg[0], 1); 770 | t_insert_blank(csiescseq.arg[0]); 771 | break; 772 | case 'A': /* CUU -- Cursor Up */ 773 | case 'e': 774 | DEFAULT(csiescseq.arg[0], 1); 775 | t_move_to(term.c.x, term.c.y - csiescseq.arg[0]); 776 | break; 777 | case 'B': /* CUD -- Cursor Down */ 778 | DEFAULT(csiescseq.arg[0], 1); 779 | t_move_to(term.c.x, term.c.y + csiescseq.arg[0]); 780 | break; 781 | case 'c': /* DA -- Device Attributes */ 782 | if (csiescseq.arg[0] == 0) tty_write(VT102ID, sizeof(VT102ID) - 1); 783 | break; 784 | case 'C': /* CUF -- Cursor Forward */ 785 | case 'a': 786 | DEFAULT(csiescseq.arg[0], 1); 787 | t_move_to(term.c.x + csiescseq.arg[0], term.c.y); 788 | break; 789 | case 'D': /* CUB -- Cursor Backward */ 790 | DEFAULT(csiescseq.arg[0], 1); 791 | t_move_to(term.c.x - csiescseq.arg[0], term.c.y); 792 | break; 793 | case 'E': /* CNL -- Cursor Down and first col */ 794 | DEFAULT(csiescseq.arg[0], 1); 795 | t_move_to(0, term.c.y + csiescseq.arg[0]); 796 | break; 797 | case 'F': /* CPL -- Cursor Up and first col */ 798 | DEFAULT(csiescseq.arg[0], 1); 799 | t_move_to(0, term.c.y - csiescseq.arg[0]); 800 | break; 801 | case 'g': /* TBC -- Tabulation clear */ 802 | switch (csiescseq.arg[0]) { 803 | case 0: /* clear current tab stop */ 804 | term.tabs[term.c.x] = 0; 805 | break; 806 | case 3: /* clear all the tabs */ 807 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 808 | break; 809 | default: 810 | goto unknown; 811 | } 812 | break; 813 | case 'G': /* CHA -- Move to */ 814 | case '`': /* HPA */ 815 | DEFAULT(csiescseq.arg[0], 1); 816 | t_move_to(csiescseq.arg[0] - 1, term.c.y); 817 | break; 818 | case 'H': /* CUP -- Move to */ 819 | case 'f': /* HVP */ 820 | DEFAULT(csiescseq.arg[0], 1); 821 | DEFAULT(csiescseq.arg[1], 1); 822 | t_move_to(csiescseq.arg[1] - 1, csiescseq.arg[0] - 1); 823 | break; 824 | case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 825 | DEFAULT(csiescseq.arg[0], 1); 826 | while (csiescseq.arg[0]--) t_put_tab(1); 827 | break; 828 | case 'J': /* ED -- Clear screen */ 829 | switch (csiescseq.arg[0]) { 830 | case 0: /* below */ 831 | t_clear_region(term.c.x, term.c.y, term.col - 1, term.c.y); 832 | if (term.c.y < term.row - 1) t_clear_region(0, term.c.y + 1, term.col - 1, term.row - 1); 833 | break; 834 | case 1: /* above */ 835 | if (term.c.y > 1) t_clear_region(0, 0, term.col - 1, term.c.y - 1); 836 | t_clear_region(0, term.c.y, term.c.x, term.c.y); 837 | break; 838 | case 2: /* all */ 839 | t_clear_region(0, 0, term.col - 1, term.row - 1); 840 | break; 841 | default: 842 | goto unknown; 843 | } 844 | break; 845 | case 'K': /* EL -- Clear line */ 846 | switch (csiescseq.arg[0]) { 847 | case 0: /* right */ 848 | t_clear_region(term.c.x, term.c.y, term.col - 1, term.c.y); 849 | break; 850 | case 1: /* left */ 851 | t_clear_region(0, term.c.y, term.c.x, term.c.y); 852 | break; 853 | case 2: /* all */ 854 | t_clear_region(0, term.c.y, term.col - 1, term.c.y); 855 | break; 856 | } 857 | break; 858 | case 'S': /* SU -- Scroll line up */ 859 | DEFAULT(csiescseq.arg[0], 1); 860 | t_scroll_up(term.top, csiescseq.arg[0]); 861 | break; 862 | case 'T': /* SD -- Scroll line down */ 863 | DEFAULT(csiescseq.arg[0], 1); 864 | t_scroll_down(term.top, csiescseq.arg[0]); 865 | break; 866 | case 'L': /* IL -- Insert blank lines */ 867 | DEFAULT(csiescseq.arg[0], 1); 868 | t_insert_blank_line(csiescseq.arg[0]); 869 | break; 870 | case 'l': /* RM -- Reset Mode */ 871 | t_set_mode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 872 | break; 873 | case 'M': /* DL -- Delete lines */ 874 | DEFAULT(csiescseq.arg[0], 1); 875 | t_delete_line(csiescseq.arg[0]); 876 | break; 877 | case 'X': /* ECH -- Erase char */ 878 | DEFAULT(csiescseq.arg[0], 1); 879 | t_clear_region(term.c.x, term.c.y, term.c.x + csiescseq.arg[0], term.c.y); 880 | break; 881 | case 'P': /* DCH -- Delete char */ 882 | DEFAULT(csiescseq.arg[0], 1); 883 | t_delete_char(csiescseq.arg[0]); 884 | break; 885 | case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 886 | DEFAULT(csiescseq.arg[0], 1); 887 | while (csiescseq.arg[0]--) t_put_tab(0); 888 | break; 889 | case 'd': /* VPA -- Move to */ 890 | DEFAULT(csiescseq.arg[0], 1); 891 | t_move_to(term.c.x, csiescseq.arg[0] - 1); 892 | break; 893 | case 'h': /* SM -- Set terminal mode */ 894 | t_set_mode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 895 | break; 896 | case 'm': /* SGR -- Terminal attribute (color) */ 897 | if (csiescseq.buf[0] == '>') { 898 | // Handle private SGR sequences like ESC[>4;2m (bracketed paste mode queries) 899 | // These are usually capability queries that we can safely ignore 900 | break; 901 | } 902 | t_set_attr(csiescseq.arg, csiescseq.narg); 903 | break; 904 | case 'r': /* DECSTBM -- Set Scrolling Region */ 905 | if (csiescseq.priv) { 906 | goto unknown; 907 | } else { 908 | DEFAULT(csiescseq.arg[0], 1); 909 | DEFAULT(csiescseq.arg[1], term.row); 910 | t_set_scroll(csiescseq.arg[0] - 1, csiescseq.arg[1] - 1); 911 | t_move_to(0, 0); 912 | } 913 | break; 914 | case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 915 | t_cursor(CURSOR_SAVE); 916 | break; 917 | case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 918 | t_cursor(CURSOR_LOAD); 919 | break; 920 | } 921 | } 922 | 923 | void csi_dump(void) { 924 | int i; 925 | uint c; 926 | 927 | printf("ESC["); 928 | for (i = 0; i < csiescseq.len; i++) { 929 | c = csiescseq.buf[i] & 0xff; 930 | if (isprint(c)) { 931 | putchar(c); 932 | } else if (c == '\n') { 933 | printf("(\\n)"); 934 | } else if (c == '\r') { 935 | printf("(\\r)"); 936 | } else if (c == 0x1b) { 937 | printf("(\\e)"); 938 | } else { 939 | printf("(%02x)", c); 940 | } 941 | } 942 | putchar('\n'); 943 | } 944 | 945 | void csi_reset(void) { memset(&csiescseq, 0, sizeof(csiescseq)); } 946 | 947 | void str_reset(void) { memset(&strescseq, 0, sizeof(strescseq)); } 948 | 949 | void t_put_tab(bool forward) { 950 | uint x = term.c.x; 951 | 952 | if (forward) { 953 | if (x == term.col) return; 954 | for (++x; x < term.col && !term.tabs[x]; ++x) /* nothing */ 955 | ; 956 | } else { 957 | if (x == 0) return; 958 | for (--x; x > 0 && !term.tabs[x]; --x) /* nothing */ 959 | ; 960 | } 961 | t_move_to(x, term.c.y); 962 | } 963 | 964 | void t_putc(char *c, int len) { 965 | uchar ascii = *c; 966 | bool control = ascii < '\x20' || ascii == 0177; 967 | 968 | if (iofd != -1) { 969 | if (x_write(iofd, c, len) < 0) { 970 | fprintf(stderr, "Error writting in %s:%s\n", opt_io, strerror(errno)); 971 | close(iofd); 972 | iofd = -1; 973 | } 974 | } 975 | /* 976 | * STR sequences must be checked before of anything 977 | * because it can use some control codes as part of the sequence 978 | */ 979 | if (term.esc & ESC_STR) { 980 | switch (ascii) { 981 | case '\033': 982 | term.esc = ESC_START | ESC_STR_END; 983 | break; 984 | case '\a': /* backwards compatibility to xterm */ 985 | term.esc = 0; 986 | break; 987 | default: 988 | strescseq.buf[strescseq.len++] = ascii; 989 | if (strescseq.len + 1 >= STR_BUF_SIZ) { 990 | term.esc = 0; 991 | } 992 | } 993 | return; 994 | } 995 | /* 996 | * Actions of control codes must be performed as soon they arrive 997 | * because they can be embedded inside a control sequence, and 998 | * they must not cause conflicts with sequences. 999 | */ 1000 | if (control) { 1001 | switch (ascii) { 1002 | case '\t': /* HT */ 1003 | t_put_tab(1); 1004 | return; 1005 | case '\b': /* BS */ 1006 | t_move_to(term.c.x - 1, term.c.y); 1007 | return; 1008 | case '\r': /* CR */ 1009 | t_move_to(0, term.c.y); 1010 | return; 1011 | case '\f': /* LF */ 1012 | case '\v': /* VT */ 1013 | case '\n': /* LF */ 1014 | /* go to first col if the mode is set */ 1015 | t_newline(IS_SET(MODE_CRLF)); 1016 | return; 1017 | case '\a': /* BEL */ 1018 | return; 1019 | case '\033': /* ESC */ 1020 | csi_reset(); 1021 | term.esc = ESC_START; 1022 | return; 1023 | case '\016': /* SO */ 1024 | term.c.attr.mode |= ATTR_GFX; 1025 | return; 1026 | case '\017': /* SI */ 1027 | term.c.attr.mode &= ~ATTR_GFX; 1028 | return; 1029 | case '\032': /* SUB */ 1030 | case '\030': /* CAN */ 1031 | csi_reset(); 1032 | return; 1033 | case '\005': /* ENQ (IGNORED) */ 1034 | case '\000': /* NUL (IGNORED) */ 1035 | case '\021': /* XON (IGNORED) */ 1036 | case '\023': /* XOFF (IGNORED) */ 1037 | case 0177: /* DEL (IGNORED) */ 1038 | return; 1039 | } 1040 | } else if (term.esc & ESC_START) { 1041 | if (term.esc & ESC_CSI) { 1042 | csiescseq.buf[csiescseq.len++] = ascii; 1043 | if (BETWEEN(ascii, 0x40, 0x7E) || csiescseq.len >= ESC_BUF_SIZ) { 1044 | term.esc = 0; 1045 | csi_parse(), csi_handle(); 1046 | } 1047 | } else if (term.esc & ESC_STR_END) { 1048 | term.esc = 0; 1049 | } else if (term.esc & ESC_ALTCHARSET) { 1050 | switch (ascii) { 1051 | case '0': /* Line drawing set */ 1052 | term.c.attr.mode |= ATTR_GFX; 1053 | break; 1054 | case 'B': /* USASCII */ 1055 | term.c.attr.mode &= ~ATTR_GFX; 1056 | break; 1057 | case 'A': /* UK (IGNORED) */ 1058 | case '<': /* multinational charset (IGNORED) */ 1059 | case '5': /* Finnish (IGNORED) */ 1060 | case 'C': /* Finnish (IGNORED) */ 1061 | case 'K': /* German (IGNORED) */ 1062 | break; 1063 | default: 1064 | fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 1065 | } 1066 | term.esc = 0; 1067 | } else if (term.esc & ESC_TEST) { 1068 | if (ascii == '8') { /* DEC screen alignment test. */ 1069 | char E[UTF_SIZ] = "E"; 1070 | int x, y; 1071 | 1072 | for (x = 0; x < term.col; ++x) { 1073 | for (y = 0; y < term.row; ++y) t_set_char(E, &term.c.attr, x, y); 1074 | } 1075 | } 1076 | term.esc = 0; 1077 | } else { 1078 | switch (ascii) { 1079 | case '[': 1080 | term.esc |= ESC_CSI; 1081 | break; 1082 | case '#': 1083 | term.esc |= ESC_TEST; 1084 | break; 1085 | case 'P': /* DCS -- Device Control String */ 1086 | case '_': /* APC -- Application Program Command */ 1087 | case '^': /* PM -- Privacy Message */ 1088 | case ']': /* OSC -- Operating System Command */ 1089 | case 'k': /* old title set compatibility */ 1090 | str_reset(); 1091 | strescseq.type = ascii; 1092 | term.esc |= ESC_STR; 1093 | break; 1094 | case '(': /* set primary charset G0 */ 1095 | term.esc |= ESC_ALTCHARSET; 1096 | break; 1097 | case ')': /* set secondary charset G1 (IGNORED) */ 1098 | case '*': /* set tertiary charset G2 (IGNORED) */ 1099 | case '+': /* set quaternary charset G3 (IGNORED) */ 1100 | term.esc = 0; 1101 | break; 1102 | case 'D': /* IND -- Linefeed */ 1103 | if (term.c.y == term.bot) { 1104 | t_scroll_up(term.top, 1); 1105 | } else { 1106 | t_move_to(term.c.x, term.c.y + 1); 1107 | } 1108 | term.esc = 0; 1109 | break; 1110 | case 'E': /* NEL -- Next line */ 1111 | t_newline(1); /* always go to first col */ 1112 | term.esc = 0; 1113 | break; 1114 | case 'H': /* HTS -- Horizontal tab stop */ 1115 | term.tabs[term.c.x] = 1; 1116 | term.esc = 0; 1117 | break; 1118 | case 'M': /* RI -- Reverse index */ 1119 | if (term.c.y == term.top) { 1120 | t_scroll_down(term.top, 1); 1121 | } else { 1122 | t_move_to(term.c.x, term.c.y - 1); 1123 | } 1124 | term.esc = 0; 1125 | break; 1126 | case 'Z': /* DECID -- Identify Terminal */ 1127 | tty_write(VT102ID, sizeof(VT102ID) - 1); 1128 | term.esc = 0; 1129 | break; 1130 | case 'c': /* RIS -- Reset to inital state */ 1131 | t_reset(); 1132 | term.esc = 0; 1133 | break; 1134 | case '=': /* DECPAM -- Application keypad */ 1135 | term.mode |= MODE_APPKEYPAD; 1136 | term.esc = 0; 1137 | break; 1138 | case '>': /* DECPNM -- Normal keypad */ 1139 | term.mode &= ~MODE_APPKEYPAD; 1140 | term.esc = 0; 1141 | break; 1142 | case '7': /* DECSC -- Save Cursor */ 1143 | t_cursor(CURSOR_SAVE); 1144 | term.esc = 0; 1145 | break; 1146 | case '8': /* DECRC -- Restore Cursor */ 1147 | t_cursor(CURSOR_LOAD); 1148 | term.esc = 0; 1149 | break; 1150 | case '\\': /* ST -- Stop */ 1151 | term.esc = 0; 1152 | break; 1153 | default: 1154 | fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar)ascii, isprint(ascii) ? ascii : '.'); 1155 | term.esc = 0; 1156 | } 1157 | } 1158 | /* 1159 | * All characters which forms part of a sequence are not 1160 | * printed 1161 | */ 1162 | return; 1163 | } 1164 | /* 1165 | * Display control codes only if we are in graphic mode 1166 | */ 1167 | if (control && !(term.c.attr.mode & ATTR_GFX)) return; 1168 | if (IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT) t_newline(1); /* always go to first col */ 1169 | t_set_char(c, &term.c.attr, term.c.x, term.c.y); 1170 | if (term.c.x + 1 < term.col) 1171 | t_move_to(term.c.x + 1, term.c.y); 1172 | else 1173 | term.c.state |= CURSOR_WRAPNEXT; 1174 | } 1175 | 1176 | int t_resize(int col, int row) { 1177 | int i, x; 1178 | int minrow = MIN(row, term.row); 1179 | int mincol = MIN(col, term.col); 1180 | int slide = term.c.y - row + 1; 1181 | bool *bp; 1182 | 1183 | if (col < 1 || row < 1) return 0; 1184 | 1185 | /* free unneeded rows */ 1186 | i = 0; 1187 | if (slide > 0) { 1188 | /* slide screen to keep cursor where we expect it - 1189 | * tscrollup would work here, but we can optimize to 1190 | * memmove because we're freeing the earlier lines */ 1191 | for (/* i = 0 */; i < slide; i++) { 1192 | free(term.line[i]); 1193 | free(term.alt[i]); 1194 | } 1195 | memmove(term.line, term.line + slide, row * sizeof(Line)); 1196 | memmove(term.alt, term.alt + slide, row * sizeof(Line)); 1197 | } 1198 | for (i += row; i < term.row; i++) { 1199 | free(term.line[i]); 1200 | free(term.alt[i]); 1201 | } 1202 | 1203 | /* resize to new height */ 1204 | term.line = x_realloc(term.line, row * sizeof(Line)); 1205 | term.alt = x_realloc(term.alt, row * sizeof(Line)); 1206 | term.dirty = x_realloc(term.dirty, row * sizeof(*term.dirty)); 1207 | term.tabs = x_realloc(term.tabs, col * sizeof(*term.tabs)); 1208 | 1209 | /* resize each row to new width, zero-pad if needed */ 1210 | for (i = 0; i < minrow; i++) { 1211 | term.dirty[i] = 1; 1212 | term.line[i] = x_realloc(term.line[i], col * sizeof(Glyph)); 1213 | term.alt[i] = x_realloc(term.alt[i], col * sizeof(Glyph)); 1214 | for (x = mincol; x < col; x++) { 1215 | term.line[i][x].state = 0; 1216 | term.alt[i][x].state = 0; 1217 | } 1218 | } 1219 | 1220 | /* allocate any new rows */ 1221 | for (/* i == minrow */; i < row; i++) { 1222 | term.dirty[i] = 1; 1223 | term.line[i] = x_calloc(col, sizeof(Glyph)); 1224 | term.alt[i] = x_calloc(col, sizeof(Glyph)); 1225 | } 1226 | if (col > term.col) { 1227 | bp = term.tabs + term.col; 1228 | 1229 | memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 1230 | while (--bp > term.tabs && !*bp) /* nothing */ 1231 | ; 1232 | for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) *bp = 1; 1233 | } 1234 | /* update terminal size */ 1235 | term.col = col; 1236 | term.row = row; 1237 | /* make use of the LIMIT in t_move_to */ 1238 | t_move_to(term.c.x, term.c.y); 1239 | /* reset scrolling region */ 1240 | t_set_scroll(0, row - 1); 1241 | 1242 | return (slide > 0); 1243 | } --------------------------------------------------------------------------------