├── dump.h ├── misc-fixed-6x10.mbf ├── .gitignore ├── gif.h ├── mbf.h ├── dump.c ├── Makefile ├── mbf2c.c ├── term.h ├── mbf.c ├── cs_437.h ├── cs_vtg.h ├── default.h ├── README ├── congif.1 ├── gen-maps.lua ├── gif.c ├── main.c └── term.c /dump.h: -------------------------------------------------------------------------------- 1 | void dump_txt(Term *term, const char *fname); 2 | -------------------------------------------------------------------------------- /misc-fixed-6x10.mbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lecram/congif/HEAD/misc-fixed-6x10.mbf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | congif 2 | a.out 3 | *.o 4 | *.gif 5 | *.t 6 | *.d 7 | *log 8 | default_font.h 9 | mbf2c 10 | -------------------------------------------------------------------------------- /gif.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct GIF { 4 | uint16_t w, h; 5 | int fd; 6 | int offset; 7 | uint8_t *cur, *old, *plt; 8 | uint32_t partial; 9 | uint8_t plt_dirty; 10 | uint8_t buffer[0xFF]; 11 | } GIF; 12 | 13 | GIF *new_gif(const char *fname, uint16_t w, uint16_t h, uint8_t *gct, int loop); 14 | void add_frame(GIF *gif, uint16_t d); 15 | void close_gif(GIF* gif); 16 | -------------------------------------------------------------------------------- /mbf.h: -------------------------------------------------------------------------------- 1 | typedef struct Header { 2 | uint16_t ng; 3 | uint8_t w, h; 4 | uint16_t nr; 5 | } Header; 6 | 7 | typedef struct Range { 8 | uint16_t offset, length; 9 | } Range; 10 | 11 | typedef struct Font { 12 | Header header; 13 | int stride; 14 | Range *ranges; 15 | uint8_t *data; 16 | } Font; 17 | 18 | Font *load_font(const char *fname); 19 | int search_glyph(Font *font, uint16_t code); 20 | int get_index(Font *font, uint16_t code); 21 | -------------------------------------------------------------------------------- /dump.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "term.h" 8 | 9 | void 10 | dump_txt(Term *term, const char *fname) 11 | { 12 | int i, j, fd; 13 | uint16_t code; 14 | char ch; 15 | 16 | fd = creat(fname, 0666); 17 | if (fd == -1) 18 | return; 19 | for (i = 0; i < term->rows; i++) { 20 | for (j = 0; j < term->cols; j++) { 21 | code = term->addr[i][j].code; 22 | if (code >= 0x20 && code < 0x7F) 23 | ch = (char) code; 24 | else 25 | ch = 0x20; 26 | write(fd, &ch, 1); 27 | } 28 | write(fd, "\n", 1); 29 | } 30 | close(fd); 31 | } 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | MANPREFIX=$(PREFIX)/man 3 | BINDIR=$(DESTDIR)$(PREFIX)/bin 4 | MANDIR=$(DESTDIR)$(MANPREFIX)/man1 5 | DEFAULT_FONT=misc-fixed-6x10.mbf 6 | 7 | HDR = term.h mbf.h gif.h 8 | SRC = ${HDR:.h=.c} 9 | EHDR = default.h cs_vtg.h cs_437.h default_font.h 10 | ESRC = main.c 11 | 12 | all: congif 13 | 14 | congif: $(HDR) $(EHDR) $(SRC) $(ESRC) 15 | $(CC) $(CFLAGS) -o $@ $(SRC) $(ESRC) 16 | 17 | default_font.h: $(DEFAULT_FONT) mbf.h mbf.c mbf2c.c 18 | $(CC) $(CFLAGS) -o mbf2c mbf.c mbf2c.c 19 | ./mbf2c $(DEFAULT_FONT) > fnt.tmp 20 | mv fnt.tmp $@ 21 | 22 | install: congif 23 | rm -f $(BINDIR)/congif 24 | mkdir -p $(BINDIR) 25 | cp congif $(BINDIR)/congif 26 | mkdir -p $(MANDIR) 27 | cp congif.1 $(MANDIR)/congif.1 28 | 29 | uninstall: $(BINDIR)/congif 30 | rm $(BINDIR)/congif 31 | rm $(MANDIR)/congif.1 32 | clean: 33 | $(RM) congif default_font.h mbf2c fnt.tmp 34 | -------------------------------------------------------------------------------- /mbf2c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mbf.h" 8 | 9 | #define MAX_NAME 0x10 10 | #define FN "default_font" 11 | 12 | int 13 | main(int argc, char *argv[]) 14 | { 15 | Font *font; 16 | int i; 17 | 18 | if (argc != 2) { 19 | fprintf(stderr, "Usage: %s font.mbf\n", argv[0]); 20 | return 1; 21 | } 22 | font = load_font(argv[1]); 23 | if (font == NULL) { 24 | fprintf(stderr, "Failed to load font '%s'.\n", argv[1]); 25 | return 1; 26 | } 27 | 28 | printf("static Range "FN"_ranges[%d] = {\n", font->header.nr); 29 | for(i=0; iheader.nr; i++) { 30 | printf(" { %d, %d }%s\n", 31 | (int) font->ranges[i].offset, 32 | (int) font->ranges[i].length, 33 | i+1 == font->header.nr?"":","); 34 | } 35 | printf("};\n\n"); 36 | 37 | printf("static uint8_t "FN"_data[] = {\n"); 38 | for (i = 0; i < font->header.ng * font->stride * font->header.h; i++) { 39 | if (i%12 == 0) { 40 | if (i) printf(",\n"); 41 | printf(" "); 42 | } else 43 | printf(", "); 44 | printf("0x%02x", font->data[i]); 45 | } 46 | printf("\n};\n\n"); 47 | 48 | printf("Font "FN"[1] = {{ "); 49 | 50 | printf("{ %d, %d, %d, %d }, ", 51 | (int) font->header.ng, 52 | (int) font->header.w, 53 | (int) font->header.h, 54 | (int) font->header.nr); 55 | 56 | printf("%d, "FN"_ranges, "FN"_data }};\n", 57 | (int) font->stride); 58 | 59 | free(font); 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /term.h: -------------------------------------------------------------------------------- 1 | #define A_NORMAL 0x00 2 | #define A_BOLD 0x01 3 | #define A_DIM 0x02 4 | #define A_ITALIC 0x04 5 | #define A_UNDERLINE 0x08 6 | #define A_BLINK 0x10 7 | #define A_INVERSE 0x20 8 | #define A_INVISIBLE 0x40 9 | #define A_CROSSED 0x80 10 | 11 | #define M_DISPCTRL 0x0001 12 | #define M_INSERT 0x0002 13 | #define M_NEWLINE 0x0004 14 | #define M_CURSORKEY 0x0008 15 | #define M_WIDETERM 0x0010 16 | #define M_REVERSE 0x0020 17 | #define M_ORIGIN 0x0040 18 | #define M_AUTOWRAP 0x0080 19 | #define M_AUTORPT 0x0100 20 | #define M_MOUSEX10 0x0200 21 | #define M_CURSORVIS 0x0400 22 | #define M_MOUSEX11 0x0800 23 | #define M_ISOLAT1 0x1000 24 | 25 | #define EMPTY 0x0020 26 | #define BCE 1 27 | #if !BCE 28 | #define BLANK (Cell) {EMPTY, def_attr, def_pair} 29 | #else 30 | #define BLANK (Cell) {EMPTY, term->attr, term->pair} 31 | #endif 32 | 33 | #define MAX_PARTIAL 0x100 34 | #define MAX_PARAMS 0x10 35 | 36 | typedef struct Cell { 37 | uint16_t code; 38 | uint8_t attr; 39 | uint8_t pair; 40 | } Cell; 41 | 42 | typedef enum CharSet {CS_BMP, CS_ISO, CS_VTG, CS_437} CharSet; 43 | typedef enum State {S_ANY, S_ESC, S_CSI, S_OSC, S_OSCESC, S_STR, S_STRESC, S_UNI} State; 44 | 45 | typedef struct SaveCursor { 46 | int row, col; 47 | } SaveCursor; 48 | 49 | typedef struct SaveMisc { 50 | int row, col; 51 | int origin_on; 52 | uint8_t attr; 53 | uint8_t pair; 54 | CharSet cs_array[2]; 55 | int cs_index; 56 | } SaveMisc; 57 | 58 | typedef struct Term { 59 | int rows, cols; 60 | int row, col; 61 | int top, bot; 62 | uint16_t mode; 63 | uint8_t attr; 64 | uint8_t pair; 65 | Cell **addr; 66 | Cell *cells; 67 | CharSet cs_array[2]; 68 | int cs_index; 69 | SaveCursor save_cursor; 70 | SaveMisc save_misc; 71 | State state; 72 | int parlen; 73 | int unilen; 74 | uint8_t partial[MAX_PARTIAL]; 75 | uint8_t plt[0x30]; 76 | uint8_t plt_local, plt_dirty; 77 | } Term; 78 | 79 | void set_verbosity(int level); 80 | Term *new_term(int rows, int cols); 81 | void parse(Term *term, uint8_t byte); 82 | void set_default_palette(char * optarg); 83 | -------------------------------------------------------------------------------- /mbf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "mbf.h" 11 | 12 | Font * 13 | load_font(const char *fname) 14 | { 15 | int fd; 16 | char sig[4]; 17 | Header header; 18 | int stride; 19 | size_t ranges_size, data_size; 20 | Font *font; 21 | 22 | fd = open(fname, O_RDONLY); 23 | if (fd == -1) 24 | return NULL; 25 | read(fd, sig, sizeof(sig)); 26 | if (memcmp(sig, (char []) {'M', 'B', 'F', 0x01}, sizeof(sig))) { 27 | close(fd); 28 | return NULL; 29 | } 30 | read(fd, &header, sizeof(header)); 31 | /* stride = ceil(w / 8) = floor(w / 8) + (w % 8 ? 1 : 0) */ 32 | stride = (header.w >> 3) + !!(header.w & 7); 33 | ranges_size = header.nr * sizeof(Range); 34 | data_size = header.ng * stride * header.h; 35 | font = malloc(sizeof(Font) + ranges_size + data_size); 36 | if (!font) { 37 | close(fd); 38 | return NULL; 39 | } 40 | font->stride = stride; 41 | font->header = header; 42 | font->ranges = (Range *) &font[1]; 43 | read(fd, font->ranges, ranges_size); 44 | font->data = (uint8_t *) &font->ranges[header.nr]; 45 | read(fd, font->data, data_size); 46 | close(fd); 47 | return font; 48 | } 49 | 50 | int 51 | search_glyph(Font *font, uint16_t code) 52 | { 53 | int index, i; 54 | Range r; 55 | 56 | index = 0; 57 | for (i = 0; i < font->header.nr; i++) { 58 | r = font->ranges[i]; 59 | if (code < r.offset) 60 | return -1; 61 | if (code < r.offset + r.length) 62 | return index + code - r.offset; 63 | index += r.length; 64 | } 65 | return -1; 66 | } 67 | 68 | int 69 | get_index(Font *font, uint16_t code) 70 | { 71 | int index; 72 | uint16_t *cur_code; 73 | uint16_t codes[] = {code, 0xFFFD, 0x003F, 0x0020, 0}; 74 | 75 | for (cur_code = &codes[0]; *cur_code; cur_code++) { 76 | index = search_glyph(font, *cur_code); 77 | if (index != -1) 78 | break; 79 | }; 80 | return index; 81 | } 82 | -------------------------------------------------------------------------------- /cs_437.h: -------------------------------------------------------------------------------- 1 | static uint16_t cs_437[0x100] = { 2 | 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 3 | 0x25D8, 0x2218, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, 4 | 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8, 5 | 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC, 6 | 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 7 | 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 8 | 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 9 | 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 10 | 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 11 | 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 12 | 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 13 | 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 14 | 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 15 | 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 16 | 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 17 | 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302, 18 | 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 19 | 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 20 | 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 21 | 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, 22 | 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 23 | 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 24 | 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 25 | 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, 26 | 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 27 | 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, 28 | 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 29 | 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, 30 | 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 31 | 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, 32 | 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 33 | 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, 34 | }; 35 | -------------------------------------------------------------------------------- /cs_vtg.h: -------------------------------------------------------------------------------- 1 | static uint16_t cs_vtg[0x100] = { 2 | 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 3 | 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 4 | 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 5 | 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 6 | 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 7 | 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F, 8 | 0x25AE, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 9 | 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 10 | 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 11 | 0x0048, 0x2603, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 12 | 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 13 | 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0, 14 | 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1, 15 | 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA, 16 | 0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C, 17 | 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F, 18 | 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 19 | 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, 20 | 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 21 | 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, 22 | 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 23 | 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 24 | 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 25 | 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 26 | 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 27 | 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 28 | 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 29 | 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 30 | 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 31 | 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 32 | 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 33 | 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 34 | }; 35 | -------------------------------------------------------------------------------- /default.h: -------------------------------------------------------------------------------- 1 | static uint8_t plt_xterm[0x30] = { 2 | 0x00, 0x00, 0x00, 3 | 0xCD, 0x00, 0x00, 4 | 0x00, 0xCD, 0x00, 5 | 0xCD, 0xCD, 0x00, 6 | 0x00, 0x00, 0xEE, 7 | 0xCD, 0x00, 0xCD, 8 | 0x00, 0xCD, 0xCD, 9 | 0xE5, 0xE5, 0xE5, 10 | 0x7F, 0x7F, 0x7F, 11 | 0xFF, 0x00, 0x00, 12 | 0x00, 0xFF, 0x00, 13 | 0xFF, 0xFF, 0x00, 14 | 0x5C, 0x5C, 0xFF, 15 | 0xFF, 0x00, 0xFF, 16 | 0x00, 0xFF, 0xFF, 17 | 0xFF, 0xFF, 0xFF 18 | }; 19 | 20 | static uint8_t plt_vga_ansi[0x30] = { 21 | 0x00, 0x00, 0x00, 22 | 0xAA, 0x00, 0x00, 23 | 0x00, 0xAA, 0x00, 24 | 0xAA, 0x55, 0x00, 25 | 0x00, 0x00, 0xAA, 26 | 0xAA, 0x00, 0xAA, 27 | 0x00, 0xAA, 0xAA, 28 | 0xAA, 0xAA, 0xAA, 29 | 0x55, 0x55, 0x55, 30 | 0xFF, 0x55, 0x55, 31 | 0x55, 0xFF, 0x55, 32 | 0xFF, 0xFF, 0x55, 33 | 0x55, 0x55, 0xFF, 34 | 0xFF, 0x55, 0xFF, 35 | 0x55, 0xFF, 0xFF, 36 | 0xFF, 0xFF, 0xFF 37 | }; 38 | 39 | static uint8_t plt_solarized_like_ansi[0x30] = { 40 | 0x07, 0x36, 0x42, 41 | 0xdc, 0x32, 0x2f, 42 | 0x32, 0x99, 0x00, 43 | 0xb5, 0x89, 0x00, 44 | 0x26, 0x5e, 0xd2, 45 | 0xd3, 0x36, 0x82, 46 | 0x26, 0x96, 0x82, 47 | 0x93, 0xa1, 0xa1, 48 | 0x34, 0x63, 0x6f, 49 | 0xff, 0x5f, 0x5c, 50 | 0x5f, 0xe2, 0x2d, 51 | 0xe2, 0xb6, 0x2d, 52 | 0x26, 0x8b, 0xff, 53 | 0xff, 0x63, 0xaf, 54 | 0x53, 0xc3, 0xaf, 55 | 0xc0, 0xce, 0xce 56 | }; 57 | 58 | static uint8_t plt_solarized[0x30] = { 59 | 0x07, 0x36, 0x42, 60 | 0xdc, 0x32, 0x2f, 61 | 0x85, 0x99, 0x00, 62 | 0xb5, 0x89, 0x00, 63 | 0x26, 0x8b, 0xd2, 64 | 0xd3, 0x36, 0x82, 65 | 0x2a, 0xa1, 0x98, 66 | 0xee, 0xe8, 0xd5, 67 | 0x00, 0x2b, 0x36, 68 | 0xcb, 0x4b, 0x16, 69 | 0x58, 0x6e, 0x75, 70 | 0x65, 0x7b, 0x83, 71 | 0x83, 0x94, 0x96, 72 | 0x6c, 0x71, 0xc4, 73 | 0x93, 0xa1, 0xa1, 74 | 0xfd, 0xf6, 0xe3 75 | }; 76 | 77 | static uint8_t plt_putty[0x30] = { 78 | 0x00, 0x00, 0x00, 79 | 0xbb, 0x00, 0x00, 80 | 0x00, 0xbb, 0x00, 81 | 0xbb, 0xbb, 0x00, 82 | 0x00, 0x00, 0xbb, 83 | 0xbb, 0x00, 0xbb, 84 | 0x00, 0xbb, 0xbb, 85 | 0xbb, 0xbb, 0xbb, 86 | 0x55, 0x55, 0x55, 87 | 0xff, 0x55, 0x55, 88 | 0x55, 0xff, 0x55, 89 | 0xff, 0xff, 0x55, 90 | 0x55, 0x55, 0xff, 91 | 0xff, 0x55, 0xff, 92 | 0x55, 0xff, 0xff, 93 | 0xff, 0xff, 0xff 94 | }; 95 | 96 | #define DEF_FORE 0x7 97 | #define DEF_BACK 0x0 98 | 99 | static uint8_t * def_plt = plt_xterm; 100 | static uint16_t def_mode = M_AUTOWRAP | M_AUTORPT | M_CURSORVIS; 101 | static uint8_t def_attr = A_NORMAL; 102 | static uint8_t def_pair = (DEF_FORE << 4) | DEF_BACK; 103 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | congif 2 | ====== 3 | 4 | This is an experimental tool that generates GIF animations of console 5 | sessions. Like scriptreplay(1), it reads the output of script(1), 6 | including timing information. Unlike scriptreplay(1), congif parses the 7 | session dialogue and encodes it as a GIF animation that can be viewed on 8 | graphical programs (e.g. web browsers). 9 | 10 | 11 | Limitations 12 | ----------- 13 | 14 | Only dialogues generated for the Linux console can be correctly parsed 15 | by congif. This means that if your $TERM environment variable is not 16 | "linux" then it probably won't work. You can manually set $TERM when 17 | calling script(1), but then your terminal may not work correctly while 18 | the session is recorded. It's recommended that you record your sessions 19 | on the Linux console itself, or on a terminal emulator that is 20 | compatible with console_codes(4). 21 | 22 | congif needs to know the terminal size used during the execution of 23 | script(1) and this size must be constant per session (i.e. if you resize 24 | the terminal while recording a session, congif won't work). By default, 25 | congif assumes that the terminal size of the session is equal to the 26 | current terminal size. Use the options -w and -h if you need to specify 27 | a different size. 28 | 29 | 30 | Building 31 | -------- 32 | 33 | A C99 compiler is needed. There are no library dependencies. 34 | 35 | $ make 36 | 37 | 38 | Usage 39 | ----- 40 | 41 | congif [options] timings dialogue 42 | 43 | timings: File generated by script(1)'s -t option 44 | dialogue: File generated by script(1)'s regular output 45 | 46 | options: 47 | -o output File name of GIF output 48 | -m maxdelay Maximum delay, as in scriptreplay(1) 49 | -d divisor Speedup, as in scriptreplay(1) 50 | -l count GIF loop count (0 = infinite loop) 51 | -f font File name of MBF font to use 52 | -h lines Terminal height 53 | -w columns Terminal width 54 | -c on|off Show/hide cursor 55 | -q Quiet mode (don't show progress bar) 56 | -v Verbose mode (show parser logs) 57 | 58 | 59 | Fonts 60 | ----- 61 | 62 | For simplicity, congif uses a custom font format, MBF. The font 63 | misc-fixed [1], included in this distribution, is used by default. 64 | 65 | For more information on the MBF format, see the mbf-util [2] 66 | project. It includes a bdf2mbf tool that converts fonts from the 67 | popular BDF format to the MBF format. 68 | 69 | 70 | Examples 71 | -------- 72 | 73 | Recording a session: 74 | $ script -t 2> foo.t foo.d 75 | 76 | Generating a GIF file with defaults (creates "con.gif"): 77 | $ congif foo.t foo.d 78 | 79 | Generating a faster version: 80 | $ congif -d3 -m1 -o fast.gif foo.t foo.d 81 | 82 | 83 | Copying 84 | ------- 85 | 86 | All of the source code and documentation for congif is released into the 87 | public domain and provided without warranty of any kind. 88 | 89 | 90 | Links 91 | ----- 92 | 93 | [1] http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html 94 | [2] https://github.com/lecram/mbf-util 95 | -------------------------------------------------------------------------------- /congif.1: -------------------------------------------------------------------------------- 1 | .TH CONGIF 1 2 | .SH NAME 3 | congif \- convert \fBscript(1)\fR output to GIF 4 | .SH SYNOPSIS 5 | .B congif 6 | [options] \fItimings\fR \fIdialogue\fR 7 | .br 8 | .SH DESCRIPTION 9 | \fBcongif\fR is an experimental tool that generates GIF animations of console 10 | sessions. Like \fBscriptreplay(1)\fR, it reads the output of \fBscript(1)\fR, 11 | including timing information. Unlike \fBscriptreplay(1)\fR, \fBcongif\fR parses 12 | the dialogue and encodes it as a GIF animation. 13 | .PP 14 | \fItimings\fR is the path to a file generated by \fBscript(1)\fR's \fB\-t\fR 15 | option. 16 | .PP 17 | \fIdialogue\fR is the path to a file generated by \fBscript(1)\fR's regular 18 | output (also known as \fItypescript\fR). 19 | .SH OPTIONS 20 | .TP 21 | \fB\-o\fR \fIoutput\fR 22 | set the file name of the resulting GIF. 23 | .PP 24 | The default is \fIcon.gif\fR. 25 | .TP 26 | \fB\-m\fR \fImaxdelay\fR 27 | set the maximum delay (in seconds), as in \fBscriptreplay(1)\fR 28 | .PP 29 | When this option is specified, the delay of each frame will be limited to 30 | \fImaxdelay\fR. This option is applied before \fB\-d\fR when both are specified. 31 | .TP 32 | \fB\-d\fR \fIdivisor\fR 33 | set the delay divisor, as in \fBscriptreplay(1)\fR 34 | .PP 35 | When this option is specified, each frame delay will be divided by \fIdivisor\fR, 36 | resulting in a sped-up animation. For instance, \fB\-d 2\fR will make a GIF that 37 | is twice as fast as the real session recorded. 38 | .TP 39 | \fB\-l\fR \fIcount\fR 40 | make a looping animation 41 | .PP 42 | The resulting GIF will instruct viewers to display the animation \fIcount\fR 43 | times. The special \fIcount\fR value of \fB0\fR will make the GIF loop forever. 44 | .PP 45 | When this option is not specified, \fBcongif\fR will not add looping information 46 | to the GIF file. For most GIF viewing programs, this is equivalent to 47 | \fB\-l 1\fR. 48 | .TP 49 | \fB\-f\fR \fIfont\fR 50 | select the bitmap font to be used in the output 51 | .PP 52 | \fIfont\fR must be the path to a MBFv1 bitmap font. \fBcongif\fR comes with a 53 | default font that will be used when this option is not given. 54 | .TP 55 | \fB\-h\fR \fIlines\fR \fB\-w\fR \fIcolumns\fR 56 | set the terminal size of the session to \fIlines\fR and \fIcolumns\fR 57 | .PP 58 | \fBcongif\fR needs to know the size of the terminal on which the session was 59 | recorded. By default, it uses the current size of the terminal on which 60 | \fBcongif\fR is being executed. 61 | .TP 62 | \fB\-c\fR \fIswitch\fR 63 | specify whether to show or hide the cursor in the animation 64 | .PP 65 | If \fIswitch\fR is \fB0\fR or \fBoff\fR, the cursor will be hidden during the 66 | entire animation. If \fIswitch\fR is \fB1\fR or \fBon\fR, the cursor will be 67 | shown as in the recorded session (this is the default). 68 | .PP 69 | Note that most terminal programs can hide or show the cursor themselves 70 | appropriately. This option is only necessary to deal with programs that do not 71 | hide the cursor when they should, as is the case in programs targetting old 72 | terminals that might not have cursor hiding capabilities. 73 | .TP 74 | \fB\-q\fR 75 | set quiet mode 76 | .PP 77 | The progress bar will not be shown. 78 | .TP 79 | \fB\-v\fR 80 | set verbose mode 81 | .PP 82 | The dialogue parser will write logs to stderr. 83 | .SH BUGS 84 | \fBcongif\fR can only parse dialogues recorded in the Linux console or any other 85 | terminal emulator that is compatible with \fBconsole_codes(4)\fR. 86 | .PP 87 | It is not possible to automatically determine the size of the terminal where the 88 | session was recorded. If the correct size is not given via \fB\-h\fR and 89 | \fB\-w\fR, \fBcongif\fR will not be able to generate an accurate animation. 90 | .SH SEE ALSO 91 | \fBscript(1)\fR, \fBscriptreplay(1)\fR, \fBconsole_codes(4)\fR 92 | -------------------------------------------------------------------------------- /gen-maps.lua: -------------------------------------------------------------------------------- 1 | local function build_vtg() 2 | -- VT100 Graphics 3 | local vtg_kv = { 4 | {0x2B, 0x2192}, {0x2C, 0x2190}, {0x2D, 0x2191}, {0x2E, 0x2193}, 5 | {0x30, 0x25AE}, {0x49, 0x2603}, {0x60, 0x25C6}, {0x61, 0x2592}, 6 | {0x66, 0x00B0}, {0x67, 0x00B1}, {0x68, 0x2591}, {0x6A, 0x2518}, 7 | {0x6B, 0x2510}, {0x6C, 0x250C}, {0x6D, 0x2514}, {0x6E, 0x253C}, 8 | {0x6F, 0x23BA}, {0x70, 0x23BB}, {0x71, 0x2500}, {0x72, 0x23BC}, 9 | {0x73, 0x23BD}, {0x74, 0x251C}, {0x75, 0x2524}, {0x76, 0x2534}, 10 | {0x77, 0x252C}, {0x78, 0x2502}, {0x79, 0x2264}, {0x7A, 0x2265}, 11 | {0x7B, 0x03C0}, {0x7C, 0x2260}, {0x7D, 0x00A3}, {0x7E, 0x00B7}, 12 | 13 | {0x5F, 0x00A0}, {0x62, 0x2409}, {0x63, 0x240c}, {0x64, 0x240d}, 14 | {0x65, 0x240a}, {0x69, 0x240b}, 15 | } 16 | local vtg = {} 17 | for i, kv in ipairs(vtg_kv) do 18 | local k, v = unpack(kv) 19 | vtg[k] = v 20 | end 21 | return vtg 22 | end 23 | 24 | local function build_ibm() 25 | -- Code Page 437 26 | local ibm_kv = { 27 | {0x01, 0x263A}, {0x02, 0x263B}, {0x03, 0x2665}, {0x04, 0x2666}, 28 | {0x05, 0x2663}, {0x06, 0x2660}, {0x07, 0x2022}, {0x08, 0x25D8}, 29 | {0x09, 0x2218}, {0x0A, 0x25D9}, {0x0B, 0x2642}, {0x0C, 0x2640}, 30 | {0x0D, 0x266A}, {0x0E, 0x266B}, {0x0F, 0x263C}, {0x10, 0x25BA}, 31 | {0x11, 0x25C4}, {0x12, 0x2195}, {0x13, 0x203C}, {0x14, 0x00B6}, 32 | {0x15, 0x00A7}, {0x16, 0x25AC}, {0x17, 0x21A8}, {0x18, 0x2191}, 33 | {0x19, 0x2193}, {0x1A, 0x2192}, {0x1B, 0x2190}, {0x1C, 0x221F}, 34 | {0x1D, 0x2194}, {0x1E, 0x25B2}, {0x1F, 0x25BC}, {0x7F, 0x2302}, 35 | 36 | {0x80, 0x00C7}, {0x81, 0x00FC}, {0x82, 0x00E9}, {0x83, 0x00E2}, 37 | {0x84, 0x00E4}, {0x85, 0x00E0}, {0x86, 0x00E5}, {0x87, 0x00E7}, 38 | {0x88, 0x00EA}, {0x89, 0x00EB}, {0x8A, 0x00E8}, {0x8B, 0x00EF}, 39 | {0x8C, 0x00EE}, {0x8D, 0x00EC}, {0x8E, 0x00C4}, {0x8F, 0x00C5}, 40 | {0x90, 0x00C9}, {0x91, 0x00E6}, {0x92, 0x00C6}, {0x93, 0x00F4}, 41 | {0x94, 0x00F6}, {0x95, 0x00F2}, {0x96, 0x00FB}, {0x97, 0x00F9}, 42 | {0x98, 0x00FF}, {0x99, 0x00D6}, {0x9A, 0x00DC}, {0x9B, 0x00A2}, 43 | {0x9C, 0x00A3}, {0x9D, 0x00A5}, {0x9E, 0x20A7}, {0x9F, 0x0192}, 44 | {0xA0, 0x00E1}, {0xA1, 0x00ED}, {0xA2, 0x00F3}, {0xA3, 0x00FA}, 45 | {0xA4, 0x00F1}, {0xA5, 0x00D1}, {0xA6, 0x00AA}, {0xA7, 0x00BA}, 46 | {0xA8, 0x00BF}, {0xA9, 0x2310}, {0xAA, 0x00AC}, {0xAB, 0x00BD}, 47 | {0xAC, 0x00BC}, {0xAD, 0x00A1}, {0xAE, 0x00AB}, {0xAF, 0x00BB}, 48 | {0xB0, 0x2591}, {0xB1, 0x2592}, {0xB2, 0x2593}, {0xB3, 0x2502}, 49 | {0xB4, 0x2524}, {0xB5, 0x2561}, {0xB6, 0x2562}, {0xB7, 0x2556}, 50 | {0xB8, 0x2555}, {0xB9, 0x2563}, {0xBA, 0x2551}, {0xBB, 0x2557}, 51 | {0xBC, 0x255D}, {0xBD, 0x255C}, {0xBE, 0x255B}, {0xBF, 0x2510}, 52 | {0xC0, 0x2514}, {0xC1, 0x2534}, {0xC2, 0x252C}, {0xC3, 0x251C}, 53 | {0xC4, 0x2500}, {0xC5, 0x253C}, {0xC6, 0x255E}, {0xC7, 0x255F}, 54 | {0xC8, 0x255A}, {0xC9, 0x2554}, {0xCA, 0x2569}, {0xCB, 0x2566}, 55 | {0xCC, 0x2560}, {0xCD, 0x2550}, {0xCE, 0x256C}, {0xCF, 0x2567}, 56 | {0xD0, 0x2568}, {0xD1, 0x2564}, {0xD2, 0x2565}, {0xD3, 0x2559}, 57 | {0xD4, 0x2558}, {0xD5, 0x2552}, {0xD6, 0x2553}, {0xD7, 0x256B}, 58 | {0xD8, 0x256A}, {0xD9, 0x2518}, {0xDA, 0x250C}, {0xDB, 0x2588}, 59 | {0xDC, 0x2584}, {0xDD, 0x258C}, {0xDE, 0x2590}, {0xDF, 0x2580}, 60 | {0xE0, 0x03B1}, {0xE1, 0x00DF}, {0xE2, 0x0393}, {0xE3, 0x03C0}, 61 | {0xE4, 0x03A3}, {0xE5, 0x03C3}, {0xE6, 0x00B5}, {0xE7, 0x03C4}, 62 | {0xE8, 0x03A6}, {0xE9, 0x0398}, {0xEA, 0x03A9}, {0xEB, 0x03B4}, 63 | {0xEC, 0x221E}, {0xED, 0x03C6}, {0xEE, 0x03B5}, {0xEF, 0x2229}, 64 | {0xF0, 0x2261}, {0xF1, 0x00B1}, {0xF2, 0x2265}, {0xF3, 0x2264}, 65 | {0xF4, 0x2320}, {0xF5, 0x2321}, {0xF6, 0x00F7}, {0xF7, 0x2248}, 66 | {0xF8, 0x00B0}, {0xF9, 0x2219}, {0xFA, 0x00B7}, {0xFB, 0x221A}, 67 | {0xFC, 0x207F}, {0xFD, 0x00B2}, {0xFE, 0x25A0}, {0xFF, 0x00A0} 68 | } 69 | local ibm = {} 70 | for i, kv in ipairs(ibm_kv) do 71 | local k, v = unpack(kv) 72 | ibm[k] = v 73 | end 74 | return ibm 75 | end 76 | 77 | local vtg = build_vtg() 78 | local ibm = build_ibm() 79 | 80 | local function gen_header(name, tab) 81 | local fname = name..".h" 82 | local fp = io.open(fname, "w") 83 | fp:write("static uint16_t "..name.."[0x100] = {") 84 | local i = 0 85 | for j = 1, 32 do 86 | fp:write("\n ") 87 | for k = 1, 8 do 88 | local c = tab[i] or i 89 | fp:write((" 0x%04X,"):format(c)) 90 | i = i + 1 91 | end 92 | end 93 | fp:write("\n};\n") 94 | fp:close() 95 | end 96 | 97 | gen_header("cs_vtg", vtg) 98 | gen_header("cs_437", ibm) 99 | -------------------------------------------------------------------------------- /gif.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "gif.h" 10 | 11 | /* helper to write a little-endian 16-bit number portably */ 12 | #define write_num(fd, n) write((fd), (uint8_t []) {(n) & 0xFF, (n) >> 8}, 2) 13 | 14 | struct Node { 15 | uint16_t key; 16 | struct Node *children[0x10]; 17 | }; 18 | typedef struct Node Node; 19 | 20 | static Node * 21 | new_node(uint16_t key) 22 | { 23 | Node *node = calloc(1, sizeof(*node)); 24 | if (node) 25 | node->key = key; 26 | return node; 27 | } 28 | 29 | static Node * 30 | new_trie(int *nkeys) 31 | { 32 | Node *root = new_node(0); 33 | /* Create nodes for single pixels. */ 34 | for (*nkeys = 0; *nkeys < 0x10; (*nkeys)++) 35 | root->children[*nkeys] = new_node(*nkeys); 36 | *nkeys += 2; /* skip clear code and stop code */ 37 | return root; 38 | } 39 | 40 | static void 41 | del_trie(Node *root) 42 | { 43 | int i; 44 | 45 | if (!root) 46 | return; 47 | for (i = 0; i < 0x10; i++) 48 | del_trie(root->children[i]); 49 | free(root); 50 | } 51 | 52 | static void put_loop(GIF *gif, uint16_t loop); 53 | 54 | GIF * 55 | new_gif(const char *fname, uint16_t w, uint16_t h, uint8_t *gct, int loop) 56 | { 57 | GIF *gif = calloc(1, sizeof(*gif) + 2*w*h); 58 | if (!gif) 59 | goto no_gif; 60 | gif->w = w; gif->h = h; 61 | gif->cur = (uint8_t *) &gif[1]; 62 | gif->old = &gif->cur[w*h]; 63 | /* fill back-buffer with invalid pixels to force overwrite */ 64 | memset(gif->old, 0x10, w*h); 65 | gif->fd = creat(fname, 0666); 66 | if (gif->fd == -1) 67 | goto no_fd; 68 | write(gif->fd, "GIF89a", 6); 69 | write_num(gif->fd, w); 70 | write_num(gif->fd, h); 71 | write(gif->fd, (uint8_t []) {0xF3, 0x00, 0x00}, 3); 72 | write(gif->fd, gct, 0x30); 73 | if (loop >= 0 && loop <= 0xFFFF) 74 | put_loop(gif, (uint16_t) loop); 75 | return gif; 76 | no_fd: 77 | free(gif); 78 | no_gif: 79 | return NULL; 80 | } 81 | 82 | static void 83 | put_loop(GIF *gif, uint16_t loop) 84 | { 85 | write(gif->fd, (uint8_t []) {'!', 0xFF, 0x0B}, 3); 86 | write(gif->fd, "NETSCAPE2.0", 11); 87 | write(gif->fd, (uint8_t []) {0x03, 0x01}, 2); 88 | write_num(gif->fd, loop); 89 | write(gif->fd, "\0", 1); 90 | } 91 | 92 | /* Add packed key to buffer, updating offset and partial. 93 | * gif->offset holds position to put next *bit* 94 | * gif->partial holds bits to include in next byte */ 95 | static void 96 | put_key(GIF *gif, uint16_t key, int key_size) 97 | { 98 | int byte_offset, bit_offset, bits_to_write; 99 | byte_offset = gif->offset / 8; 100 | bit_offset = gif->offset % 8; 101 | gif->partial |= ((uint32_t) key) << bit_offset; 102 | bits_to_write = bit_offset + key_size; 103 | while (bits_to_write >= 8) { 104 | gif->buffer[byte_offset++] = gif->partial & 0xFF; 105 | if (byte_offset == 0xFF) { 106 | write(gif->fd, "\xFF", 1); 107 | write(gif->fd, gif->buffer, 0xFF); 108 | byte_offset = 0; 109 | } 110 | gif->partial >>= 8; 111 | bits_to_write -= 8; 112 | } 113 | gif->offset = (gif->offset + key_size) % (0xFF * 8); 114 | } 115 | 116 | static void 117 | end_key(GIF *gif) 118 | { 119 | int byte_offset; 120 | byte_offset = gif->offset / 8; 121 | if (gif->offset % 8) 122 | gif->buffer[byte_offset++] = gif->partial & 0xFF; 123 | if (byte_offset) { 124 | write(gif->fd, (uint8_t []) {byte_offset}, 1); 125 | write(gif->fd, gif->buffer, byte_offset); 126 | } 127 | write(gif->fd, "\0", 1); 128 | gif->offset = gif->partial = 0; 129 | } 130 | 131 | static void 132 | put_image(GIF *gif, uint16_t w, uint16_t h, uint16_t x, uint16_t y) 133 | { 134 | int nkeys, key_size, i, j; 135 | Node *node, *child, *root; 136 | uint8_t id_packed = 0x00; 137 | 138 | if (gif->plt) { 139 | id_packed &= ~0x7; 140 | id_packed |= 0x83; /* Local clut, 4 bits. */ 141 | } 142 | 143 | root = malloc(sizeof(*root)); 144 | write(gif->fd, ",", 1); 145 | write_num(gif->fd, x); 146 | write_num(gif->fd, y); 147 | write_num(gif->fd, w); 148 | write_num(gif->fd, h); 149 | write(gif->fd, &id_packed, 1); 150 | if (id_packed & 0x80) 151 | write(gif->fd, gif->plt, 3<<((id_packed & 0x7)+1)); 152 | 153 | write(gif->fd, "\x04", 1); /* Min code size */ 154 | root = node = new_trie(&nkeys); 155 | key_size = 5; 156 | put_key(gif, 0x10, key_size); /* clear code */ 157 | for (i = y; i < y+h; i++) { 158 | for (j = x; j < x+w; j++) { 159 | uint8_t pixel = gif->cur[i*gif->w+j]; 160 | child = node->children[pixel]; 161 | if (child) { 162 | node = child; 163 | } else { 164 | put_key(gif, node->key, key_size); 165 | if (nkeys < 0x1000) { 166 | if (nkeys == (1 << key_size)) 167 | key_size++; 168 | node->children[pixel] = new_node(nkeys++); 169 | } else { 170 | put_key(gif, 0x10, key_size); /* clear code */ 171 | del_trie(root); 172 | root = node = new_trie(&nkeys); 173 | key_size = 5; 174 | } 175 | node = root->children[pixel]; 176 | } 177 | } 178 | } 179 | put_key(gif, node->key, key_size); 180 | put_key(gif, 0x11, key_size); /* stop code */ 181 | end_key(gif); 182 | del_trie(root); 183 | } 184 | 185 | static int 186 | get_bbox(GIF *gif, uint16_t *w, uint16_t *h, uint16_t *x, uint16_t *y) 187 | { 188 | int i, j, k; 189 | int left, right, top, bottom; 190 | left = gif->w; right = 0; 191 | top = gif->h; bottom = 0; 192 | k = 0; 193 | for (i = 0; i < gif->h; i++) { 194 | for (j = 0; j < gif->w; j++, k++) { 195 | if (gif->cur[k] != gif->old[k]) { 196 | if (j < left) left = j; 197 | if (j > right) right = j; 198 | if (i < top) top = i; 199 | if (i > bottom) bottom = i; 200 | } 201 | } 202 | } 203 | if (left != gif->w && top != gif->h) { 204 | *x = left; *y = top; 205 | *w = right - left + 1; 206 | *h = bottom - top + 1; 207 | return 1; 208 | } else { 209 | return 0; 210 | } 211 | } 212 | 213 | static void 214 | set_delay(GIF *gif, uint16_t d) 215 | { 216 | write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); 217 | write_num(gif->fd, d); 218 | write(gif->fd, "\0\0", 2); 219 | } 220 | 221 | void 222 | add_frame(GIF *gif, uint16_t d) 223 | { 224 | uint16_t w, h, x, y; 225 | uint8_t *tmp; 226 | 227 | if (d) 228 | set_delay(gif, d); 229 | if (gif->plt_dirty) { 230 | w = gif->w; h = gif->h; x = y = 0; 231 | gif->plt_dirty = 0; 232 | } else 233 | if (!get_bbox(gif, &w, &h, &x, &y)) { 234 | /* image's not changed; save one pixel just to add delay */ 235 | if (!d) return; 236 | w = h = 1; 237 | x = y = 0; 238 | } 239 | put_image(gif, w, h, x, y); 240 | tmp = gif->old; 241 | gif->old = gif->cur; 242 | gif->cur = tmp; 243 | } 244 | 245 | void 246 | close_gif(GIF* gif) 247 | { 248 | write(gif->fd, ";", 1); 249 | close(gif->fd); 250 | free(gif); 251 | } 252 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "term.h" 15 | #include "mbf.h" 16 | #include "gif.h" 17 | #include "default_font.h" 18 | 19 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 20 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 21 | 22 | #define MIN_DELAY 6 23 | 24 | static struct Options { 25 | char *timings, *dialogue; 26 | char *output; 27 | float maxdelay, divisor; 28 | int loop; 29 | char *font; 30 | int height, width; 31 | int cursor; 32 | int quiet; 33 | int barsize; 34 | 35 | int has_winsize; 36 | struct winsize size; 37 | } options; 38 | 39 | uint8_t 40 | get_pair(Term *term, int row, int col) 41 | { 42 | Cell cell; 43 | uint8_t fore, back; 44 | int inverse; 45 | 46 | inverse = term->mode & M_REVERSE; 47 | if (term->mode & M_CURSORVIS) 48 | inverse = term->row == row && term->col == col ? !inverse : inverse; 49 | cell = term->addr[row][col]; 50 | inverse = cell.attr & A_INVERSE ? !inverse : inverse; 51 | fore = cell.pair >> 4; 52 | back = cell.pair & 0xF; 53 | if (cell.attr & (A_ITALIC | A_CROSSED)) 54 | fore = 0x2; 55 | else if (cell.attr & A_UNDERLINE) 56 | fore = 0x6; 57 | else if (cell.attr & A_DIM) 58 | fore = 0x8; 59 | if (inverse) { 60 | uint8_t t; 61 | t = fore; fore = back; back = t; 62 | } 63 | if (cell.attr & A_BOLD) 64 | fore |= 0x8; 65 | if (cell.attr & A_BLINK) 66 | back |= 0x8; 67 | if ((cell.attr & A_INVISIBLE) != 0) fore = back; 68 | return (fore << 4) | (back & 0xF); 69 | } 70 | 71 | void 72 | draw_char(Font *font, GIF *gif, uint16_t code, uint8_t pair, int row, int col) 73 | { 74 | int i, j; 75 | int x, y; 76 | int index; 77 | int pixel; 78 | uint8_t *strip; 79 | 80 | index = get_index(font, code); 81 | if (index == -1) 82 | return; 83 | strip = &font->data[font->stride * font->header.h * index]; 84 | y = font->header.h * row; 85 | for (i = 0; i < font->header.h; i++) { 86 | x = font->header.w * col; 87 | for (j = 0; j < font->header.w; j++) { 88 | pixel = strip[j >> 3] & (1 << (7 - (j & 7))); 89 | gif->cur[y * gif->w + x] = pixel ? pair >> 4 : pair & 0xF; 90 | x++; 91 | } 92 | y++; 93 | strip += font->stride; 94 | } 95 | } 96 | 97 | void 98 | render(Term *term, Font *font, GIF *gif, uint16_t delay) 99 | { 100 | int i, j; 101 | uint16_t code; 102 | uint8_t pair; 103 | 104 | for (i = 0; i < term->rows; i++) { 105 | for (j = 0; j < term->cols; j++) { 106 | code = term->addr[i][j].code; 107 | pair = get_pair(term, i, j); 108 | draw_char(font, gif, code, pair, i, j); 109 | } 110 | } 111 | 112 | if (term->plt_local) 113 | gif->plt = term->plt; 114 | else 115 | gif->plt = 0; 116 | gif->plt_dirty |= term->plt_dirty; 117 | term->plt_dirty = 0; 118 | 119 | add_frame(gif, delay); 120 | } 121 | 122 | int 123 | convert_script() 124 | { 125 | FILE *ft; 126 | int fd; 127 | float t; 128 | int n; 129 | uint8_t ch; 130 | Font *font; 131 | int w, h; 132 | int i, c; 133 | float d; 134 | uint16_t rd, id = 0; 135 | float lastdone, done; 136 | char pb[options.barsize+1]; 137 | char fl[512]; 138 | int fln = 0; 139 | GIF *gif; 140 | Term *term; 141 | 142 | ft = fopen(options.timings, "r"); 143 | if (!ft) { 144 | fprintf(stderr, "error: could not load timings: %s\n", options.timings); 145 | goto no_ft; 146 | } 147 | fd = open(options.dialogue, O_RDONLY); 148 | if (fd == -1) { 149 | fprintf(stderr, "error: could not load dialogue: %s\n", options.dialogue); 150 | goto no_fd; 151 | } 152 | if (options.font == 0) { 153 | font = default_font; 154 | } else { 155 | font = load_font(options.font); 156 | if (!font) { 157 | fprintf(stderr, "error: could not load font: %s\n", options.font); 158 | goto no_font; 159 | } 160 | } 161 | 162 | /* Save first line of dialogue */ 163 | do { 164 | if (read(fd, &ch, 1) <= 0) break; 165 | if (fln<(int)sizeof(fl)-1) fl[fln++]=ch; 166 | } while (ch != '\n'); 167 | /* Inspect it for the terminal size if needed */ 168 | if (fln > 16 && (options.height == 0 || options.width == 0)) { 169 | int col=0, ln=0; 170 | char * s; 171 | fl[fln] = 0; 172 | s = strstr(fl, "COLUMNS=\""); 173 | if (s) col = atoi(s+9); 174 | s = strstr(fl, "LINES=\""); 175 | if (s) ln = atoi(s+7); 176 | 177 | if (ln>0 && col>0) { 178 | if (options.width <= 0) 179 | options.width = col; 180 | if (options.height <= 0) 181 | options.height = ln; 182 | } 183 | } 184 | 185 | /* Default the VT to our real terminal */ 186 | if (options.has_winsize && (options.height == 0 || options.width == 0)) { 187 | if (options.height <= 0) 188 | options.height = options.size.ws_row; 189 | if (options.width <= 0) 190 | options.width = options.size.ws_col; 191 | } 192 | 193 | if (options.width <= 0 || options.height <= 0) { 194 | fprintf(stderr, "error: no terminal size specified\n"); 195 | goto no_termsize; 196 | } 197 | 198 | term = new_term(options.height, options.width); 199 | w = term->cols * font->header.w; 200 | h = term->rows * font->header.h; 201 | gif = new_gif(options.output, w, h, term->plt, options.loop); 202 | if (!gif) { 203 | fprintf(stderr, "error: could not create GIF: %s\n", options.output); 204 | goto no_gif; 205 | } 206 | if (options.barsize) { 207 | pb[0] = '['; 208 | pb[options.barsize-1] = ']'; 209 | pb[options.barsize] = '\0'; 210 | for (i = 1; i < options.barsize-1; i++) 211 | pb[i] = '-'; 212 | lastdone = 0; 213 | printf("%s\r[", pb); 214 | /* get number of chunks */ 215 | for (c = 0; fscanf(ft, "%f %d\n", &t, &n) == 2; c++); 216 | rewind(ft); 217 | } 218 | i = 0; 219 | d = rd = 0; 220 | while (fscanf(ft, "%f %d\n", &t, &n) == 2) { 221 | if (options.barsize) { 222 | done = i * (options.barsize-1) / c; 223 | if (done > lastdone) { 224 | while (done > lastdone) { 225 | putchar('#'); 226 | lastdone++; 227 | } 228 | fflush(stdout); 229 | } 230 | } 231 | d += (MIN(t, options.maxdelay) * 100.0 / options.divisor); 232 | rd = (uint16_t) MIN((int)(d + 0.5), 65535); 233 | if (i && rd >= MIN_DELAY) { 234 | render(term, font, gif, rd); 235 | d = 0; 236 | } 237 | if (i == 0) { id = rd; rd = 0; d = 0; } 238 | while (n--) { 239 | read(fd, &ch, 1); 240 | parse(term, ch); 241 | } 242 | if (!options.cursor) 243 | term->mode &= ~M_CURSORVIS; 244 | i++; 245 | } 246 | rd += id; 247 | if (options.barsize) { 248 | while (lastdone < options.barsize-2) { 249 | putchar('#'); 250 | lastdone++; 251 | } 252 | putchar('\n'); 253 | } 254 | render(term, font, gif, MAX(rd, 1)); 255 | close_gif(gif); 256 | free(term); 257 | return 0; 258 | no_gif: 259 | free(term); 260 | no_termsize: 261 | if (options.font) free(font); 262 | no_font: 263 | close(fd); 264 | no_fd: 265 | fclose(ft); 266 | no_ft: 267 | return 1; 268 | } 269 | 270 | void 271 | help(char *name) 272 | { 273 | fprintf(stderr, 274 | "Usage: %s [options] timings dialogue\n\n" 275 | "timings: File generated by script(1)'s -t option\n" 276 | "dialogue: File generated by script(1)'s regular output\n\n" 277 | "options:\n" 278 | " -o output File name of GIF output\n" 279 | " -m maxdelay Maximum delay, as in scriptreplay(1)\n" 280 | " -d divisor Speedup, as in scriptreplay(1)\n" 281 | " -l count GIF loop count (0 = infinite loop)\n" 282 | " -f font File name of MBF font to use\n" 283 | " -h lines Terminal height\n" 284 | " -w columns Terminal width\n" 285 | " -c on|off Show/hide cursor\n" 286 | " -p palette Define color palette, '@help' for std else file.\n" 287 | " -q Quiet mode (don't show progress bar)\n" 288 | " -v Verbose mode (show parser logs)\n" 289 | , name); 290 | } 291 | 292 | void 293 | set_defaults() 294 | { 295 | options.height = 0; 296 | options.width = 0; 297 | options.output = "con.gif"; 298 | options.maxdelay = FLT_MAX; 299 | options.divisor = 1.0; 300 | options.loop = -1; 301 | options.font = 0; 302 | options.cursor = 1; 303 | options.quiet = 0; 304 | options.barsize = 0; 305 | } 306 | 307 | int 308 | main(int argc, char *argv[]) 309 | { 310 | int opt; 311 | int ret; 312 | 313 | set_defaults(); 314 | options.has_winsize = 0; 315 | if (ioctl(0, TIOCGWINSZ, &options.size) != -1) { 316 | options.has_winsize = 1; 317 | } 318 | while ((opt = getopt(argc, argv, "o:m:d:l:f:h:w:c:p:qv")) != -1) { 319 | switch (opt) { 320 | case 'o': 321 | options.output = optarg; 322 | break; 323 | case 'm': 324 | options.maxdelay = atof(optarg); 325 | break; 326 | case 'd': 327 | options.divisor = atof(optarg); 328 | break; 329 | case 'l': 330 | options.loop = atoi(optarg); 331 | break; 332 | case 'f': 333 | options.font = optarg; 334 | break; 335 | case 'h': 336 | options.height = atoi(optarg); 337 | break; 338 | case 'w': 339 | options.width = atoi(optarg); 340 | break; 341 | case 'c': 342 | if (!strcmp(optarg, "on") || !strcmp(optarg, "1")) 343 | options.cursor = 1; 344 | else if (!strcmp(optarg, "off") || !strcmp(optarg, "0")) 345 | options.cursor = 0; 346 | break; 347 | case 'q': 348 | options.quiet = 1; 349 | break; 350 | case 'v': 351 | set_verbosity(1); 352 | break; 353 | case 'p': 354 | set_default_palette(optarg); 355 | break; 356 | default: 357 | help(argv[0]); 358 | return 1; 359 | } 360 | } 361 | if (optind >= argc - 1) { 362 | fprintf(stderr, "error: no input given\n"); 363 | help(argv[0]); 364 | return 1; 365 | } 366 | options.timings = argv[optind++]; 367 | options.dialogue = argv[optind++]; 368 | if (!options.quiet && options.has_winsize) 369 | options.barsize = options.size.ws_col - 1; 370 | ret = convert_script(); 371 | return ret; 372 | } 373 | -------------------------------------------------------------------------------- /term.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "term.h" 8 | #include "default.h" 9 | #include "cs_vtg.h" 10 | #include "cs_437.h" 11 | 12 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 13 | #define CLEARWRAP do{ if (term->col >= term->cols) term->col = term->cols-1; }while(0) 14 | #define CLIPROW(X) if (term->row<0 || term->row >= term->rows) term->row = X 15 | #define CLIPCOL(X) if (term->col<0 || term->col >= term->cols) term->col = X 16 | 17 | static int verbose = 0; 18 | 19 | static void 20 | logfmt(char *fmt, ...) 21 | { 22 | va_list args; 23 | va_start(args, fmt); 24 | if (verbose) 25 | vfprintf(stderr, fmt, args); 26 | va_end(args); 27 | } 28 | 29 | void 30 | set_verbosity(int level) 31 | { 32 | verbose = level; 33 | } 34 | 35 | static void 36 | save_cursor(Term *term) 37 | { 38 | term->save_cursor.row = term->row; 39 | term->save_cursor.col = term->col; 40 | } 41 | 42 | static void 43 | load_cursor(Term *term) 44 | { 45 | term->row = term->save_cursor.row; 46 | term->col = term->save_cursor.col; 47 | } 48 | 49 | static void 50 | save_misc(Term *term) 51 | { 52 | term->save_misc.row = term->row; 53 | term->save_misc.col = term->col; 54 | term->save_misc.origin_on = term->mode & M_ORIGIN; 55 | term->save_misc.attr = term->attr; 56 | term->save_misc.pair = term->pair; 57 | term->save_misc.cs_array[0] = term->cs_array[0]; 58 | term->save_misc.cs_array[1] = term->cs_array[1]; 59 | term->save_misc.cs_index = term->cs_index; 60 | } 61 | 62 | static void 63 | load_misc(Term *term) 64 | { 65 | term->row = term->save_misc.row; 66 | term->col = term->save_misc.col; 67 | if (term->save_misc.origin_on) 68 | term->mode |= M_ORIGIN; 69 | else 70 | term->mode &= ~M_ORIGIN; 71 | term->attr = term->save_misc.attr; 72 | term->pair = term->save_misc.pair; 73 | term->cs_array[0] = term->save_misc.cs_array[0]; 74 | term->cs_array[1] = term->save_misc.cs_array[1]; 75 | term->cs_index = term->save_misc.cs_index; 76 | } 77 | 78 | void 79 | set_default_palette(char * pname) 80 | { 81 | static struct { 82 | char * name; 83 | uint8_t * plt; 84 | } pal[] = { 85 | { "xterm", plt_xterm }, 86 | { "vga", plt_vga_ansi }, 87 | { "sol_ansi", plt_solarized_like_ansi }, 88 | { "solarized", plt_solarized }, 89 | { "putty", plt_putty }, 90 | { 0, 0} 91 | }; 92 | 93 | if (pname[0] == '@') { 94 | int i; 95 | for(i=0; pal[i].name; i++) { 96 | if (strcasecmp(pname+1, pal[i].name) == 0) { 97 | def_plt = pal[i].plt; 98 | return; 99 | } 100 | } 101 | fprintf(stderr, "Known standard palette names are:\n"); 102 | for(i=0; pal[i].name; i++) 103 | fprintf(stderr, " @%s\n", pal[i].name); 104 | exit(2); 105 | } else { 106 | FILE * fd; 107 | char buf[BUFSIZ]; 108 | Term * term = 0; 109 | uint8_t * plt = malloc(sizeof(term->plt)); 110 | memcpy(plt, def_plt, sizeof(term->plt)); 111 | def_plt = plt; 112 | 113 | if ((fd = fopen(pname, "r")) == 0) { 114 | perror(pname); exit(1); 115 | } 116 | while (fgets(buf, sizeof(buf), fd) != 0) { 117 | char * s = buf, *e; 118 | long cno = 0, colour = 0; 119 | 120 | while (*s == ' ' || *s == '\t') s++; 121 | if (*s == '#') continue; 122 | 123 | if (strncasecmp(s, "color", 5) == 0) { 124 | s += 5; 125 | } else if (strncasecmp(s, "colour", 6) == 0) { 126 | s += 6; 127 | } else continue; 128 | 129 | // Only lines that match /^ *colou?r[0-9]+/i 130 | if (*s < '0' || *s > '9') continue; 131 | 132 | cno = strtol(s, &e, 0); 133 | if (s == e) goto bad_line; 134 | if (cno < 0 || cno > 16) goto bad_line; 135 | 136 | s = e; 137 | while (*s == ' ' || *s == '\t' || *s == '#' || *s == '=') s++; 138 | colour = strtol(s, &e, 16); 139 | if (e-s != 6) goto bad_line; 140 | 141 | plt[cno*3+0] = ((colour>>16) & 0xFF); 142 | plt[cno*3+1] = ((colour>> 8) & 0xFF); 143 | plt[cno*3+2] = ((colour ) & 0xFF); 144 | continue; 145 | bad_line: 146 | fprintf(stderr, "Bad line in colour file: %s", buf); 147 | exit(2); 148 | } 149 | 150 | fclose(fd); 151 | } 152 | return; 153 | } 154 | 155 | static void 156 | reset(Term *term) 157 | { 158 | int i, j; 159 | 160 | term->row = term->col = 0; 161 | term->top = 0; 162 | term->bot = term->rows - 1; 163 | term->mode = def_mode; 164 | term->attr = def_attr; 165 | term->pair = def_pair; 166 | term->cs_array[0] = CS_BMP; 167 | term->cs_array[1] = CS_VTG; 168 | term->cs_index = 0; 169 | term->state = S_ANY; 170 | term->parlen = 0; 171 | if (memcmp(term->plt, def_plt, sizeof(term->plt) != 0)) { 172 | term->plt_dirty = 1; 173 | } 174 | memcpy(term->plt, def_plt, sizeof(term->plt)); 175 | term->plt_local = 0; 176 | for (i = 0; i < term->rows; i++) { 177 | term->addr[i] = &term->cells[i*term->cols]; 178 | for (j = 0; j < term->cols; j++) 179 | term->addr[i][j] = (Cell) {EMPTY, def_attr, def_pair}; 180 | } 181 | save_cursor(term); 182 | save_misc(term); 183 | } 184 | 185 | Term * 186 | new_term(int rows, int cols) 187 | { 188 | size_t size = sizeof(Term) + rows*sizeof(Cell *) + rows*cols*sizeof(Cell); 189 | Term *term = malloc(size); 190 | if (!term) 191 | return NULL; 192 | term->rows = rows; 193 | term->cols = cols; 194 | term->addr = (Cell **) &term[1]; 195 | term->cells = (Cell *) &term->addr[rows]; 196 | reset(term); 197 | term->plt_dirty = 0; 198 | return term; 199 | } 200 | 201 | static uint16_t 202 | char_code(Term *term) 203 | { 204 | int i; 205 | uint16_t code = term->partial[0] & ((1 << (8 - term->parlen)) - 1); 206 | for (i = 1; i < term->parlen; i++) 207 | code = (code << 6) | (term->partial[i] & 0x3F); 208 | return code; 209 | } 210 | 211 | static int 212 | within_bounds(Term *term, int row, int col) 213 | { 214 | if (row < 0 || row >= term->rows || col < 0 || col > term->cols) { 215 | logfmt("position %d,%d is out of bounds %d,%d\n", 216 | row+1, col+1, term->rows, term->cols); 217 | return 0; 218 | } else { 219 | return 1; 220 | } 221 | } 222 | 223 | /* Move lines down and put a blank line at the top. */ 224 | static void 225 | scroll_up(Term *term) 226 | { 227 | int row, col; 228 | Cell *addr; 229 | 230 | if (!within_bounds(term, term->top, 0)) 231 | return; 232 | if (!within_bounds(term, term->bot, 0)) 233 | return; 234 | addr = term->addr[term->bot]; 235 | for (row = term->bot; row > term->top; row--) 236 | term->addr[row] = term->addr[row-1]; 237 | term->addr[term->top] = addr; 238 | for (col = 0; col < term->cols; col++) 239 | term->addr[term->top][col] = BLANK; 240 | } 241 | 242 | /* Move lines up and put a blank line at the bottom. */ 243 | static void 244 | scroll_down(Term *term) 245 | { 246 | int row, col; 247 | Cell *addr; 248 | 249 | if (!within_bounds(term, term->top, 0)) 250 | return; 251 | if (!within_bounds(term, term->bot, 0)) 252 | return; 253 | addr = term->addr[term->top]; 254 | for (row = term->top; row < term->bot; row++) 255 | term->addr[row] = term->addr[row+1]; 256 | term->addr[term->bot] = addr; 257 | for (col = 0; col < term->cols; col++) 258 | term->addr[term->bot][col] = BLANK; 259 | } 260 | 261 | static void 262 | addchar(Term *term, uint16_t code) 263 | { 264 | Cell cell = (Cell) {code, term->attr, term->pair}; 265 | if (term->col >= term->cols) { 266 | if (term->mode & M_AUTOWRAP) { 267 | term->col = 0; 268 | if (term->row < term->bot) 269 | term->row++; 270 | else 271 | scroll_down(term); 272 | } else { 273 | term->col = term->cols - 1; 274 | } 275 | } 276 | if (!within_bounds(term, term->row, term->col)) 277 | return; 278 | if (term->mode & M_INSERT) { 279 | Cell next; 280 | int col; 281 | for (col = term->col; col < term->cols; col++) { 282 | next = term->addr[term->row][col]; 283 | term->addr[term->row][col] = cell; 284 | cell = next; 285 | } 286 | } else { 287 | term->addr[term->row][term->col] = cell; 288 | } 289 | term->col++; 290 | } 291 | 292 | static void 293 | linefeed(Term *term) 294 | { 295 | if (term->row == term->bot) 296 | scroll_down(term); 297 | else 298 | term->row++; 299 | if (term->mode & M_NEWLINE) 300 | term->col = 0; 301 | } 302 | 303 | static void 304 | ctrlchar(Term *term, uint8_t byte) 305 | { 306 | switch (byte) { 307 | case 0x08: 308 | CLEARWRAP; 309 | if (term->col) term->col--; 310 | break; 311 | case 0x09: 312 | /* TODO: See ESC Sequence H (HTS) */ 313 | term->col &= ~7; term->col += 8; 314 | CLIPCOL(term->cols-1); 315 | break; 316 | case 0x0A: case 0x0B: case 0x0C: 317 | CLEARWRAP; 318 | linefeed(term); 319 | break; 320 | case 0x0D: 321 | term->col = 0; 322 | break; 323 | case 0x0E: 324 | term->cs_index = 1; 325 | break; 326 | case 0x0F: 327 | term->cs_index = 0; 328 | break; 329 | } 330 | } 331 | 332 | static void 333 | escseq(Term *term, uint8_t byte) 334 | { 335 | uint8_t first, second; 336 | 337 | if (term->parlen) { 338 | first = *term->partial; 339 | second = byte; 340 | } else { 341 | first = byte; 342 | second = 0; 343 | } 344 | switch (first) { 345 | case 'c': 346 | reset(term); 347 | break; 348 | case 'D': 349 | CLEARWRAP; 350 | if (term->row == term->bot) 351 | scroll_down(term); 352 | else 353 | term->row++; 354 | break; 355 | case 'E': 356 | CLEARWRAP; 357 | if (term->row == term->bot) { 358 | scroll_down(term); 359 | term->col = 0; 360 | } else { 361 | term->row++; 362 | } 363 | break; 364 | case 'H': 365 | /* TODO: set tab stop at current column */ 366 | logfmt("NYI: ESC Sequence H (HTS)\n"); 367 | break; 368 | case 'M': 369 | if (term->row == term->top) 370 | scroll_up(term); 371 | else 372 | term->row--; 373 | break; 374 | case 'Z': 375 | /* Identify Terminal (DECID) */ 376 | /* if we were a real terminal, we'd reply with "ESC [ ? 6 c" */ 377 | /* since there is no application listening, we can ignore this */ 378 | break; 379 | case '7': 380 | CLEARWRAP; 381 | save_misc(term); 382 | break; 383 | case '8': 384 | CLEARWRAP; 385 | load_misc(term); 386 | break; 387 | case '%': 388 | /* TODO: select charset */ 389 | logfmt("NYI: ESC Sequence %% (character set selection)\n"); 390 | switch(second) 391 | { 392 | case '8': /* Linux switch to UTF8 */ 393 | case 'G': /* DOCS: Designate other coding system */ 394 | term->mode &= ~M_ISOLAT1; 395 | if (term->cs_array[0] == CS_ISO) 396 | term->cs_array[0] = CS_BMP; 397 | if (term->cs_array[1] == CS_ISO) 398 | term->cs_array[1] = CS_BMP; 399 | break; 400 | case '@': /* DOCS, Standard return */ 401 | term->mode |= M_ISOLAT1; 402 | if (term->cs_array[0] == CS_BMP) 403 | term->cs_array[0] = CS_ISO; 404 | if (term->cs_array[1] == CS_BMP) 405 | term->cs_array[1] = CS_ISO; 406 | break; 407 | } 408 | break; 409 | case '#': 410 | switch(second) 411 | { 412 | case '8': 413 | { 414 | int i, j; 415 | for (i = 0; i < term->rows; i++) { 416 | for (j = 0; j < term->cols; j++) { 417 | term->addr[i][j] = (Cell) {'E', def_attr, def_pair}; 418 | } 419 | } 420 | } 421 | break; 422 | default: 423 | /* TODO */ 424 | logfmt("NYI: ESC Sequence # 3..6 DECDWL etc\n"); 425 | break; 426 | } 427 | break; 428 | case '(': 429 | switch (second) { 430 | case 'B': 431 | term->cs_array[0] = (term->mode&M_ISOLAT1)?CS_ISO:CS_BMP; 432 | break; 433 | case '0': 434 | term->cs_array[0] = CS_VTG; 435 | break; 436 | case 'U': 437 | term->cs_array[0] = CS_437; 438 | break; 439 | case 'K': 440 | logfmt("UNS: user-defined mapping\n"); 441 | term->cs_array[0] = (term->mode&M_ISOLAT1)?CS_ISO:CS_BMP; 442 | break; 443 | } 444 | break; 445 | case ')': 446 | switch (second) { 447 | case 'B': 448 | term->cs_array[1] = (term->mode&M_ISOLAT1)?CS_ISO:CS_BMP; 449 | break; 450 | case '0': 451 | term->cs_array[1] = CS_VTG; 452 | break; 453 | case 'U': 454 | term->cs_array[1] = CS_437; 455 | break; 456 | case 'K': 457 | logfmt("UNS: user-defined mapping\n"); 458 | term->cs_array[1] = (term->mode&M_ISOLAT1)?CS_ISO:CS_BMP; 459 | break; 460 | } 461 | break; 462 | case '>': 463 | /* TODO: set numeric keypad mode */ 464 | logfmt("NYI: ESC Sequence > (DECPNM)\n"); 465 | break; 466 | case '=': 467 | /* TODO: set application keypad mode */ 468 | logfmt("NYI: ESC Sequence = (DECPAM)\n"); 469 | break; 470 | default: 471 | logfmt("UNS: ESC Sequence %c\n", first); 472 | } 473 | } 474 | 475 | static int 476 | do_linux_osc(Term *term) 477 | { 478 | int i; 479 | uint8_t buf[4] = {0,0,0,0}; 480 | if (term->partial[0] == 'R') 481 | { 482 | if (memcmp(term->plt, def_plt, sizeof(term->plt) != 0)) 483 | term->plt_dirty = 1; 484 | memcpy(term->plt, def_plt, sizeof(term->plt)); 485 | term->plt_local = 0; 486 | return 1; 487 | } 488 | if (term->partial[0] != 'P' || term->parlen != 8) 489 | return 0; 490 | 491 | for (i=1; i<8; i++) { 492 | /* isxdigit is locale broken */ 493 | int ch = term->partial[i]; 494 | if (ch >= '0' && ch <= '9') 495 | buf[i>>1] = (buf[i>>1] << 4) + (ch - '0'); 496 | else if (ch >= 'a' && ch <= 'f') 497 | buf[i>>1] = (buf[i>>1] << 4) + (ch - 'a' + 10); 498 | else if (ch >= 'A' && ch <= 'F') 499 | buf[i>>1] = (buf[i>>1] << 4) + (ch - 'A' + 10); 500 | else 501 | return 0; 502 | } 503 | for(i=0; i<3; i++) { 504 | if (term->plt[buf[0]*3+i] != buf[i+1]) { 505 | term->plt[buf[0]*3+i] = buf[i+1]; 506 | term->plt_local = term->plt_dirty = 1; 507 | } 508 | } 509 | 510 | return 1; 511 | } 512 | 513 | static int 514 | getparams(char *partial, int *params, int n) 515 | { 516 | char *next; 517 | char *token = partial; 518 | int i = 0; 519 | if (!*partial) { 520 | params[0] = 0; 521 | return 1; 522 | } 523 | while (i < n && *token) { 524 | params[i++] = strtol(token, &next, 10); 525 | if (*next) next++; 526 | token = next; 527 | } 528 | if (i < n && *(token-1) == ';') 529 | params[i++] = 0; 530 | return i; 531 | } 532 | 533 | #define SWITCH(T, F, V) (T)->mode = (V) ? (T)->mode | (F) : (T)->mode & ~(F) 534 | 535 | static void 536 | modeswitch(Term *term, int private, int number, int value) 537 | { 538 | if (private) { 539 | /* DEC modes */ 540 | switch (number) { 541 | case 1: 542 | SWITCH(term, M_CURSORKEY, value); 543 | break; 544 | case 3: 545 | /* TODO: 80/132 columns mode switch */ 546 | logfmt("NYI: DEC mode 3\n"); 547 | break; 548 | case 5: 549 | SWITCH(term, M_REVERSE, value); 550 | break; 551 | case 6: 552 | SWITCH(term, M_ORIGIN, value); 553 | term->row = term->top; 554 | term->col = 0; 555 | break; 556 | case 7: 557 | SWITCH(term, M_AUTOWRAP, value); 558 | break; 559 | case 8: 560 | SWITCH(term, M_AUTORPT, value); 561 | break; 562 | case 9: 563 | SWITCH(term, M_MOUSEX10, value); 564 | break; 565 | case 25: 566 | SWITCH(term, M_CURSORVIS, value); 567 | break; 568 | case 1000: 569 | SWITCH(term, M_MOUSEX11, value); 570 | break; 571 | default: 572 | logfmt("UNS: DEC mode %d\n", number); 573 | } 574 | } else { 575 | /* ANSI modes */ 576 | switch (number) { 577 | case 3: 578 | SWITCH(term, M_DISPCTRL, value); 579 | break; 580 | case 4: 581 | SWITCH(term, M_INSERT, value); 582 | break; 583 | case 20: 584 | SWITCH(term, M_NEWLINE, value); 585 | break; 586 | default: 587 | logfmt("UNS: ANSI mode %d\n", number); 588 | } 589 | } 590 | } 591 | 592 | static int 593 | fake_colour_16m(int red, int green, int blue) 594 | { 595 | int av = (red+green+blue)/3; 596 | int palno = 1*(red>=av) + 2*(green>=av) + 4*(blue>=av); 597 | if (red==green && green==blue) { 598 | if (av > 212) palno = 15-8; 599 | else if (av > 127) palno = 7-8; 600 | else if (av > 42) palno = 8; 601 | else palno = 0; 602 | } 603 | return palno + 8*(av>127); 604 | } 605 | 606 | static int 607 | fake_colour_256(int colnum){ 608 | int nr, ng, nb; 609 | if (colnum < 16) { 610 | nr = (colnum&1) ? 170:0; 611 | ng = (colnum&2) ? 170:0; 612 | nb = (colnum&4) ? 170:0; 613 | if (colnum&8) { nr += 85; ng += 85; nb += 85; } 614 | if (colnum == 3) ng = 85; 615 | } else if (colnum < 232) { 616 | int i = colnum - 16; 617 | nr = i / 36; ng = (i / 6) % 6; nb = i % 6; 618 | nr = nr ? nr * 40 + 55 : 0; 619 | ng = ng ? ng * 40 + 55 : 0; 620 | nb = nb ? nb * 40 + 55 : 0; 621 | } else if (colnum < 256) { 622 | int i = colnum - 232; 623 | nr = ng = nb = i * 10 + 8; 624 | } else 625 | nr=ng=nb = 127; 626 | 627 | return fake_colour_16m(nr, ng, nb); 628 | } 629 | 630 | static void 631 | sgr(Term *term, int n, int *params) 632 | { 633 | int i, number; 634 | for(i=0; iattr = def_attr; 640 | term->pair = def_pair; 641 | break; 642 | case 1: 643 | term->attr |= A_BOLD; 644 | break; 645 | case 2: 646 | term->attr |= A_DIM; 647 | break; 648 | case 3: 649 | term->attr |= A_ITALIC; 650 | break; 651 | case 4: 652 | term->attr |= A_UNDERLINE; 653 | break; 654 | case 5: 655 | term->attr |= A_BLINK; 656 | break; 657 | case 7: 658 | term->attr |= A_INVERSE; 659 | break; 660 | case 8: 661 | term->attr |= A_INVISIBLE; 662 | break; 663 | case 10: 664 | /* TODO: reset toggle meta flag */ 665 | term->cs_array[term->cs_index = 0] = (term->mode&M_ISOLAT1)?CS_ISO:CS_BMP; 666 | term->mode &= ~M_DISPCTRL; 667 | break; 668 | case 11: 669 | /* TODO: reset toggle meta flag */ 670 | term->cs_array[term->cs_index] = CS_437; 671 | term->mode |= M_DISPCTRL; 672 | break; 673 | case 12: 674 | /* TODO: set toggle meta flag */ 675 | term->cs_array[term->cs_index] = CS_437; 676 | term->mode |= M_DISPCTRL; 677 | break; 678 | case 21: 679 | #ifndef OLDLINUX 680 | term->attr |= A_UNDERLINE; /* Linux say should be: DOUBLE_ULINE */ 681 | break; 682 | #endif 683 | case 22: 684 | term->attr &= ~A_DIM; 685 | term->attr &= ~A_BOLD; 686 | break; 687 | case 23: 688 | term->attr &= ~A_ITALIC; 689 | break; 690 | case 24: 691 | term->attr &= ~A_UNDERLINE; 692 | break; 693 | case 25: 694 | term->attr &= ~A_BLINK; 695 | break; 696 | case 27: 697 | term->attr &= ~A_INVERSE; 698 | break; 699 | case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: 700 | term->pair = ((number - 30) << 4) | (term->pair & 0x0F); 701 | break; 702 | #ifdef OLDLINUX 703 | case 38: 704 | term->attr |= A_UNDERLINE; 705 | term->pair = (DEF_FORE << 4) | (term->pair & 0x0F); 706 | break; 707 | case 39: 708 | term->attr &= ~A_UNDERLINE; 709 | term->pair = (DEF_FORE << 4) | (term->pair & 0x0F); 710 | break; 711 | #endif 712 | #ifndef OLDLINUX 713 | case 38: 714 | i++ ; 715 | if (i>n) break; 716 | if (params[i] == 5) { 717 | /* 256 colours */ 718 | term->pair = (fake_colour_256(params[i+1]) << 4) | (term->pair & 0x0F); 719 | i++; 720 | break; 721 | } else if (params[i] == 2) { 722 | /* 16M colours, note I'm using common form not strict ITU T.416 */ 723 | term->pair = (fake_colour_16m(params[i+1],params[i+2],params[i+3]) << 4) | (term->pair & 0x0F); 724 | i+=3; 725 | break; 726 | } 727 | /* Could be CMYK; probably broken */ 728 | i = n; 729 | break; 730 | case 39: 731 | term->pair = (DEF_FORE << 4) | (term->pair & 0x0F); 732 | break; 733 | #endif 734 | case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: 735 | term->pair = (term->pair & 0xF0) | (number - 40); 736 | break; 737 | #ifndef OLDLINUX 738 | case 48: 739 | i++ ; 740 | if (i>n) break; 741 | if (params[i] == 5) { 742 | /* 256 colours */ 743 | term->pair = (term->pair & 0xF0) | fake_colour_256(params[i+1]); 744 | i++; 745 | break; 746 | } else if (params[i] == 2) { 747 | /* 16M colours, note I'm using common form not strict ITU T.416 */ 748 | term->pair = (term->pair & 0xF0) | fake_colour_16m(params[i+1],params[i+2],params[i+3]); 749 | i+=3; 750 | break; 751 | } 752 | /* Could be CMYK; probably broken */ 753 | i = n; 754 | break; 755 | #endif 756 | case 49: 757 | term->pair = (term->pair & 0xF0) | DEF_BACK; 758 | break; 759 | case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: 760 | term->pair = ((number - 90 + 8) << 4) | (term->pair & 0x0F); 761 | break; 762 | case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: 763 | term->pair = (term->pair & 0xF0) | (number - 100 + 8); 764 | break; 765 | default: 766 | logfmt("UNS: SGR %d\n", number); 767 | } 768 | } 769 | } 770 | 771 | static void 772 | ctrlseq(Term *term, uint8_t byte) 773 | { 774 | int private; 775 | int n, k, k1; 776 | int params[MAX_PARAMS]; 777 | char *str; 778 | int ra, rb, ca, cb; 779 | int i, j; 780 | Cell cell; 781 | 782 | if (!within_bounds(term, term->row, term->col)) 783 | return; 784 | term->partial[term->parlen] = '\0'; 785 | if (*term->partial == '?') { 786 | private = 1; 787 | str = (char *) term->partial + 1; 788 | } else { 789 | private = 0; 790 | str = (char *) term->partial; 791 | } 792 | n = getparams(str, params, MAX_PARAMS); 793 | k = n ? *params : 0; 794 | k1 = k ? k : 1; 795 | switch (byte) { 796 | case '@': 797 | CLEARWRAP; 798 | /* TODO: insert the indicated # of blank characters */ 799 | logfmt("NYI: Control Sequence @ (ICH)\n"); 800 | break; 801 | case 'A': 802 | CLEARWRAP; 803 | term->row -= k1; 804 | CLIPROW(0); 805 | break; 806 | case 'B': case 'e': 807 | CLEARWRAP; 808 | term->row += k1; 809 | CLIPROW(term->rows-1); 810 | break; 811 | case 'C': case 'a': 812 | CLEARWRAP; 813 | term->col += k1; 814 | CLIPCOL(term->cols-1); 815 | break; 816 | case 'D': 817 | CLEARWRAP; 818 | term->col -= k1; 819 | CLIPCOL(0); 820 | break; 821 | case 'E': 822 | term->row += k1; 823 | term->col = 0; 824 | CLIPROW(term->rows-1); 825 | break; 826 | case 'F': 827 | term->row -= k1; 828 | term->col = 0; 829 | CLIPROW(0); 830 | break; 831 | case 'G': case '`': 832 | term->col = k1 - 1; 833 | CLIPCOL(term->cols-1); 834 | break; 835 | case 'H': case 'f': 836 | if (n == 2) { 837 | term->row = MAX(params[0], 1) - 1; 838 | term->col = MAX(params[1], 1) - 1; 839 | } else if (n == 1) { 840 | term->row = MAX(params[0], 1) - 1; 841 | term->col = 0; 842 | } else { 843 | term->row = term->col = 0; 844 | } 845 | if (term->mode & M_ORIGIN) 846 | term->row += term->top; 847 | CLIPROW(term->rows-1); 848 | CLIPCOL(term->cols-1); 849 | break; 850 | case 'J': 851 | CLEARWRAP; 852 | ra = 0; rb = term->rows - 1; 853 | ca = 0; cb = term->cols - 1; 854 | if (k == 0) { 855 | ra = term->row; 856 | ca = term->col; 857 | } else if (k == 1) { 858 | rb = term->row; 859 | cb = term->col; 860 | } 861 | for (j = ca; j < term->cols; j++) 862 | term->addr[ra][j] = BLANK; 863 | for (i = ra+1; i < rb; i++) { 864 | for (j = 0; j < term->cols; j++) { 865 | term->addr[i][j] = BLANK; 866 | } 867 | } 868 | for (j = 0; j <= cb; j++) 869 | term->addr[rb][j] = BLANK; 870 | break; 871 | case 'K': 872 | CLEARWRAP; 873 | ca = 0; cb = term->cols - 1; 874 | if (k == 0) 875 | ca = term->col; 876 | else if (k == 1) 877 | cb = term->col; 878 | for (j = ca; j <= cb; j++) 879 | term->addr[term->row][j] = BLANK; 880 | break; 881 | case 'L': 882 | CLEARWRAP; 883 | if (term->row < term->top || term->row > term->bot) 884 | break; 885 | /* This is implemented naively: 886 | 1. temporarily change the top margin to current row; 887 | 2. scroll up as many times as requested; 888 | 3. restore top margin to previous value. */ 889 | i = term->top; 890 | term->top = term->row; 891 | for (j = 0; j < k1; j++) 892 | scroll_up(term); 893 | term->top = i; 894 | break; 895 | case 'M': 896 | CLEARWRAP; 897 | if (term->row < term->top || term->row > term->bot) 898 | break; 899 | /* This is implemented naively: 900 | 1. temporarily change the top margin to current row; 901 | 2. scroll down as many times as requested; 902 | 3. restore top margin to previous value. */ 903 | /* TODO: 904 | vt102-ug says: 905 | "Lines added to bottom of screen have spaces with same character 906 | attributes as last line moved up." 907 | we need a more flexible scroll_down() to fix this. */ 908 | i = term->top; 909 | term->top = term->row; 910 | for (j = 0; j < k1; j++) 911 | scroll_down(term); 912 | term->top = i; 913 | break; 914 | case 'P': 915 | CLEARWRAP; 916 | cell = term->addr[term->row][term->cols-1]; 917 | cell.code = EMPTY; 918 | for (j = term->col; j < term->cols-k1; j++) 919 | term->addr[term->row][j] = term->addr[term->row][j+k1]; 920 | for (j = term->cols-k1; j < term->cols; j++) 921 | term->addr[term->row][j] = cell; 922 | break; 923 | case 'X': 924 | CLEARWRAP; 925 | for (j = 0; j < k1; j++) 926 | term->addr[term->row][term->col+j] = BLANK; 927 | break; 928 | case 'c': 929 | /* Device Attributes (DA) */ 930 | /* if we were a real terminal, we'd reply with "ESC [ ? 6 c" */ 931 | /* since there is no application listening, we can ignore this */ 932 | break; 933 | case 'd': 934 | CLEARWRAP; 935 | term->row = k1 - 1; 936 | break; 937 | case 'g': 938 | /* TODO: clear tab stop */ 939 | logfmt("NYI: Control Sequence g (TBC)\n"); 940 | break; 941 | case 'h': 942 | for (i = 0; i < n; i++) 943 | modeswitch(term, private, params[i], 1); 944 | break; 945 | case 'l': 946 | for (i = 0; i < n; i++) 947 | modeswitch(term, private, params[i], 0); 948 | break; 949 | case 'm': 950 | sgr(term, n, params); 951 | break; 952 | case 'n': 953 | /* Device Status Report (DSR) */ 954 | /* if we were a real terminal, we'd send a status reply (e.g. CPR) */ 955 | /* since there is no application listening, we can ignore this */ 956 | break; 957 | case 'q': 958 | /* TODO: set keyboard LEDs */ 959 | logfmt("NYI: Control Sequence q (DECLL)\n"); 960 | break; 961 | case 'r': 962 | if (n == 2) { 963 | term->top = MAX(params[0], 1) - 1; 964 | term->bot = MAX(params[1], 1) - 1; 965 | } else { 966 | term->top = 0; 967 | term->bot = term->rows - 1; 968 | } 969 | term->row = term->mode & M_ORIGIN ? term->top : 0; 970 | term->col = 0; 971 | break; 972 | case 's': 973 | save_cursor(term); 974 | break; 975 | case 'u': 976 | load_cursor(term); 977 | break; 978 | default: 979 | logfmt("UNS: Control Sequence %c\n", byte); 980 | } 981 | } 982 | 983 | #define CHARSET(T) ((T)->cs_array[(T)->cs_index]) 984 | #define PARCAT(T, B) ((T)->partial[(T)->parlen++] = (B)) 985 | #define RESET_STATE(T) do { (T)->state = S_ANY; (T)->parlen = 0; } while(0) 986 | #define CHARLEN(B) ((B) < 0xE0 ? 2 : ((B) < 0xF0 ? 3 : 4)) 987 | 988 | void 989 | parse(Term *term, uint8_t byte) 990 | { 991 | int es; 992 | 993 | /* Bad suffix for a unicode sequence, dump it and interpret this byte normally. */ 994 | if (term->state == S_UNI && (byte < 0x80 || byte >= 0xC0)) { 995 | addchar(term, 0xFFFD); 996 | RESET_STATE(term); 997 | } 998 | if (byte != 0x1B && byte < 0x20 && !(term->mode & M_DISPCTRL)) { 999 | ctrlchar(term, byte); 1000 | } else { 1001 | switch (term->state) { 1002 | case S_ANY: 1003 | switch (byte) { 1004 | case 0x1B: 1005 | term->state = S_ESC; 1006 | break; 1007 | case 0x9B: 1008 | term->state = S_CSI; 1009 | break; 1010 | default: 1011 | switch (CHARSET(term)) { 1012 | case CS_BMP: 1013 | if (byte < 0x80) { 1014 | /* single-byte UTF-8, i.e. ASCII */ 1015 | addchar(term, byte); 1016 | } else if (byte >= 0xC0) { 1017 | term->unilen = CHARLEN(byte); 1018 | PARCAT(term, byte); 1019 | term->state = S_UNI; 1020 | } else 1021 | addchar(term, 0xFFFD); 1022 | break; 1023 | case CS_ISO: 1024 | addchar(term, byte); 1025 | break; 1026 | case CS_VTG: 1027 | addchar(term, cs_vtg[byte]); 1028 | break; 1029 | case CS_437: 1030 | addchar(term, cs_437[byte]); 1031 | break; 1032 | } 1033 | } 1034 | break; 1035 | case S_OSCESC: case S_STRESC: 1036 | if (byte == '\\') { 1037 | /* do_osc_etc() */ 1038 | if (term->state == S_OSCESC) 1039 | logfmt("NYI: Operating System Sequence\n"); 1040 | } 1041 | RESET_STATE(term); 1042 | term->state = S_ESC; 1043 | /*FALLTHROUGH*/ 1044 | case S_ESC: 1045 | es = 1; 1046 | if (!term->parlen) { 1047 | if (byte == 0x5B) { 1048 | term->state = S_CSI; 1049 | es = 0; 1050 | } else if (byte == 0x5D) { 1051 | term->state = S_OSC; 1052 | es = 0; 1053 | } else if (byte == 'P' || /* DCS */ 1054 | byte == '_' || /* APC */ 1055 | byte == '^' || /* PM */ 1056 | byte == 'X' /* SOS */ 1057 | ) { 1058 | term->state = S_STR; /* A string to eat */ 1059 | es = 0; 1060 | } 1061 | } 1062 | if (es) { 1063 | if (byte >= 0x20 && byte < 0x30) { 1064 | PARCAT(term, byte); 1065 | } else { 1066 | escseq(term, byte); 1067 | RESET_STATE(term); 1068 | } 1069 | } 1070 | break; 1071 | case S_CSI: 1072 | if (byte < 0x40 || byte >= 0x7F) { 1073 | PARCAT(term, byte); 1074 | } else { 1075 | ctrlseq(term, byte); 1076 | RESET_STATE(term); 1077 | } 1078 | break; 1079 | case S_OSC: case S_STR: 1080 | /* TODO: set/reset palette entries */ 1081 | /* Currently this just eats the string */ 1082 | if (byte == 0x1B) { 1083 | if (term->state == S_OSC) 1084 | term->state = S_OSCESC; 1085 | else 1086 | term->state = S_STRESC; 1087 | } else if (byte == 13 || byte == 10) 1088 | RESET_STATE(term); /* CR or LF assume something broke */ 1089 | else if (byte == 7) { 1090 | /* do_osc_etc() */ 1091 | if (term->state == S_OSC) 1092 | logfmt("NYI: Operating System Sequence\n"); 1093 | RESET_STATE(term); 1094 | } else { 1095 | if (term->parlen < MAX_PARTIAL-3) 1096 | PARCAT(term, byte); 1097 | else 1098 | term->state = S_STR; 1099 | 1100 | if (term->partial[0] == 'P' && term->parlen == 8) { 1101 | if (do_linux_osc(term)) 1102 | RESET_STATE(term); 1103 | } else if (term->partial[0] == 'R' && term->parlen == 1) { 1104 | if (do_linux_osc(term)) 1105 | RESET_STATE(term); 1106 | } 1107 | } 1108 | break; 1109 | case S_UNI: 1110 | PARCAT(term, byte); 1111 | if (term->parlen == term->unilen) { 1112 | addchar(term, char_code(term)); 1113 | RESET_STATE(term); 1114 | } 1115 | break; 1116 | } 1117 | } 1118 | } 1119 | --------------------------------------------------------------------------------