| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| .libs | ||
| *.lo | ||
| *.la | ||
|
|
||
| bin/* | ||
| !bin/*.c | ||
|
|
||
| pangoterm | ||
| t/test | ||
| t/suites.h | ||
| t/externs.h | ||
| t/harness | ||
| src/encoding/*.inc |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| *~ | ||
| *.swp | ||
|
|
||
| tags | ||
| src/*.o | ||
| src/*.lo | ||
| src/encoding/*.inc | ||
|
|
||
| libvterm.la | ||
| bin/unterm | ||
| bin/vterm-ctrl | ||
| bin/vterm-dump | ||
|
|
||
| t/harness | ||
| t/harness.lo | ||
| t/harness.o | ||
|
|
||
| .libs/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
|
|
||
|
|
||
| The MIT License | ||
|
|
||
| Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk> | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in | ||
| all copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| THE SOFTWARE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| ifeq ($(shell uname),Darwin) | ||
| LIBTOOL ?= glibtool | ||
| else | ||
| LIBTOOL ?= libtool | ||
| endif | ||
|
|
||
| ifneq ($(VERBOSE),1) | ||
| LIBTOOL +=--quiet | ||
| endif | ||
|
|
||
| # override CFLAGS +=-Wall -Iinclude -std=c99 -DINLINE="static inline" -DUSE_INLINE | ||
| override CFLAGS +=-Wall -Iinclude -std=c90 -Wpedantic -DINLINE="" | ||
|
|
||
| ifeq ($(shell uname),SunOS) | ||
| override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX | ||
| endif | ||
|
|
||
| ifeq ($(DEBUG),1) | ||
| override CFLAGS +=-ggdb -DDEBUG | ||
| endif | ||
|
|
||
| ifeq ($(PROFILE),1) | ||
| override CFLAGS +=-pg | ||
| override LDFLAGS+=-pg | ||
| endif | ||
|
|
||
| CFILES=$(sort $(wildcard src/*.c)) | ||
| HFILES=$(sort $(wildcard include/*.h)) | ||
| OBJECTS=$(CFILES:.c=.lo) | ||
| LIBRARY=libvterm.la | ||
|
|
||
| BINFILES_SRC=$(sort $(wildcard bin/*.c)) | ||
| BINFILES=$(BINFILES_SRC:.c=) | ||
|
|
||
| TBLFILES=$(sort $(wildcard src/encoding/*.tbl)) | ||
| INCFILES=$(TBLFILES:.tbl=.inc) | ||
|
|
||
| HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES) | ||
|
|
||
| VERSION_MAJOR=0 | ||
| VERSION_MINOR=0 | ||
|
|
||
| VERSION_CURRENT=0 | ||
| VERSION_REVISION=0 | ||
| VERSION_AGE=0 | ||
|
|
||
| VERSION=0 | ||
|
|
||
| PREFIX=/usr/local | ||
| BINDIR=$(PREFIX)/bin | ||
| LIBDIR=$(PREFIX)/lib | ||
| INCDIR=$(PREFIX)/include | ||
| MANDIR=$(PREFIX)/share/man | ||
| MAN3DIR=$(MANDIR)/man3 | ||
|
|
||
| all: $(LIBRARY) $(BINFILES) | ||
|
|
||
| $(LIBRARY): $(OBJECTS) | ||
| @echo LINK $@ | ||
| @$(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS) | ||
|
|
||
| src/%.lo: src/%.c $(HFILES_INT) | ||
| @echo CC $< | ||
| @$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $< | ||
|
|
||
| src/encoding/%.inc: src/encoding/%.tbl | ||
| @echo TBL $< | ||
| @perl -CSD tbl2inc_c.pl $< >$@ | ||
|
|
||
| src/encoding.lo: $(INCFILES) | ||
|
|
||
| bin/%: bin/%.c $(LIBRARY) | ||
| @echo CC $< | ||
| @$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS) | ||
|
|
||
| t/harness.lo: t/harness.c $(HFILES) | ||
| @echo CC $< | ||
| @$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $< | ||
|
|
||
| t/harness: t/harness.lo $(LIBRARY) | ||
| @echo LINK $@ | ||
| @$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) | ||
|
|
||
| .PHONY: test | ||
| test: $(LIBRARY) t/harness | ||
| for T in `ls t/[0-9]*.test`; do echo "** $$T **"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done | ||
|
|
||
| .PHONY: clean | ||
| clean: | ||
| $(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES) | ||
| $(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness | ||
| $(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES) | ||
|
|
||
| .PHONY: install | ||
| install: install-inc install-lib install-bin | ||
|
|
||
| install-inc: | ||
| install -d $(DESTDIR)$(INCDIR) | ||
| install -m644 $(HFILES) $(DESTDIR)$(INCDIR) | ||
| install -d $(DESTDIR)$(LIBDIR)/pkgconfig | ||
| sed -e "s,@PREFIX@,$(PREFIX)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc | ||
|
|
||
| install-lib: $(LIBRARY) | ||
| install -d $(DESTDIR)$(LIBDIR) | ||
| $(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY) | ||
| $(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR) | ||
|
|
||
| install-bin: $(BINFILES) | ||
| install -d $(DESTDIR)$(BINDIR) | ||
| $(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/ | ||
|
|
||
| # DIST CUT | ||
|
|
||
| VERSION=$(VERSION_MAJOR).$(VERSION_MINOR) | ||
|
|
||
| DISTDIR=libvterm-$(VERSION) | ||
|
|
||
| distdir: $(INCFILES) | ||
| mkdir __distdir | ||
| cp LICENSE __distdir | ||
| mkdir __distdir/src | ||
| cp src/*.c src/*.h __distdir/src | ||
| mkdir __distdir/src/encoding | ||
| cp src/encoding/*.inc __distdir/src/encoding | ||
| mkdir __distdir/include | ||
| cp include/*.h __distdir/include | ||
| mkdir __distdir/bin | ||
| cp bin/*.c __distdir/bin | ||
| mkdir __distdir/t | ||
| cp t/*.test t/harness.c t/run-test.pl __distdir/t | ||
| sed "s,@VERSION@,$(VERSION)," <vterm.pc.in >__distdir/vterm.pc.in | ||
| sed "/^# DIST CUT/Q" <Makefile >__distdir/Makefile | ||
| mv __distdir $(DISTDIR) | ||
|
|
||
| TARBALL=$(DISTDIR).tar.gz | ||
|
|
||
| dist: distdir | ||
| tar -czf $(TARBALL) $(DISTDIR) | ||
| rm -rf $(DISTDIR) | ||
|
|
||
| dist+bzr: | ||
| $(MAKE) dist VERSION=$(VERSION)+bzr`bzr revno` | ||
|
|
||
| distdir+bzr: | ||
| $(MAKE) distdir VERSION=$(VERSION)+bzr`bzr revno` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| This is a MODIFIED version of libvterm. | ||
|
|
||
| The original can be found: | ||
| On the original site (tar archive and Bazaar repository): | ||
| http://www.leonerd.org.uk/code/libvterm/ | ||
| Cloned on Github: | ||
| https://github.com/neovim/libvterm | ||
|
|
||
| Modifications: | ||
| - Add a .gitignore file. | ||
| - Convert from C99 to C90. | ||
| - Other changes to support embedding in Vim. | ||
| - |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,287 @@ | ||
| #include <stdio.h> | ||
| #include <string.h> | ||
|
|
||
| #include <errno.h> | ||
| #include <fcntl.h> | ||
| #include <getopt.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include "vterm.h" | ||
|
|
||
| #define DEFINE_INLINES | ||
| #include "../src/utf8.h" /* fill_utf8 */ | ||
|
|
||
| #define streq(a,b) (!strcmp(a,b)) | ||
|
|
||
| static VTerm *vt; | ||
| static VTermScreen *vts; | ||
|
|
||
| static int cols; | ||
| static int rows; | ||
|
|
||
| static enum { | ||
| FORMAT_PLAIN, | ||
| FORMAT_SGR | ||
| } format = FORMAT_PLAIN; | ||
|
|
||
| static int col2index(VTermColor target) | ||
| { | ||
| int index; | ||
|
|
||
| for(index = 0; index < 256; index++) { | ||
| VTermColor col; | ||
| vterm_state_get_palette_color(NULL, index, &col); | ||
| if(col.red == target.red && col.green == target.green && col.blue == target.blue) | ||
| return index; | ||
| } | ||
| return -1; | ||
| } | ||
|
|
||
| static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell) | ||
| { | ||
| switch(format) { | ||
| case FORMAT_PLAIN: | ||
| break; | ||
| case FORMAT_SGR: | ||
| { | ||
| /* If all 7 attributes change, that means 7 SGRs max */ | ||
| /* Each colour could consume up to 3 */ | ||
| int sgr[7 + 2*3]; int sgri = 0; | ||
|
|
||
| if(!prevcell->attrs.bold && cell->attrs.bold) | ||
| sgr[sgri++] = 1; | ||
| if(prevcell->attrs.bold && !cell->attrs.bold) | ||
| sgr[sgri++] = 22; | ||
|
|
||
| if(!prevcell->attrs.underline && cell->attrs.underline) | ||
| sgr[sgri++] = 4; | ||
| if(prevcell->attrs.underline && !cell->attrs.underline) | ||
| sgr[sgri++] = 24; | ||
|
|
||
| if(!prevcell->attrs.italic && cell->attrs.italic) | ||
| sgr[sgri++] = 3; | ||
| if(prevcell->attrs.italic && !cell->attrs.italic) | ||
| sgr[sgri++] = 23; | ||
|
|
||
| if(!prevcell->attrs.blink && cell->attrs.blink) | ||
| sgr[sgri++] = 5; | ||
| if(prevcell->attrs.blink && !cell->attrs.blink) | ||
| sgr[sgri++] = 25; | ||
|
|
||
| if(!prevcell->attrs.reverse && cell->attrs.reverse) | ||
| sgr[sgri++] = 7; | ||
| if(prevcell->attrs.reverse && !cell->attrs.reverse) | ||
| sgr[sgri++] = 27; | ||
|
|
||
| if(!prevcell->attrs.strike && cell->attrs.strike) | ||
| sgr[sgri++] = 9; | ||
| if(prevcell->attrs.strike && !cell->attrs.strike) | ||
| sgr[sgri++] = 29; | ||
|
|
||
| if(!prevcell->attrs.font && cell->attrs.font) | ||
| sgr[sgri++] = 10 + cell->attrs.font; | ||
| if(prevcell->attrs.font && !cell->attrs.font) | ||
| sgr[sgri++] = 10; | ||
|
|
||
| if(prevcell->fg.red != cell->fg.red || | ||
| prevcell->fg.green != cell->fg.green || | ||
| prevcell->fg.blue != cell->fg.blue) { | ||
| int index = col2index(cell->fg); | ||
| if(index == -1) | ||
| sgr[sgri++] = 39; | ||
| else if(index < 8) | ||
| sgr[sgri++] = 30 + index; | ||
| else if(index < 16) | ||
| sgr[sgri++] = 90 + (index - 8); | ||
| else { | ||
| sgr[sgri++] = 38; | ||
| sgr[sgri++] = 5 | (1<<31); | ||
| sgr[sgri++] = index | (1<<31); | ||
| } | ||
| } | ||
|
|
||
| if(prevcell->bg.red != cell->bg.red || | ||
| prevcell->bg.green != cell->bg.green || | ||
| prevcell->bg.blue != cell->bg.blue) { | ||
| int index = col2index(cell->bg); | ||
| if(index == -1) | ||
| sgr[sgri++] = 49; | ||
| else if(index < 8) | ||
| sgr[sgri++] = 40 + index; | ||
| else if(index < 16) | ||
| sgr[sgri++] = 100 + (index - 8); | ||
| else { | ||
| sgr[sgri++] = 48; | ||
| sgr[sgri++] = 5 | (1<<31); | ||
| sgr[sgri++] = index | (1<<31); | ||
| } | ||
| } | ||
|
|
||
| if(!sgri) | ||
| break; | ||
|
|
||
| printf("\x1b["); | ||
| { | ||
| int i; | ||
| for(i = 0; i < sgri; i++) | ||
| printf(!i ? "%d" : | ||
| sgr[i] & (1<<31) ? ":%d" : | ||
| ";%d", | ||
| sgr[i] & ~(1<<31)); | ||
| } | ||
| printf("m"); | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| { | ||
| int i; | ||
| for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { | ||
| char bytes[6]; | ||
| bytes[fill_utf8(cell->chars[i], bytes)] = 0; | ||
| printf("%s", bytes); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static void dump_eol(const VTermScreenCell *prevcell) | ||
| { | ||
| switch(format) { | ||
| case FORMAT_PLAIN: | ||
| break; | ||
| case FORMAT_SGR: | ||
| if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic || | ||
| prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike || | ||
| prevcell->attrs.font) | ||
| printf("\x1b[m"); | ||
| break; | ||
| } | ||
|
|
||
| printf("\n"); | ||
| } | ||
|
|
||
| void dump_row(int row) | ||
| { | ||
| VTermPos pos; | ||
| VTermScreenCell prevcell; | ||
| pos.row = row; | ||
| pos.col = 0; | ||
| memset(&prevcell, 0, sizeof(prevcell)); | ||
| vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg); | ||
|
|
||
| while(pos.col < cols) { | ||
| VTermScreenCell cell; | ||
| vterm_screen_get_cell(vts, pos, &cell); | ||
|
|
||
| dump_cell(&cell, &prevcell); | ||
|
|
||
| pos.col += cell.width; | ||
| prevcell = cell; | ||
| } | ||
|
|
||
| dump_eol(&prevcell); | ||
| } | ||
|
|
||
| static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) | ||
| { | ||
| VTermScreenCell prevcell; | ||
| int col; | ||
|
|
||
| memset(&prevcell, 0, sizeof(prevcell)); | ||
| vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg); | ||
|
|
||
| for(col = 0; col < cols; col++) { | ||
| dump_cell(cells + col, &prevcell); | ||
| prevcell = cells[col]; | ||
| } | ||
|
|
||
| dump_eol(&prevcell); | ||
|
|
||
| return 1; | ||
| } | ||
|
|
||
| static int screen_resize(int new_rows, int new_cols, void *user) | ||
| { | ||
| rows = new_rows; | ||
| cols = new_cols; | ||
| return 1; | ||
| } | ||
|
|
||
| static VTermScreenCallbacks cb_screen = { | ||
| NULL, /* damage */ | ||
| NULL, /* moverect */ | ||
| NULL, /* movecursor */ | ||
| NULL, /* settermprop */ | ||
| NULL, /* bell */ | ||
| &screen_resize, /* resize */ | ||
| &screen_sb_pushline, /* sb_pushline */ | ||
| NULL, /* popline */ | ||
| }; | ||
|
|
||
| int main(int argc, char *argv[]) | ||
| { | ||
| int opt; | ||
| const char *file; | ||
| int fd; | ||
| int len; | ||
| char buffer[1024]; | ||
| int row; | ||
|
|
||
| rows = 25; | ||
| cols = 80; | ||
|
|
||
| while((opt = getopt(argc, argv, "f:l:c:")) != -1) { | ||
| switch(opt) { | ||
| case 'f': | ||
| if(streq(optarg, "plain")) | ||
| format = FORMAT_PLAIN; | ||
| else if(streq(optarg, "sgr")) | ||
| format = FORMAT_SGR; | ||
| else { | ||
| fprintf(stderr, "Unrecognised format '%s'\n", optarg); | ||
| exit(1); | ||
| } | ||
| break; | ||
|
|
||
| case 'l': | ||
| rows = atoi(optarg); | ||
| if(!rows) | ||
| rows = 25; | ||
| break; | ||
|
|
||
| case 'c': | ||
| cols = atoi(optarg); | ||
| if(!cols) | ||
| cols = 80; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| file = argv[optind++]; | ||
| fd = open(file, O_RDONLY); | ||
| if(fd == -1) { | ||
| fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno)); | ||
| exit(1); | ||
| } | ||
|
|
||
| vt = vterm_new(rows, cols); | ||
| vterm_set_utf8(vt, true); | ||
|
|
||
| vts = vterm_obtain_screen(vt); | ||
| vterm_screen_set_callbacks(vts, &cb_screen, NULL); | ||
|
|
||
| vterm_screen_reset(vts, 1); | ||
|
|
||
| while((len = read(fd, buffer, sizeof(buffer))) > 0) { | ||
| vterm_input_write(vt, buffer, len); | ||
| } | ||
|
|
||
| for(row = 0; row < rows; row++) { | ||
| dump_row(row); | ||
| } | ||
|
|
||
| close(fd); | ||
|
|
||
| vterm_free(vt); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,362 @@ | ||
| #define _XOPEN_SOURCE 500 /* strdup */ | ||
|
|
||
| #include <stdbool.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #define streq(a,b) (strcmp(a,b)==0) | ||
|
|
||
| #include <termios.h> | ||
|
|
||
| static char *getvalue(int *argip, int argc, char *argv[]) | ||
| { | ||
| if(*argip >= argc) { | ||
| fprintf(stderr, "Expected an option value\n"); | ||
| exit(1); | ||
| } | ||
|
|
||
| return argv[(*argip)++]; | ||
| } | ||
|
|
||
| static int getchoice(int *argip, int argc, char *argv[], const char *options[]) | ||
| { | ||
| const char *arg = getvalue(argip, argc, argv); | ||
|
|
||
| int value = -1; | ||
| while(options[++value]) | ||
| if(streq(arg, options[value])) | ||
| return value; | ||
|
|
||
| fprintf(stderr, "Unrecognised option value %s\n", arg); | ||
| exit(1); | ||
| } | ||
|
|
||
| typedef enum { | ||
| OFF, | ||
| ON, | ||
| QUERY | ||
| } BoolQuery; | ||
|
|
||
| static BoolQuery getboolq(int *argip, int argc, char *argv[]) | ||
| { | ||
| const char *choices[] = {"off", "on", "query", NULL}; | ||
| return getchoice(argip, argc, argv, choices); | ||
| } | ||
|
|
||
| static char *helptext[] = { | ||
| "reset", | ||
| "s8c1t [off|on]", | ||
| "keypad [app|num]", | ||
| "screen [off|on|query]", | ||
| "cursor [off|on|query]", | ||
| "curblink [off|on|query]", | ||
| "curshape [block|under|bar|query]", | ||
| "mouse [off|click|clickdrag|motion]", | ||
| "altscreen [off|on|query]", | ||
| "bracketpaste [off|on|query]", | ||
| "icontitle [STR]", | ||
| "icon [STR]", | ||
| "title [STR]", | ||
| NULL | ||
| }; | ||
|
|
||
| static bool seticanon(bool icanon, bool echo) | ||
| { | ||
| struct termios termios; | ||
|
|
||
| tcgetattr(0, &termios); | ||
|
|
||
| bool ret = (termios.c_lflag & ICANON); | ||
|
|
||
| if(icanon) termios.c_lflag |= ICANON; | ||
| else termios.c_lflag &= ~ICANON; | ||
|
|
||
| if(echo) termios.c_lflag |= ECHO; | ||
| else termios.c_lflag &= ~ECHO; | ||
|
|
||
| tcsetattr(0, TCSANOW, &termios); | ||
|
|
||
| return ret; | ||
| } | ||
|
|
||
| static void await_c1(int c1) | ||
| { | ||
| int c; | ||
|
|
||
| /* await CSI - 8bit or 2byte 7bit form */ | ||
| bool in_esc = false; | ||
| while((c = getchar())) { | ||
| if(c == c1) | ||
| break; | ||
| if(in_esc && c == (char)(c1 - 0x40)) | ||
| break; | ||
| if(!in_esc && c == 0x1b) | ||
| in_esc = true; | ||
| else | ||
| in_esc = false; | ||
| } | ||
| } | ||
|
|
||
| static char *read_csi() | ||
| { | ||
| unsigned char csi[32]; | ||
| int i = 0; | ||
|
|
||
| await_c1(0x9B); /* CSI */ | ||
|
|
||
| /* TODO: This really should be a more robust CSI parser | ||
| */ | ||
| for(; i < sizeof(csi)-1; i++) { | ||
| int c = csi[i] = getchar(); | ||
| if(c >= 0x40 && c <= 0x7e) | ||
| break; | ||
| } | ||
| csi[++i] = 0; | ||
|
|
||
| /* TODO: returns longer than 32? */ | ||
|
|
||
| return strdup((char *)csi); | ||
| } | ||
|
|
||
| static char *read_dcs() | ||
| { | ||
| unsigned char dcs[32]; | ||
| bool in_esc = false; | ||
| int i; | ||
|
|
||
| await_c1(0x90); | ||
|
|
||
| for(i = 0; i < sizeof(dcs)-1; ) { | ||
| char c = getchar(); | ||
| if(c == 0x9c) /* ST */ | ||
| break; | ||
| if(in_esc && c == 0x5c) | ||
| break; | ||
| if(!in_esc && c == 0x1b) | ||
| in_esc = true; | ||
| else { | ||
| dcs[i++] = c; | ||
| in_esc = false; | ||
| } | ||
| } | ||
| dcs[++i] = 0; | ||
|
|
||
| return strdup((char *)dcs); | ||
| } | ||
|
|
||
| static void usage(int exitcode) | ||
| { | ||
| char **p; | ||
|
|
||
| fprintf(stderr, "Control a libvterm-based terminal\n" | ||
| "\n" | ||
| "Options:\n"); | ||
|
|
||
| for(p = helptext; *p; p++) | ||
| fprintf(stderr, " %s\n", *p); | ||
|
|
||
| exit(exitcode); | ||
| } | ||
|
|
||
| static bool query_dec_mode(int mode) | ||
| { | ||
| char *s = NULL; | ||
|
|
||
| printf("\x1b[?%d$p", mode); | ||
|
|
||
| do { | ||
| int reply_mode, reply_value; | ||
| char reply_cmd; | ||
|
|
||
| if(s) | ||
| free(s); | ||
| s = read_csi(); | ||
|
|
||
| /* expect "?" mode ";" value "$y" */ | ||
|
|
||
| /* If the sscanf format string ends in a literal, we can't tell from | ||
| * its return value if it matches. Hence we'll %c the cmd and check it | ||
| * explicitly | ||
| */ | ||
| if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3) | ||
| continue; | ||
| if(reply_cmd != 'y') | ||
| continue; | ||
|
|
||
| if(reply_mode != mode) | ||
| continue; | ||
|
|
||
| free(s); | ||
|
|
||
| if(reply_value == 1 || reply_value == 3) | ||
| return true; | ||
| if(reply_value == 2 || reply_value == 4) | ||
| return false; | ||
|
|
||
| printf("Unrecognised reply to DECRQM: %d\n", reply_value); | ||
| return false; | ||
| } while(1); | ||
| } | ||
|
|
||
| static void do_dec_mode(int mode, BoolQuery val, const char *name) | ||
| { | ||
| switch(val) { | ||
| case OFF: | ||
| case ON: | ||
| printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l'); | ||
| break; | ||
|
|
||
| case QUERY: | ||
| if(query_dec_mode(mode)) | ||
| printf("%s on\n", name); | ||
| else | ||
| printf("%s off\n", name); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| static int query_rqss_numeric(char *cmd) | ||
| { | ||
| char *s = NULL; | ||
|
|
||
| printf("\x1bP$q%s\x1b\\", cmd); | ||
|
|
||
| do { | ||
| int num; | ||
|
|
||
| if(s) | ||
| free(s); | ||
| s = read_dcs(); | ||
|
|
||
| if(!s) | ||
| return -1; | ||
| if(strlen(s) < strlen(cmd)) | ||
| return -1; | ||
| if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) { | ||
| printf("No match\n"); | ||
| continue; | ||
| } | ||
|
|
||
| if(s[0] != '1' || s[1] != '$' || s[2] != 'r') | ||
| return -1; | ||
|
|
||
| if(sscanf(s + 3, "%d", &num) != 1) | ||
| return -1; | ||
|
|
||
| return num; | ||
| } while(1); | ||
| } | ||
|
|
||
| bool wasicanon; | ||
|
|
||
| void restoreicanon(void) | ||
| { | ||
| seticanon(wasicanon, true); | ||
| } | ||
|
|
||
| int main(int argc, char *argv[]) | ||
| { | ||
| int argi = 1; | ||
|
|
||
| if(argc == 1) | ||
| usage(0); | ||
|
|
||
| wasicanon = seticanon(false, false); | ||
| atexit(restoreicanon); | ||
|
|
||
| while(argi < argc) { | ||
| const char *arg = argv[argi++]; | ||
|
|
||
| if(streq(arg, "reset")) { | ||
| printf("\x1b" "c"); | ||
| } | ||
| else if(streq(arg, "s8c1t")) { | ||
| const char *choices[] = {"off", "on", NULL}; | ||
| switch(getchoice(&argi, argc, argv, choices)) { | ||
| case 0: | ||
| printf("\x1b F"); break; | ||
| case 1: | ||
| printf("\x1b G"); break; | ||
| } | ||
| } | ||
| else if(streq(arg, "keypad")) { | ||
| const char *choices[] = {"app", "num", NULL}; | ||
| switch(getchoice(&argi, argc, argv, choices)) { | ||
| case 0: | ||
| printf("\x1b="); break; | ||
| case 1: | ||
| printf("\x1b>"); break; | ||
| } | ||
| } | ||
| else if(streq(arg, "screen")) { | ||
| do_dec_mode(5, getboolq(&argi, argc, argv), "screen"); | ||
| } | ||
| else if(streq(arg, "cursor")) { | ||
| do_dec_mode(25, getboolq(&argi, argc, argv), "cursor"); | ||
| } | ||
| else if(streq(arg, "curblink")) { | ||
| do_dec_mode(12, getboolq(&argi, argc, argv), "curblink"); | ||
| } | ||
| else if(streq(arg, "curshape")) { | ||
| /* TODO: This ought to query the current value of DECSCUSR because it */ | ||
| /* may need blinking on or off */ | ||
| const char *choices[] = {"block", "under", "bar", "query", NULL}; | ||
| int shape = getchoice(&argi, argc, argv, choices); | ||
| switch(shape) { | ||
| case 3: /* query */ | ||
| shape = query_rqss_numeric(" q"); | ||
| switch(shape) { | ||
| case 1: case 2: | ||
| printf("curshape block\n"); | ||
| break; | ||
| case 3: case 4: | ||
| printf("curshape under\n"); | ||
| break; | ||
| case 5: case 6: | ||
| printf("curshape bar\n"); | ||
| break; | ||
| } | ||
| break; | ||
|
|
||
| case 0: | ||
| case 1: | ||
| case 2: | ||
| printf("\x1b[%d q", 1 + (shape * 2)); | ||
| break; | ||
| } | ||
| } | ||
| else if(streq(arg, "mouse")) { | ||
| const char *choices[] = {"off", "click", "clickdrag", "motion", NULL}; | ||
| switch(getchoice(&argi, argc, argv, choices)) { | ||
| case 0: | ||
| printf("\x1b[?1000l"); break; | ||
| case 1: | ||
| printf("\x1b[?1000h"); break; | ||
| case 2: | ||
| printf("\x1b[?1002h"); break; | ||
| case 3: | ||
| printf("\x1b[?1003h"); break; | ||
| } | ||
| } | ||
| else if(streq(arg, "altscreen")) { | ||
| do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen"); | ||
| } | ||
| else if(streq(arg, "bracketpaste")) { | ||
| do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste"); | ||
| } | ||
| else if(streq(arg, "icontitle")) { | ||
| printf("\x1b]0;%s\a", getvalue(&argi, argc, argv)); | ||
| } | ||
| else if(streq(arg, "icon")) { | ||
| printf("\x1b]1;%s\a", getvalue(&argi, argc, argv)); | ||
| } | ||
| else if(streq(arg, "title")) { | ||
| printf("\x1b]2;%s\a", getvalue(&argi, argc, argv)); | ||
| } | ||
| else { | ||
| fprintf(stderr, "Unrecognised command %s\n", arg); | ||
| exit(1); | ||
| } | ||
| } | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| /* Require getopt(3) */ | ||
| #define _XOPEN_SOURCE | ||
|
|
||
| #include <stdio.h> | ||
| #include <string.h> | ||
| #define streq(a,b) (strcmp(a,b)==0) | ||
|
|
||
| #include <errno.h> | ||
| #include <fcntl.h> | ||
| #include <sys/types.h> | ||
| #include <sys/stat.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include "vterm.h" | ||
|
|
||
| static const char *special_begin = "{"; | ||
| static const char *special_end = "}"; | ||
|
|
||
| static int parser_text(const char bytes[], size_t len, void *user) | ||
| { | ||
| unsigned char *b = (unsigned char *)bytes; | ||
|
|
||
| int i; | ||
| for(i = 0; i < len; /* none */) { | ||
| if(b[i] < 0x20) /* C0 */ | ||
| break; | ||
| else if(b[i] < 0x80) /* ASCII */ | ||
| i++; | ||
| else if(b[i] < 0xa0) /* C1 */ | ||
| break; | ||
| else if(b[i] < 0xc0) /* UTF-8 continuation */ | ||
| break; | ||
| else if(b[i] < 0xe0) { /* UTF-8 2-byte */ | ||
| /* 2-byte UTF-8 */ | ||
| if(len < i+2) break; | ||
| i += 2; | ||
| } | ||
| else if(b[i] < 0xf0) { /* UTF-8 3-byte */ | ||
| if(len < i+3) break; | ||
| i += 3; | ||
| } | ||
| else if(b[i] < 0xf8) { /* UTF-8 4-byte */ | ||
| if(len < i+4) break; | ||
| i += 4; | ||
| } | ||
| else /* otherwise invalid */ | ||
| break; | ||
| } | ||
|
|
||
| printf("%.*s", i, b); | ||
| return i; | ||
| } | ||
|
|
||
| /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ | ||
| static const char *name_c0[] = { | ||
| "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "LS0", "LS1", | ||
| "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", | ||
| }; | ||
| static const char *name_c1[] = { | ||
| NULL, NULL, "BPH", "NBH", NULL, "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3", | ||
| "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", NULL, "SCI", "CSI", "ST", "OSC", "PM", "APC", | ||
| }; | ||
|
|
||
| static int parser_control(unsigned char control, void *user) | ||
| { | ||
| if(control < 0x20) | ||
| printf("%s%s%s", special_begin, name_c0[control], special_end); | ||
| else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80]) | ||
| printf("%s%s%s", special_begin, name_c1[control - 0x80], special_end); | ||
| else | ||
| printf("%sCONTROL 0x%02x%s", special_begin, control, special_end); | ||
|
|
||
| if(control == 0x0a) | ||
| printf("\n"); | ||
| return 1; | ||
| } | ||
|
|
||
| static int parser_escape(const char bytes[], size_t len, void *user) | ||
| { | ||
| if(bytes[0] >= 0x20 && bytes[0] < 0x30) { | ||
| if(len < 2) | ||
| return -1; | ||
| len = 2; | ||
| } | ||
| else { | ||
| len = 1; | ||
| } | ||
|
|
||
| printf("%sESC %.*s%s", special_begin, (int)len, bytes, special_end); | ||
|
|
||
| return len; | ||
| } | ||
|
|
||
| /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ | ||
| static const char *name_csi_plain[] = { | ||
| "ICH", "CUU", "CUD", "CUF", "CUB", "CNL", "CPL", "CHA", "CUP", "CHT", "ED", "EL", "IL", "DL", "EF", "EA", | ||
| "DCH", "SSE", "CPR", "SU", "SD", "NP", "PP", "CTC", "ECH", "CVT", "CBT", "SRS", "PTX", "SDS", "SIMD",NULL, | ||
| "HPA", "HPR", "REP", "DA", "VPA", "VPR", "HVP", "TBC", "SM", "MC", "HPB", "VPB", "RM", "SGR", "DSR", "DAQ", | ||
| }; | ||
|
|
||
| /*0 4 8 B */ | ||
| static const int newline_csi_plain[] = { | ||
| 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, | ||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, | ||
| 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, | ||
| }; | ||
|
|
||
| static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) | ||
| { | ||
| const char *name = NULL; | ||
| if(!leader && !intermed && command < 0x70) | ||
| name = name_csi_plain[command - 0x40]; | ||
| else if(leader && streq(leader, "?") && !intermed) { | ||
| /* DEC */ | ||
| switch(command) { | ||
| case 'h': name = "DECSM"; break; | ||
| case 'l': name = "DECRM"; break; | ||
| } | ||
| if(name) | ||
| leader = NULL; | ||
| } | ||
|
|
||
| if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40]) | ||
| printf("\n"); | ||
|
|
||
| if(name) | ||
| printf("%s%s", special_begin, name); | ||
| else | ||
| printf("%sCSI", special_begin); | ||
|
|
||
| if(leader && leader[0]) | ||
| printf(" %s", leader); | ||
|
|
||
| { | ||
| int i; | ||
| for(i = 0; i < argcount; i++) { | ||
| printf(i ? "," : " "); | ||
| } | ||
|
|
||
| if(args[i] == CSI_ARG_MISSING) | ||
| printf("*"); | ||
| else { | ||
| while(CSI_ARG_HAS_MORE(args[i])) | ||
| printf("%ld+", CSI_ARG(args[i++])); | ||
| printf("%ld", CSI_ARG(args[i])); | ||
| } | ||
| } | ||
|
|
||
| if(intermed && intermed[0]) | ||
| printf(" %s", intermed); | ||
|
|
||
| if(name) | ||
| printf("%s", special_end); | ||
| else | ||
| printf(" %c%s", command, special_end); | ||
|
|
||
| return 1; | ||
| } | ||
|
|
||
| static int parser_osc(const char *command, size_t cmdlen, void *user) | ||
| { | ||
| printf("%sOSC %.*s%s", special_begin, (int)cmdlen, command, special_end); | ||
|
|
||
| return 1; | ||
| } | ||
|
|
||
| static int parser_dcs(const char *command, size_t cmdlen, void *user) | ||
| { | ||
| printf("%sDCS %.*s%s", special_begin, (int)cmdlen, command, special_end); | ||
|
|
||
| return 1; | ||
| } | ||
|
|
||
| static VTermParserCallbacks parser_cbs = { | ||
| &parser_text, /* text */ | ||
| &parser_control, /* control */ | ||
| &parser_escape, /* escape */ | ||
| &parser_csi, /* csi */ | ||
| &parser_osc, /* osc */ | ||
| &parser_dcs, /* dcs */ | ||
| NULL /* resize */ | ||
| }; | ||
|
|
||
| int main(int argc, char *argv[]) | ||
| { | ||
| int use_colour = isatty(1); | ||
| const char *file; | ||
| int fd; | ||
| VTerm *vt; | ||
| int len; | ||
| char buffer[1024]; | ||
|
|
||
| int opt; | ||
| while((opt = getopt(argc, argv, "c")) != -1) { | ||
| switch(opt) { | ||
| case 'c': use_colour = 1; break; | ||
| } | ||
| } | ||
|
|
||
| file = argv[optind++]; | ||
|
|
||
| if(!file || streq(file, "-")) | ||
| fd = 0; /* stdin */ | ||
| else { | ||
| fd = open(file, O_RDONLY); | ||
| if(fd == -1) { | ||
| fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno)); | ||
| exit(1); | ||
| } | ||
| } | ||
|
|
||
| if(use_colour) { | ||
| special_begin = "\x1b[7m{"; | ||
| special_end = "}\x1b[m"; | ||
| } | ||
|
|
||
| /* Size matters not for the parser */ | ||
| vt = vterm_new(25, 80); | ||
| vterm_set_utf8(vt, 1); | ||
| vterm_parser_set_callbacks(vt, &parser_cbs, NULL); | ||
|
|
||
| while((len = read(fd, buffer, sizeof(buffer))) > 0) { | ||
| vterm_input_write(vt, buffer, len); | ||
| } | ||
|
|
||
| printf("\n"); | ||
|
|
||
| close(fd); | ||
| vterm_free(vt); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| ECMA-48: | ||
| http://www.ecma-international.org/publications/standards/Ecma-048.htm | ||
|
|
||
| Xterm Control Sequences: | ||
| http://invisible-island.net/xterm/ctlseqs/ctlseqs.html | ||
|
|
||
| Digital VT100 User Guide: | ||
| http://vt100.net/docs/vt100-ug/ | ||
|
|
||
| Digital VT220 Programmer Reference Manual | ||
| http://vt100.net/docs/vt220-rm/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| Sequences documented in parens are implicit ones from parser.c, which move | ||
| between states. | ||
|
|
||
| 1 = VT100 | ||
| 2 = VT220 | ||
| 3 = VT320 | ||
|
|
||
| C0 controls | ||
|
|
||
| 123 0x00 = NUL | ||
| 123 0x07 = BEL | ||
| 123 0x08 = BS | ||
| 123 0x09 = HT | ||
| 123 0x0A = LF | ||
| 123 0x0B = VT | ||
| 123 0x0C = FF | ||
| 123 0x0D = CR | ||
| 123 0x0E = LS1 | ||
| 123 0x0F = LS0 | ||
| (0x18 = CAN) | ||
| (0x1A = SUB) | ||
| (0x1B = ESC) | ||
|
|
||
| 123 0x7f = DEL (ignored) | ||
|
|
||
| C1 controls | ||
|
|
||
| 123 0x84 = IND | ||
| 123 0x85 = NEL | ||
| 123 0x88 = HTS | ||
| 123 0x8D = RI | ||
| 23 0x8e = SS2 | ||
| 23 0x8f = SS3 | ||
| (0x90 = DCS) | ||
| (0x9B = CSI) | ||
| (0x9C = ST) | ||
| (0x9D = OSC) | ||
|
|
||
| Escape sequences | ||
| - excluding sequences that are C1 aliases | ||
|
|
||
| 123 ESC () = SCS, select character set (G0, G1) | ||
| 23 ESC *+ = SCS, select character set (G2, G3) | ||
| 123 ESC 7 = DECSC - save cursor | ||
| 123 ESC 8 = DECRC - restore cursor | ||
| 123 ESC # 3 = DECDHL, double-height line (top half) | ||
| 123 ESC # 4 = DECDHL, double-height line (bottom half) | ||
| 123 ESC # 5 = DECSWL, single-width single-height line | ||
| 123 ESC # 6 = DECDWL, double-width single-height line | ||
| 123 ESC # 8 = DECALN | ||
| 123 ESC < = Ignored (used by VT100 to exit VT52 mode) | ||
| 123 ESC = = DECKPAM, keypad application mode | ||
| 123 ESC > = DECKPNM, keypad numeric mode | ||
| 23 ESC Sp F = S7C1T | ||
| 23 ESC Sp G = S8C1T | ||
| (ESC P = DCS) | ||
| (ESC [ = CSI) | ||
| (ESC \ = ST) | ||
| (ESC ] = OSC) | ||
| 123 ESC c = RIS, reset initial state | ||
| 3 ESC n = LS2 | ||
| 3 ESC o = LS3 | ||
| 3 ESC ~ = LS1R | ||
| 3 ESC } = LS2R | ||
| 3 ESC | = LS3R | ||
|
|
||
| DCSes | ||
|
|
||
| 3 DCS $ q ST = DECRQSS | ||
| 3 m = Request SGR | ||
| Sp q = Request DECSCUSR | ||
| 3 " q = Request DECSCA | ||
| 3 r = Request DECSTBM | ||
| s = Request DECSLRM | ||
|
|
||
| CSIs | ||
| 23 CSI @ = ICH | ||
| 123 CSI A = CUU | ||
| 123 CSI B = CUD | ||
| 123 CSI C = CUF | ||
| 123 CSI D = CUB | ||
| CSI E = CNL | ||
| CSI F = CPL | ||
| CSI G = CHA | ||
| 123 CSI H = CUP | ||
| CSI I = CHT | ||
| 123 CSI J = ED | ||
| 23 CSI ? J = DECSED, selective erase in display | ||
| 123 CSI K = EL | ||
| 23 CSI ? K = DECSEL, selective erase in line | ||
| 23 CSI L = IL | ||
| 23 CSI M = DL | ||
| 23 CSI P = DCH | ||
| CSI S = SU | ||
| CSI T = SD | ||
| 23 CSI X = ECH | ||
| CSI Z = CBT | ||
| CSI ` = HPA | ||
| CSI a = HPR | ||
| 123 CSI c = DA, device attributes | ||
| 123 0 = DA | ||
| 23 CSI > c = DECSDA | ||
| 23 0 = SDA | ||
| CSI d = VPA | ||
| CSI e = VPR | ||
| 123 CSI f = HVP | ||
| 123 CSI g = TBC | ||
| 123 CSI h = SM, Set mode | ||
| 123 CSI ? h = DECSM, DEC set mode | ||
| CSI j = HPB | ||
| CSI k = VPB | ||
| 123 CSI l = RM, Reset mode | ||
| 123 CSI ? l = DECRM, DEC reset mode | ||
| 123 CSI m = SGR, Set Graphic Rendition | ||
| 123 CSI n = DSR, Device Status Report | ||
| 23 5 = operating status | ||
| 23 6 = CPR = cursor position | ||
| 23 CSI ? n = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond | ||
| 23 CSI ! p = DECSTR, soft terminal reset | ||
| 3 CSI ? $ p = DECRQM, request mode | ||
| CSI Sp q = DECSCUSR (odd numbers blink, even numbers solid) | ||
| 1 or 2 = block | ||
| 3 or 4 = underline | ||
| 5 or 6 = I-beam to left | ||
| 23 CSI " q = DECSCA, select character attributes | ||
| 123 CSI r = DECSTBM | ||
| CSI s = DECSLRM | ||
| CSI ' } = DECIC | ||
| CSI ' ~ = DECDC | ||
|
|
||
| OSCs | ||
|
|
||
| OSC 0; = Set icon name and title | ||
| OSC 1; = Set icon name | ||
| OSC 2; = Set title | ||
|
|
||
| Standard modes | ||
|
|
||
| 23 SM 4 = IRM | ||
| 123 SM 20 = NLM, linefeed/newline | ||
|
|
||
| DEC modes | ||
|
|
||
| 123 DECSM 1 = DECCKM, cursor keys | ||
| 123 DECSM 5 = DECSCNM, screen | ||
| 123 DECSM 6 = DECOM, origin | ||
| 123 DECSM 7 = DECAWM, autowrap | ||
| DECSM 12 = Cursor blink | ||
| 23 DECSM 25 = DECTCEM, text cursor enable | ||
| DECSM 69 = DECVSSM, vertical screen split | ||
| DECSM 1000 = Mouse click/release tracking | ||
| DECSM 1002 = Mouse click/release/drag tracking | ||
| DECSM 1003 = Mouse all movements tracking | ||
| DECSM 1005 = Mouse protocol extended (UTF-8) - not recommended | ||
| DECSM 1006 = Mouse protocol SGR | ||
| DECSM 1015 = Mouse protocol rxvt | ||
| DECSM 1047 = Altscreen | ||
| DECSM 1048 = Save cursor | ||
| DECSM 1049 = 1047 + 1048 | ||
| DECSM 2004 = Bracketed paste | ||
|
|
||
| Graphic Renditions | ||
|
|
||
| 123 SGR 0 = Reset | ||
| 123 SGR 1 = Bold on | ||
| SGR 3 = Italic on | ||
| 123 SGR 4 = Underline single | ||
| 123 SGR 5 = Blink on | ||
| 123 SGR 7 = Reverse on | ||
| SGR 9 = Strikethrough on | ||
| SGR 10-19 = Select font | ||
| SGR 21 = Underline double | ||
| 23 SGR 22 = Bold off | ||
| SGR 23 = Italic off | ||
| 23 SGR 24 = Underline off | ||
| 23 SGR 25 = Blink off | ||
| 23 SGR 27 = Reverse off | ||
| SGR 29 = Strikethrough off | ||
| SGR 30-37 = Foreground ANSI | ||
| SGR 38 = Foreground alternative palette | ||
| SGR 39 = Foreground default | ||
| SGR 40-47 = Background ANSI | ||
| SGR 48 = Background alternative palette | ||
| SGR 49 = Background default | ||
| SGR 90-97 = Foreground ANSI high-intensity | ||
| SGR 100-107 = Background ANSI high-intensity | ||
|
|
||
| The state storage used by ESC 7 and DECSM 1048/1049 is shared. | ||
|
|
||
| Unimplemented sequences: | ||
|
|
||
| The following sequences are not recognised by libvterm. | ||
|
|
||
| 123 0x05 = ENQ | ||
| 3 0x11 = DC1 (XON) | ||
| 3 0x13 = DC3 (XOFF) | ||
| 12 ESC Z = DECID, identify terminal | ||
| DCS $ q = [DECRQSS] | ||
| 3 " p = Request DECSCL | ||
| 3 $ } = Request DECSASD | ||
| 3 $ ~ = Request DECSSDT | ||
| 23 DCS { = DECDLD, down-line-loadable character set | ||
| 23 DCS | = DECUDK, user-defined key | ||
| 23 CSI i = DEC printer control | ||
| 23 CSI " p = DECSCL, set compatibility level | ||
| 1 CSI q = DECLL, load LEDs | ||
| 3 CSI $ u = DECRQTSR, request terminal state report | ||
| 3 1 = terminal state report | ||
| 3 CSI & u = DECRQUPSS, request user-preferred supplemental set | ||
| 3 CSI $ w = DECRQPSR, request presentation state report | ||
| 3 1 = cursor information report | ||
| 3 2 = tab stop report | ||
| 1 CSI x = DECREQTPARM, request terminal parameters | ||
| 123 CSI y = DECTST, invoke confidence test | ||
| 3 CSI $ } = DECSASD, select active status display | ||
| 3 CSI $ ~ = DECSSDT, select status line type | ||
| 23 SM 2 = KAM, keyboard action | ||
| 123 SM 12 = SRM, send/receive | ||
| 123 DECSM 2 = DECANM, ANSI/VT52 | ||
| 123 DECSM 3 = DECCOLM, 132 column | ||
| 123 DECSM 4 = DECSCLM, scrolling | ||
| 123 DECSM 8 = DECARM, auto-repeat | ||
| 12 DECSM 9 = DECINLM, interlace | ||
| 23 DECSM 18 = DECPFF, print form feed | ||
| 23 DECSM 19 = DECPEX, print extent | ||
| 23 DECSM 42 = DECNRCM, national/multinational character |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,370 @@ | ||
| /* | ||
| * NOTE: This is a MODIFIED version of libvterm, see the README file. | ||
| */ | ||
| #ifndef __VTERM_H__ | ||
| #define __VTERM_H__ | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| #include <stdint.h> | ||
| #include <stdlib.h> | ||
| #include <stdbool.h> | ||
|
|
||
| #include "vterm_keycodes.h" | ||
|
|
||
| typedef struct VTerm VTerm; | ||
| typedef struct VTermState VTermState; | ||
| typedef struct VTermScreen VTermScreen; | ||
|
|
||
| /* Specifies a screen point. */ | ||
| typedef struct { | ||
| int row; | ||
| int col; | ||
| } VTermPos; | ||
|
|
||
| /* | ||
| * Some small utility functions; we can just keep these static here. | ||
| */ | ||
|
|
||
| /* | ||
| * Order points by on-screen flow order: | ||
| * Return < 0 if "a" is before "b" | ||
| * Return 0 if "a" and "b" are equal | ||
| * Return > 0 if "a" is after "b". | ||
| */ | ||
| int vterm_pos_cmp(VTermPos a, VTermPos b); | ||
|
|
||
| #if defined(DEFINE_INLINES) || USE_INLINE | ||
| INLINE int vterm_pos_cmp(VTermPos a, VTermPos b) | ||
| { | ||
| return (a.row == b.row) ? a.col - b.col : a.row - b.row; | ||
| } | ||
| #endif | ||
|
|
||
| /* Specifies a rectangular screen area. */ | ||
| typedef struct { | ||
| int start_row; | ||
| int end_row; | ||
| int start_col; | ||
| int end_col; | ||
| } VTermRect; | ||
|
|
||
| /* Return true if the rect "r" contains the point "p". */ | ||
| int vterm_rect_contains(VTermRect r, VTermPos p); | ||
|
|
||
| #if defined(DEFINE_INLINES) || USE_INLINE | ||
| INLINE int vterm_rect_contains(VTermRect r, VTermPos p) | ||
| { | ||
| return p.row >= r.start_row && p.row < r.end_row && | ||
| p.col >= r.start_col && p.col < r.end_col; | ||
| } | ||
| #endif | ||
|
|
||
| /* Move "rect" "row_delta" down and "col_delta" right. | ||
| * Does not check boundaries. */ | ||
| void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta); | ||
|
|
||
| #if defined(DEFINE_INLINES) || USE_INLINE | ||
| INLINE void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) | ||
| { | ||
| rect->start_row += row_delta; rect->end_row += row_delta; | ||
| rect->start_col += col_delta; rect->end_col += col_delta; | ||
| } | ||
| #endif | ||
|
|
||
| typedef struct { | ||
| uint8_t red, green, blue; | ||
| } VTermColor; | ||
|
|
||
| typedef enum { | ||
| /* VTERM_VALUETYPE_NONE = 0 */ | ||
| VTERM_VALUETYPE_BOOL = 1, | ||
| VTERM_VALUETYPE_INT, | ||
| VTERM_VALUETYPE_STRING, | ||
| VTERM_VALUETYPE_COLOR | ||
| } VTermValueType; | ||
|
|
||
| typedef union { | ||
| int boolean; | ||
| int number; | ||
| char *string; | ||
| VTermColor color; | ||
| } VTermValue; | ||
|
|
||
| typedef enum { | ||
| /* VTERM_ATTR_NONE = 0 */ | ||
| VTERM_ATTR_BOLD = 1, /* bool: 1, 22 */ | ||
| VTERM_ATTR_UNDERLINE, /* number: 4, 21, 24 */ | ||
| VTERM_ATTR_ITALIC, /* bool: 3, 23 */ | ||
| VTERM_ATTR_BLINK, /* bool: 5, 25 */ | ||
| VTERM_ATTR_REVERSE, /* bool: 7, 27 */ | ||
| VTERM_ATTR_STRIKE, /* bool: 9, 29 */ | ||
| VTERM_ATTR_FONT, /* number: 10-19 */ | ||
| VTERM_ATTR_FOREGROUND, /* color: 30-39 90-97 */ | ||
| VTERM_ATTR_BACKGROUND /* color: 40-49 100-107 */ | ||
| } VTermAttr; | ||
|
|
||
| typedef enum { | ||
| /* VTERM_PROP_NONE = 0 */ | ||
| VTERM_PROP_CURSORVISIBLE = 1, /* bool */ | ||
| VTERM_PROP_CURSORBLINK, /* bool */ | ||
| VTERM_PROP_ALTSCREEN, /* bool */ | ||
| VTERM_PROP_TITLE, /* string */ | ||
| VTERM_PROP_ICONNAME, /* string */ | ||
| VTERM_PROP_REVERSE, /* bool */ | ||
| VTERM_PROP_CURSORSHAPE, /* number */ | ||
| VTERM_PROP_MOUSE /* number */ | ||
| } VTermProp; | ||
|
|
||
| enum { | ||
| VTERM_PROP_CURSORSHAPE_BLOCK = 1, | ||
| VTERM_PROP_CURSORSHAPE_UNDERLINE, | ||
| VTERM_PROP_CURSORSHAPE_BAR_LEFT | ||
| }; | ||
|
|
||
| enum { | ||
| VTERM_PROP_MOUSE_NONE = 0, | ||
| VTERM_PROP_MOUSE_CLICK, | ||
| VTERM_PROP_MOUSE_DRAG, | ||
| VTERM_PROP_MOUSE_MOVE | ||
| }; | ||
|
|
||
| typedef struct { | ||
| const uint32_t *chars; | ||
| int width; | ||
| unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ | ||
| unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ | ||
| unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ | ||
| } VTermGlyphInfo; | ||
|
|
||
| typedef struct { | ||
| unsigned int doublewidth:1; /* DECDWL or DECDHL line */ | ||
| unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ | ||
| } VTermLineInfo; | ||
|
|
||
| typedef struct { | ||
| /* libvterm relies on the allocated memory to be zeroed out before it is | ||
| * returned by the allocator. */ | ||
| void *(*malloc)(size_t size, void *allocdata); | ||
| void (*free)(void *ptr, void *allocdata); | ||
| } VTermAllocatorFunctions; | ||
|
|
||
| /* Allocate and initialize a new terminal with default allocators. */ | ||
| VTerm *vterm_new(int rows, int cols); | ||
|
|
||
| /* Allocate and initialize a new terminal with specified allocators. */ | ||
| VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); | ||
|
|
||
| /* Free and cleanup a terminal and all its data. */ | ||
| void vterm_free(VTerm* vt); | ||
|
|
||
| void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); | ||
| void vterm_set_size(VTerm *vt, int rows, int cols); | ||
|
|
||
| int vterm_get_utf8(const VTerm *vt); | ||
| void vterm_set_utf8(VTerm *vt, int is_utf8); | ||
|
|
||
| size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); | ||
|
|
||
| size_t vterm_output_get_buffer_size(const VTerm *vt); | ||
| size_t vterm_output_get_buffer_current(const VTerm *vt); | ||
| size_t vterm_output_get_buffer_remaining(const VTerm *vt); | ||
|
|
||
| size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); | ||
|
|
||
| void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); | ||
| void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); | ||
|
|
||
| void vterm_keyboard_start_paste(VTerm *vt); | ||
| void vterm_keyboard_end_paste(VTerm *vt); | ||
|
|
||
| void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); | ||
| void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); | ||
|
|
||
| /* ------------ | ||
| * Parser layer | ||
| * ------------ */ | ||
|
|
||
| /* Flag to indicate non-final subparameters in a single CSI parameter. | ||
| * Consider | ||
| * CSI 1;2:3:4;5a | ||
| * 1 4 and 5 are final. | ||
| * 2 and 3 are non-final and will have this bit set | ||
| * | ||
| * Don't confuse this with the final byte of the CSI escape; 'a' in this case. | ||
| */ | ||
| #define CSI_ARG_FLAG_MORE (1<<31) | ||
| #define CSI_ARG_MASK (~(1<<31)) | ||
|
|
||
| #define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) | ||
| #define CSI_ARG(a) ((a) & CSI_ARG_MASK) | ||
|
|
||
| /* Can't use -1 to indicate a missing argument; use this instead */ | ||
| #define CSI_ARG_MISSING ((1UL<<31)-1) | ||
|
|
||
| #define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) | ||
| #define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) | ||
| #define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) | ||
|
|
||
| typedef struct { | ||
| int (*text)(const char *bytes, size_t len, void *user); | ||
| int (*control)(unsigned char control, void *user); | ||
| int (*escape)(const char *bytes, size_t len, void *user); | ||
| int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); | ||
| int (*osc)(const char *command, size_t cmdlen, void *user); | ||
| int (*dcs)(const char *command, size_t cmdlen, void *user); | ||
| int (*resize)(int rows, int cols, void *user); | ||
| } VTermParserCallbacks; | ||
|
|
||
| void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); | ||
| void *vterm_parser_get_cbdata(VTerm *vt); | ||
|
|
||
| /* ----------- | ||
| * State layer | ||
| * ----------- */ | ||
|
|
||
| typedef struct { | ||
| int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); | ||
| int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); | ||
| int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); | ||
| int (*moverect)(VTermRect dest, VTermRect src, void *user); | ||
| int (*erase)(VTermRect rect, int selective, void *user); | ||
| int (*initpen)(void *user); | ||
| int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); | ||
| int (*settermprop)(VTermProp prop, VTermValue *val, void *user); | ||
| int (*bell)(void *user); | ||
| int (*resize)(int rows, int cols, VTermPos *delta, void *user); | ||
| int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); | ||
| } VTermStateCallbacks; | ||
|
|
||
| VTermState *vterm_obtain_state(VTerm *vt); | ||
|
|
||
| void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); | ||
| void *vterm_state_get_cbdata(VTermState *state); | ||
|
|
||
| /* Only invokes control, csi, osc, dcs */ | ||
| void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user); | ||
| void *vterm_state_get_unrecognised_fbdata(VTermState *state); | ||
|
|
||
| void vterm_state_reset(VTermState *state, int hard); | ||
| void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); | ||
| void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); | ||
| void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); | ||
| void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); | ||
| void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); | ||
| void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); | ||
| int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); | ||
| int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); | ||
| const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); | ||
|
|
||
| /* ------------ | ||
| * Screen layer | ||
| * ------------ */ | ||
|
|
||
| typedef struct { | ||
| unsigned int bold : 1; | ||
| unsigned int underline : 2; | ||
| unsigned int italic : 1; | ||
| unsigned int blink : 1; | ||
| unsigned int reverse : 1; | ||
| unsigned int strike : 1; | ||
| unsigned int font : 4; /* 0 to 9 */ | ||
| unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ | ||
| unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ | ||
| } VTermScreenCellAttrs; | ||
|
|
||
| typedef struct { | ||
| #define VTERM_MAX_CHARS_PER_CELL 6 | ||
| uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; | ||
| char width; | ||
| VTermScreenCellAttrs attrs; | ||
| VTermColor fg, bg; | ||
| } VTermScreenCell; | ||
|
|
||
| /* All fields are optional, NULL when not used. */ | ||
| typedef struct { | ||
| int (*damage)(VTermRect rect, void *user); | ||
| int (*moverect)(VTermRect dest, VTermRect src, void *user); | ||
| int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); | ||
| int (*settermprop)(VTermProp prop, VTermValue *val, void *user); | ||
| int (*bell)(void *user); | ||
| int (*resize)(int rows, int cols, void *user); | ||
| int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); | ||
| int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); | ||
| } VTermScreenCallbacks; | ||
|
|
||
| VTermScreen *vterm_obtain_screen(VTerm *vt); | ||
|
|
||
| /* | ||
| * Install screen callbacks. These are invoked when the screen contents is | ||
| * changed. "user" is passed into to the callback. | ||
| */ | ||
| void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); | ||
| void *vterm_screen_get_cbdata(VTermScreen *screen); | ||
|
|
||
| /* Only invokes control, csi, osc, dcs */ | ||
| void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user); | ||
| void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); | ||
|
|
||
| void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); | ||
|
|
||
| typedef enum { | ||
| VTERM_DAMAGE_CELL, /* every cell */ | ||
| VTERM_DAMAGE_ROW, /* entire rows */ | ||
| VTERM_DAMAGE_SCREEN, /* entire screen */ | ||
| VTERM_DAMAGE_SCROLL /* entire screen + scrollrect */ | ||
| } VTermDamageSize; | ||
|
|
||
| void vterm_screen_flush_damage(VTermScreen *screen); | ||
| void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); | ||
|
|
||
| void vterm_screen_reset(VTermScreen *screen, int hard); | ||
|
|
||
| /* Neither of these functions NUL-terminate the buffer */ | ||
| size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); | ||
| size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); | ||
|
|
||
| typedef enum { | ||
| VTERM_ATTR_BOLD_MASK = 1 << 0, | ||
| VTERM_ATTR_UNDERLINE_MASK = 1 << 1, | ||
| VTERM_ATTR_ITALIC_MASK = 1 << 2, | ||
| VTERM_ATTR_BLINK_MASK = 1 << 3, | ||
| VTERM_ATTR_REVERSE_MASK = 1 << 4, | ||
| VTERM_ATTR_STRIKE_MASK = 1 << 5, | ||
| VTERM_ATTR_FONT_MASK = 1 << 6, | ||
| VTERM_ATTR_FOREGROUND_MASK = 1 << 7, | ||
| VTERM_ATTR_BACKGROUND_MASK = 1 << 8 | ||
| } VTermAttrMask; | ||
|
|
||
| int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); | ||
|
|
||
| int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); | ||
|
|
||
| int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); | ||
|
|
||
| /* --------- | ||
| * Utilities | ||
| * --------- */ | ||
|
|
||
| VTermValueType vterm_get_attr_type(VTermAttr attr); | ||
| VTermValueType vterm_get_prop_type(VTermProp prop); | ||
|
|
||
| void vterm_scroll_rect(VTermRect rect, | ||
| int downward, | ||
| int rightward, | ||
| int (*moverect)(VTermRect src, VTermRect dest, void *user), | ||
| int (*eraserect)(VTermRect rect, int selective, void *user), | ||
| void *user); | ||
|
|
||
| void vterm_copy_cells(VTermRect dest, | ||
| VTermRect src, | ||
| void (*copycell)(VTermPos dest, VTermPos src, void *user), | ||
| void *user); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #ifndef __VTERM_INPUT_H__ | ||
| #define __VTERM_INPUT_H__ | ||
|
|
||
| typedef enum { | ||
| VTERM_MOD_NONE = 0x00, | ||
| VTERM_MOD_SHIFT = 0x01, | ||
| VTERM_MOD_ALT = 0x02, | ||
| VTERM_MOD_CTRL = 0x04 | ||
| } VTermModifier; | ||
|
|
||
| typedef enum { | ||
| VTERM_KEY_NONE, | ||
|
|
||
| VTERM_KEY_ENTER, | ||
| VTERM_KEY_TAB, | ||
| VTERM_KEY_BACKSPACE, | ||
| VTERM_KEY_ESCAPE, | ||
|
|
||
| VTERM_KEY_UP, | ||
| VTERM_KEY_DOWN, | ||
| VTERM_KEY_LEFT, | ||
| VTERM_KEY_RIGHT, | ||
|
|
||
| VTERM_KEY_INS, | ||
| VTERM_KEY_DEL, | ||
| VTERM_KEY_HOME, | ||
| VTERM_KEY_END, | ||
| VTERM_KEY_PAGEUP, | ||
| VTERM_KEY_PAGEDOWN, | ||
|
|
||
| VTERM_KEY_FUNCTION_0 = 256, | ||
| VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, | ||
|
|
||
| VTERM_KEY_KP_0, | ||
| VTERM_KEY_KP_1, | ||
| VTERM_KEY_KP_2, | ||
| VTERM_KEY_KP_3, | ||
| VTERM_KEY_KP_4, | ||
| VTERM_KEY_KP_5, | ||
| VTERM_KEY_KP_6, | ||
| VTERM_KEY_KP_7, | ||
| VTERM_KEY_KP_8, | ||
| VTERM_KEY_KP_9, | ||
| VTERM_KEY_KP_MULT, | ||
| VTERM_KEY_KP_PLUS, | ||
| VTERM_KEY_KP_COMMA, | ||
| VTERM_KEY_KP_MINUS, | ||
| VTERM_KEY_KP_PERIOD, | ||
| VTERM_KEY_KP_DIVIDE, | ||
| VTERM_KEY_KP_ENTER, | ||
| VTERM_KEY_KP_EQUAL, | ||
|
|
||
| VTERM_KEY_MAX /* Must be last */ | ||
| } VTermKey; | ||
|
|
||
| #define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| #include "vterm_internal.h" | ||
|
|
||
| #define UNICODE_INVALID 0xFFFD | ||
|
|
||
| #if defined(DEBUG) && DEBUG > 1 | ||
| # define DEBUG_PRINT_UTF8 | ||
| #endif | ||
|
|
||
| struct UTF8DecoderData { | ||
| /* number of bytes remaining in this codepoint */ | ||
| int bytes_remaining; | ||
|
|
||
| /* number of bytes total in this codepoint once it's finished | ||
| (for detecting overlongs) */ | ||
| int bytes_total; | ||
|
|
||
| int this_cp; | ||
| }; | ||
|
|
||
| static void init_utf8(VTermEncoding *enc UNUSED, void *data_) | ||
| { | ||
| struct UTF8DecoderData *data = data_; | ||
|
|
||
| data->bytes_remaining = 0; | ||
| data->bytes_total = 0; | ||
| } | ||
|
|
||
| static void decode_utf8(VTermEncoding *enc UNUSED, void *data_, | ||
| uint32_t cp[], int *cpi, int cplen, | ||
| const char bytes[], size_t *pos, size_t bytelen) | ||
| { | ||
| struct UTF8DecoderData *data = data_; | ||
|
|
||
| #ifdef DEBUG_PRINT_UTF8 | ||
| printf("BEGIN UTF-8\n"); | ||
| #endif | ||
|
|
||
| for(; *pos < bytelen && *cpi < cplen; (*pos)++) { | ||
| unsigned char c = bytes[*pos]; | ||
|
|
||
| #ifdef DEBUG_PRINT_UTF8 | ||
| printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); | ||
| #endif | ||
|
|
||
| if(c < 0x20) /* C0 */ | ||
| return; | ||
|
|
||
| else if(c >= 0x20 && c < 0x7f) { | ||
| if(data->bytes_remaining) | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
|
|
||
| cp[(*cpi)++] = c; | ||
| #ifdef DEBUG_PRINT_UTF8 | ||
| printf(" UTF-8 char: U+%04x\n", c); | ||
| #endif | ||
| data->bytes_remaining = 0; | ||
| } | ||
|
|
||
| else if(c == 0x7f) /* DEL */ | ||
| return; | ||
|
|
||
| else if(c >= 0x80 && c < 0xc0) { | ||
| if(!data->bytes_remaining) { | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
| continue; | ||
| } | ||
|
|
||
| data->this_cp <<= 6; | ||
| data->this_cp |= c & 0x3f; | ||
| data->bytes_remaining--; | ||
|
|
||
| if(!data->bytes_remaining) { | ||
| #ifdef DEBUG_PRINT_UTF8 | ||
| printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); | ||
| #endif | ||
| /* Check for overlong sequences */ | ||
| switch(data->bytes_total) { | ||
| case 2: | ||
| if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; | ||
| break; | ||
| case 3: | ||
| if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; | ||
| break; | ||
| case 4: | ||
| if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; | ||
| break; | ||
| case 5: | ||
| if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; | ||
| break; | ||
| case 6: | ||
| if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; | ||
| break; | ||
| } | ||
| /* Now look for plain invalid ones */ | ||
| if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || | ||
| data->this_cp == 0xFFFE || | ||
| data->this_cp == 0xFFFF) | ||
| data->this_cp = UNICODE_INVALID; | ||
| #ifdef DEBUG_PRINT_UTF8 | ||
| printf(" char: U+%04x\n", data->this_cp); | ||
| #endif | ||
| cp[(*cpi)++] = data->this_cp; | ||
| } | ||
| } | ||
|
|
||
| else if(c >= 0xc0 && c < 0xe0) { | ||
| if(data->bytes_remaining) | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
|
|
||
| data->this_cp = c & 0x1f; | ||
| data->bytes_total = 2; | ||
| data->bytes_remaining = 1; | ||
| } | ||
|
|
||
| else if(c >= 0xe0 && c < 0xf0) { | ||
| if(data->bytes_remaining) | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
|
|
||
| data->this_cp = c & 0x0f; | ||
| data->bytes_total = 3; | ||
| data->bytes_remaining = 2; | ||
| } | ||
|
|
||
| else if(c >= 0xf0 && c < 0xf8) { | ||
| if(data->bytes_remaining) | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
|
|
||
| data->this_cp = c & 0x07; | ||
| data->bytes_total = 4; | ||
| data->bytes_remaining = 3; | ||
| } | ||
|
|
||
| else if(c >= 0xf8 && c < 0xfc) { | ||
| if(data->bytes_remaining) | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
|
|
||
| data->this_cp = c & 0x03; | ||
| data->bytes_total = 5; | ||
| data->bytes_remaining = 4; | ||
| } | ||
|
|
||
| else if(c >= 0xfc && c < 0xfe) { | ||
| if(data->bytes_remaining) | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
|
|
||
| data->this_cp = c & 0x01; | ||
| data->bytes_total = 6; | ||
| data->bytes_remaining = 5; | ||
| } | ||
|
|
||
| else { | ||
| cp[(*cpi)++] = UNICODE_INVALID; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static VTermEncoding encoding_utf8 = { | ||
| &init_utf8, /* init */ | ||
| &decode_utf8 /* decode */ | ||
| }; | ||
|
|
||
| static void decode_usascii(VTermEncoding *enc UNUSED, void *data UNUSED, | ||
| uint32_t cp[], int *cpi, int cplen, | ||
| const char bytes[], size_t *pos, size_t bytelen) | ||
| { | ||
| int is_gr = bytes[*pos] & 0x80; | ||
|
|
||
| for(; *pos < bytelen && *cpi < cplen; (*pos)++) { | ||
| unsigned char c = bytes[*pos] ^ is_gr; | ||
|
|
||
| if(c < 0x20 || c == 0x7f || c >= 0x80) | ||
| return; | ||
|
|
||
| cp[(*cpi)++] = c; | ||
| } | ||
| } | ||
|
|
||
| static VTermEncoding encoding_usascii = { | ||
| NULL, /* init */ | ||
| &decode_usascii /* decode */ | ||
| }; | ||
|
|
||
| struct StaticTableEncoding { | ||
| const VTermEncoding enc; | ||
| const uint32_t chars[128]; | ||
| }; | ||
|
|
||
| static void decode_table(VTermEncoding *enc, void *data UNUSED, | ||
| uint32_t cp[], int *cpi, int cplen, | ||
| const char bytes[], size_t *pos, size_t bytelen) | ||
| { | ||
| struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; | ||
| int is_gr = bytes[*pos] & 0x80; | ||
|
|
||
| for(; *pos < bytelen && *cpi < cplen; (*pos)++) { | ||
| unsigned char c = bytes[*pos] ^ is_gr; | ||
|
|
||
| if(c < 0x20 || c == 0x7f || c >= 0x80) | ||
| return; | ||
|
|
||
| if(table->chars[c]) | ||
| cp[(*cpi)++] = table->chars[c]; | ||
| else | ||
| cp[(*cpi)++] = c; | ||
| } | ||
| } | ||
|
|
||
| #include "encoding/DECdrawing.inc" | ||
| #include "encoding/uk.inc" | ||
|
|
||
| static struct { | ||
| VTermEncodingType type; | ||
| char designation; | ||
| VTermEncoding *enc; | ||
| } | ||
| encodings[] = { | ||
| { ENC_UTF8, 'u', &encoding_utf8 }, | ||
| { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing }, | ||
| { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk }, | ||
| { ENC_SINGLE_94, 'B', &encoding_usascii }, | ||
| { 0 }, | ||
| }; | ||
|
|
||
| /* This ought to be INTERNAL but isn't because it's used by unit testing */ | ||
| VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) | ||
| { | ||
| int i; | ||
| for(i = 0; encodings[i].designation; i++) | ||
| if(encodings[i].type == type && encodings[i].designation == designation) | ||
| return encodings[i].enc; | ||
| return NULL; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| 6/0 = U+25C6 # BLACK DIAMOND | ||
| 6/1 = U+2592 # MEDIUM SHADE (checkerboard) | ||
| 6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB | ||
| 6/3 = U+240C # SYMBOL FOR FORM FEED | ||
| 6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN | ||
| 6/5 = U+240A # SYMBOL FOR LINE FEED | ||
| 6/6 = U+00B0 # DEGREE SIGN | ||
| 6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus) | ||
| 6/8 = U+2424 # SYMBOL FOR NEW LINE | ||
| 6/9 = U+240B # SYMBOL FOR VERTICAL TAB | ||
| 6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner) | ||
| 6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner) | ||
| 6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner) | ||
| 6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner) | ||
| 6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines) | ||
| 6/15 = U+23BA # HORIZONTAL SCAN LINE-1 | ||
| 7/0 = U+23BB # HORIZONTAL SCAN LINE-3 | ||
| 7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL | ||
| 7/2 = U+23BC # HORIZONTAL SCAN LINE-7 | ||
| 7/3 = U+23BD # HORIZONTAL SCAN LINE-9 | ||
| 7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT | ||
| 7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT | ||
| 7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL | ||
| 7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL | ||
| 7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL | ||
| 7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO | ||
| 7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO | ||
| 7/11 = U+03C0 # GREEK SMALL LETTER PI | ||
| 7/12 = U+2260 # NOT EQUAL TO | ||
| 7/13 = U+00A3 # POUND SIGN | ||
| 7/14 = U+00B7 # MIDDLE DOT |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 2/3 = "£" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| #include "vterm_internal.h" | ||
|
|
||
| #include <stdio.h> | ||
|
|
||
| #include "utf8.h" | ||
|
|
||
| void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) | ||
| { | ||
| int needs_CSIu; | ||
|
|
||
| /* The shift modifier is never important for Unicode characters | ||
| * apart from Space | ||
| */ | ||
| if(c != ' ') | ||
| mod &= ~VTERM_MOD_SHIFT; | ||
|
|
||
| if(mod == 0) { | ||
| /* Normal text - ignore just shift */ | ||
| char str[6]; | ||
| int seqlen = fill_utf8(c, str); | ||
| vterm_push_output_bytes(vt, str, seqlen); | ||
| return; | ||
| } | ||
|
|
||
| switch(c) { | ||
| /* Special Ctrl- letters that can't be represented elsewise */ | ||
| case 'i': case 'j': case 'm': case '[': | ||
| needs_CSIu = 1; | ||
| break; | ||
| /* Ctrl-\ ] ^ _ don't need CSUu */ | ||
| case '\\': case ']': case '^': case '_': | ||
| needs_CSIu = 0; | ||
| break; | ||
| /* Shift-space needs CSIu */ | ||
| case ' ': | ||
| needs_CSIu = !!(mod & VTERM_MOD_SHIFT); | ||
| break; | ||
| /* All other characters needs CSIu except for letters a-z */ | ||
| default: | ||
| needs_CSIu = (c < 'a' || c > 'z'); | ||
| } | ||
|
|
||
| /* ALT we can just prefix with ESC; anything else requires CSI u */ | ||
| if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1); | ||
| return; | ||
| } | ||
|
|
||
| if(mod & VTERM_MOD_CTRL) | ||
| c &= 0x1f; | ||
|
|
||
| vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); | ||
| } | ||
|
|
||
| typedef struct { | ||
| enum { | ||
| KEYCODE_NONE, | ||
| KEYCODE_LITERAL, | ||
| KEYCODE_TAB, | ||
| KEYCODE_ENTER, | ||
| KEYCODE_SS3, | ||
| KEYCODE_CSI, | ||
| KEYCODE_CSI_CURSOR, | ||
| KEYCODE_CSINUM, | ||
| KEYCODE_KEYPAD | ||
| } type; | ||
| char literal; | ||
| int csinum; | ||
| } keycodes_s; | ||
|
|
||
| static keycodes_s keycodes[] = { | ||
| { KEYCODE_NONE, 0, 0 }, /* NONE */ | ||
|
|
||
| { KEYCODE_ENTER, '\r', 0 }, /* ENTER */ | ||
| { KEYCODE_TAB, '\t', 0 }, /* TAB */ | ||
| { KEYCODE_LITERAL, '\x7f', 0 }, /* BACKSPACE == ASCII DEL */ | ||
| { KEYCODE_LITERAL, '\x1b', 0 }, /* ESCAPE */ | ||
|
|
||
| { KEYCODE_CSI_CURSOR, 'A', 0 }, /* UP */ | ||
| { KEYCODE_CSI_CURSOR, 'B', 0 }, /* DOWN */ | ||
| { KEYCODE_CSI_CURSOR, 'D', 0 }, /* LEFT */ | ||
| { KEYCODE_CSI_CURSOR, 'C', 0 }, /* RIGHT */ | ||
|
|
||
| { KEYCODE_CSINUM, '~', 2 }, /* INS */ | ||
| { KEYCODE_CSINUM, '~', 3 }, /* DEL */ | ||
| { KEYCODE_CSI_CURSOR, 'H', 0 }, /* HOME */ | ||
| { KEYCODE_CSI_CURSOR, 'F', 0 }, /* END */ | ||
| { KEYCODE_CSINUM, '~', 5 }, /* PAGEUP */ | ||
| { KEYCODE_CSINUM, '~', 6 }, /* PAGEDOWN */ | ||
| }; | ||
|
|
||
| static keycodes_s keycodes_fn[] = { | ||
| { KEYCODE_NONE, 0, 0 }, /* F0 - shouldn't happen */ | ||
| { KEYCODE_CSI_CURSOR, 'P', 0 }, /* F1 */ | ||
| { KEYCODE_CSI_CURSOR, 'Q', 0 }, /* F2 */ | ||
| { KEYCODE_CSI_CURSOR, 'R', 0 }, /* F3 */ | ||
| { KEYCODE_CSI_CURSOR, 'S', 0 }, /* F4 */ | ||
| { KEYCODE_CSINUM, '~', 15 }, /* F5 */ | ||
| { KEYCODE_CSINUM, '~', 17 }, /* F6 */ | ||
| { KEYCODE_CSINUM, '~', 18 }, /* F7 */ | ||
| { KEYCODE_CSINUM, '~', 19 }, /* F8 */ | ||
| { KEYCODE_CSINUM, '~', 20 }, /* F9 */ | ||
| { KEYCODE_CSINUM, '~', 21 }, /* F10 */ | ||
| { KEYCODE_CSINUM, '~', 23 }, /* F11 */ | ||
| { KEYCODE_CSINUM, '~', 24 }, /* F12 */ | ||
| }; | ||
|
|
||
| static keycodes_s keycodes_kp[] = { | ||
| { KEYCODE_KEYPAD, '0', 'p' }, /* KP_0 */ | ||
| { KEYCODE_KEYPAD, '1', 'q' }, /* KP_1 */ | ||
| { KEYCODE_KEYPAD, '2', 'r' }, /* KP_2 */ | ||
| { KEYCODE_KEYPAD, '3', 's' }, /* KP_3 */ | ||
| { KEYCODE_KEYPAD, '4', 't' }, /* KP_4 */ | ||
| { KEYCODE_KEYPAD, '5', 'u' }, /* KP_5 */ | ||
| { KEYCODE_KEYPAD, '6', 'v' }, /* KP_6 */ | ||
| { KEYCODE_KEYPAD, '7', 'w' }, /* KP_7 */ | ||
| { KEYCODE_KEYPAD, '8', 'x' }, /* KP_8 */ | ||
| { KEYCODE_KEYPAD, '9', 'y' }, /* KP_9 */ | ||
| { KEYCODE_KEYPAD, '*', 'j' }, /* KP_MULT */ | ||
| { KEYCODE_KEYPAD, '+', 'k' }, /* KP_PLUS */ | ||
| { KEYCODE_KEYPAD, ',', 'l' }, /* KP_COMMA */ | ||
| { KEYCODE_KEYPAD, '-', 'm' }, /* KP_MINUS */ | ||
| { KEYCODE_KEYPAD, '.', 'n' }, /* KP_PERIOD */ | ||
| { KEYCODE_KEYPAD, '/', 'o' }, /* KP_DIVIDE */ | ||
| { KEYCODE_KEYPAD, '\n', 'M' }, /* KP_ENTER */ | ||
| { KEYCODE_KEYPAD, '=', 'X' }, /* KP_EQUAL */ | ||
| }; | ||
|
|
||
| void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) | ||
| { | ||
| keycodes_s k; | ||
|
|
||
| if(key == VTERM_KEY_NONE) | ||
| return; | ||
|
|
||
| if(key < VTERM_KEY_FUNCTION_0) { | ||
| if(key >= sizeof(keycodes)/sizeof(keycodes[0])) | ||
| return; | ||
| k = keycodes[key]; | ||
| } | ||
| else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { | ||
| if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) | ||
| return; | ||
| k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; | ||
| } | ||
| else if(key >= VTERM_KEY_KP_0) { | ||
| if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) | ||
| return; | ||
| k = keycodes_kp[key - VTERM_KEY_KP_0]; | ||
| } | ||
|
|
||
| switch(k.type) { | ||
| case KEYCODE_NONE: | ||
| break; | ||
|
|
||
| case KEYCODE_TAB: | ||
| /* Shift-Tab is CSI Z but plain Tab is 0x09 */ | ||
| if(mod == VTERM_MOD_SHIFT) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); | ||
| else if(mod & VTERM_MOD_SHIFT) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1); | ||
| else | ||
| goto case_LITERAL; | ||
| break; | ||
|
|
||
| case KEYCODE_ENTER: | ||
| /* Enter is CRLF in newline mode, but just LF in linefeed */ | ||
| if(vt->state->mode.newline) | ||
| vterm_push_output_sprintf(vt, "\r\n"); | ||
| else | ||
| goto case_LITERAL; | ||
| break; | ||
|
|
||
| case KEYCODE_LITERAL: case_LITERAL: | ||
| if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1); | ||
| else | ||
| vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); | ||
| break; | ||
|
|
||
| case KEYCODE_SS3: case_SS3: | ||
| if(mod == 0) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); | ||
| else | ||
| goto case_CSI; | ||
| break; | ||
|
|
||
| case KEYCODE_CSI: case_CSI: | ||
| if(mod == 0) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); | ||
| else | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); | ||
| break; | ||
|
|
||
| case KEYCODE_CSINUM: | ||
| if(mod == 0) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); | ||
| else | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); | ||
| break; | ||
|
|
||
| case KEYCODE_CSI_CURSOR: | ||
| if(vt->state->mode.cursor) | ||
| goto case_SS3; | ||
| else | ||
| goto case_CSI; | ||
|
|
||
| case KEYCODE_KEYPAD: | ||
| if(vt->state->mode.keypad) { | ||
| k.literal = k.csinum; | ||
| goto case_SS3; | ||
| } | ||
| else | ||
| goto case_LITERAL; | ||
| } | ||
| } | ||
|
|
||
| void vterm_keyboard_start_paste(VTerm *vt) | ||
| { | ||
| if(vt->state->mode.bracketpaste) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); | ||
| } | ||
|
|
||
| void vterm_keyboard_end_paste(VTerm *vt) | ||
| { | ||
| if(vt->state->mode.bracketpaste) | ||
| vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #include "vterm_internal.h" | ||
|
|
||
| #include "utf8.h" | ||
|
|
||
| static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) | ||
| { | ||
| modifiers <<= 2; | ||
|
|
||
| switch(state->mouse_protocol) { | ||
| case MOUSE_X10: | ||
| if(col + 0x21 > 0xff) | ||
| col = 0xff - 0x21; | ||
| if(row + 0x21 > 0xff) | ||
| row = 0xff - 0x21; | ||
|
|
||
| if(!pressed) | ||
| code = 3; | ||
|
|
||
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", | ||
| (code | modifiers) + 0x20, col + 0x21, row + 0x21); | ||
| break; | ||
|
|
||
| case MOUSE_UTF8: | ||
| { | ||
| char utf8[18]; size_t len = 0; | ||
|
|
||
| if(!pressed) | ||
| code = 3; | ||
|
|
||
| len += fill_utf8((code | modifiers) + 0x20, utf8 + len); | ||
| len += fill_utf8(col + 0x21, utf8 + len); | ||
| len += fill_utf8(row + 0x21, utf8 + len); | ||
| utf8[len] = 0; | ||
|
|
||
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); | ||
| } | ||
| break; | ||
|
|
||
| case MOUSE_SGR: | ||
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", | ||
| code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); | ||
| break; | ||
|
|
||
| case MOUSE_RXVT: | ||
| if(!pressed) | ||
| code = 3; | ||
|
|
||
| vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", | ||
| code | modifiers, col + 1, row + 1); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) | ||
| { | ||
| VTermState *state = vt->state; | ||
|
|
||
| if(col == state->mouse_col && row == state->mouse_row) | ||
| return; | ||
|
|
||
| state->mouse_col = col; | ||
| state->mouse_row = row; | ||
|
|
||
| if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || | ||
| (state->mouse_flags & MOUSE_WANT_MOVE)) { | ||
| int button = state->mouse_buttons & 0x01 ? 1 : | ||
| state->mouse_buttons & 0x02 ? 2 : | ||
| state->mouse_buttons & 0x04 ? 3 : 4; | ||
| output_mouse(state, button-1 + 0x20, 1, mod, col, row); | ||
| } | ||
| } | ||
|
|
||
| void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) | ||
| { | ||
| VTermState *state = vt->state; | ||
|
|
||
| int old_buttons = state->mouse_buttons; | ||
|
|
||
| if(button > 0 && button <= 3) { | ||
| if(pressed) | ||
| state->mouse_buttons |= (1 << (button-1)); | ||
| else | ||
| state->mouse_buttons &= ~(1 << (button-1)); | ||
| } | ||
|
|
||
| /* Most of the time we don't get button releases from 4/5 */ | ||
| if(state->mouse_buttons == old_buttons && button < 4) | ||
| return; | ||
|
|
||
| if(button < 4) { | ||
| output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row); | ||
| } | ||
| else if(button < 6) { | ||
| output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,346 @@ | ||
| #include "vterm_internal.h" | ||
|
|
||
| #include <stdio.h> | ||
| #include <string.h> | ||
|
|
||
| #define CSI_ARGS_MAX 16 | ||
| #define CSI_LEADER_MAX 16 | ||
| #define CSI_INTERMED_MAX 16 | ||
|
|
||
| static void do_control(VTerm *vt, unsigned char control) | ||
| { | ||
| if(vt->parser_callbacks && vt->parser_callbacks->control) | ||
| if((*vt->parser_callbacks->control)(control, vt->cbdata)) | ||
| return; | ||
|
|
||
| DEBUG_LOG1("libvterm: Unhandled control 0x%02x\n", control); | ||
| } | ||
|
|
||
| static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command) | ||
| { | ||
| int i = 0; | ||
|
|
||
| int leaderlen = 0; | ||
| char leader[CSI_LEADER_MAX]; | ||
| int argcount = 1; /* Always at least 1 arg */ | ||
| long csi_args[CSI_ARGS_MAX]; | ||
| int argi; | ||
| int intermedlen = 0; | ||
| char intermed[CSI_INTERMED_MAX]; | ||
|
|
||
| /* Extract leader bytes 0x3c to 0x3f */ | ||
| for( ; i < (int)arglen; i++) { | ||
| if(args[i] < 0x3c || args[i] > 0x3f) | ||
| break; | ||
| if(leaderlen < CSI_LEADER_MAX-1) | ||
| leader[leaderlen++] = args[i]; | ||
| } | ||
|
|
||
| leader[leaderlen] = 0; | ||
|
|
||
| for( ; i < (int)arglen; i++) | ||
| if(args[i] == 0x3b || args[i] == 0x3a) /* ; or : */ | ||
| argcount++; | ||
|
|
||
| /* TODO: Consider if these buffers should live in the VTerm struct itself */ | ||
| if(argcount > CSI_ARGS_MAX) | ||
| argcount = CSI_ARGS_MAX; | ||
|
|
||
| for(argi = 0; argi < argcount; argi++) | ||
| csi_args[argi] = CSI_ARG_MISSING; | ||
|
|
||
| argi = 0; | ||
| for(i = leaderlen; i < (int)arglen && argi < argcount; i++) { | ||
| switch(args[i]) { | ||
| case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: | ||
| case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: | ||
| if(csi_args[argi] == CSI_ARG_MISSING) | ||
| csi_args[argi] = 0; | ||
| csi_args[argi] *= 10; | ||
| csi_args[argi] += args[i] - '0'; | ||
| break; | ||
| case 0x3a: | ||
| csi_args[argi] |= CSI_ARG_FLAG_MORE; | ||
| /* FALLTHROUGH */ | ||
| case 0x3b: | ||
| argi++; | ||
| break; | ||
| default: | ||
| goto done_leader; | ||
| } | ||
| } | ||
| done_leader: ; | ||
|
|
||
| for( ; i < (int)arglen; i++) { | ||
| if((args[i] & 0xf0) != 0x20) | ||
| break; | ||
|
|
||
| if(intermedlen < CSI_INTERMED_MAX-1) | ||
| intermed[intermedlen++] = args[i]; | ||
| } | ||
|
|
||
| intermed[intermedlen] = 0; | ||
|
|
||
| if(i < (int)arglen) { | ||
| DEBUG_LOG2("libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i); | ||
| } | ||
|
|
||
| #if 0 | ||
| printf("Parsed CSI args %.*s as:\n", arglen, args); | ||
| printf(" leader: %s\n", leader); | ||
| for(argi = 0; argi < argcount; argi++) { | ||
| printf(" %lu", CSI_ARG(csi_args[argi])); | ||
| if(!CSI_ARG_HAS_MORE(csi_args[argi])) | ||
| printf("\n"); | ||
| printf(" intermed: %s\n", intermed); | ||
| } | ||
| #endif | ||
|
|
||
| if(vt->parser_callbacks && vt->parser_callbacks->csi) | ||
| if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata)) | ||
| return; | ||
|
|
||
| DEBUG_LOG3("libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command); | ||
| } | ||
|
|
||
| static void append_strbuffer(VTerm *vt, const char *str, size_t len) | ||
| { | ||
| if(len > vt->strbuffer_len - vt->strbuffer_cur) { | ||
| len = vt->strbuffer_len - vt->strbuffer_cur; | ||
| DEBUG_LOG1("Truncating strbuffer preserve to %zd bytes\n", len); | ||
| } | ||
|
|
||
| if(len > 0) { | ||
| strncpy(vt->strbuffer + vt->strbuffer_cur, str, len); | ||
| vt->strbuffer_cur += len; | ||
| } | ||
| } | ||
|
|
||
| static size_t do_string(VTerm *vt, const char *str_frag, size_t len) | ||
| { | ||
| size_t eaten; | ||
|
|
||
| if(vt->strbuffer_cur) { | ||
| if(str_frag) | ||
| append_strbuffer(vt, str_frag, len); | ||
|
|
||
| str_frag = vt->strbuffer; | ||
| len = vt->strbuffer_cur; | ||
| } | ||
| else if(!str_frag) { | ||
| DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n"); | ||
| len = 0; | ||
| } | ||
|
|
||
| vt->strbuffer_cur = 0; | ||
|
|
||
| switch(vt->parser_state) { | ||
| case NORMAL: | ||
| if(vt->parser_callbacks && vt->parser_callbacks->text) | ||
| if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata))) | ||
| return eaten; | ||
|
|
||
| DEBUG_LOG1("libvterm: Unhandled text (%zu chars)\n", len); | ||
| return 0; | ||
|
|
||
| case ESC: | ||
| if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) { | ||
| /* C1 emulations using 7bit clean */ | ||
| /* ESC 0x40 == 0x80 */ | ||
| do_control(vt, str_frag[0] + 0x40); | ||
| return 0; | ||
| } | ||
|
|
||
| if(vt->parser_callbacks && vt->parser_callbacks->escape) | ||
| if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata)) | ||
| return 0; | ||
|
|
||
| DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]); | ||
| return 0; | ||
|
|
||
| case CSI: | ||
| do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]); | ||
| return 0; | ||
|
|
||
| case OSC: | ||
| if(vt->parser_callbacks && vt->parser_callbacks->osc) | ||
| if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata)) | ||
| return 0; | ||
|
|
||
| DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str_frag); | ||
| return 0; | ||
|
|
||
| case DCS: | ||
| if(vt->parser_callbacks && vt->parser_callbacks->dcs) | ||
| if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata)) | ||
| return 0; | ||
|
|
||
| DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str_frag); | ||
| return 0; | ||
|
|
||
| case ESC_IN_OSC: | ||
| case ESC_IN_DCS: | ||
| DEBUG_LOG("libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n"); | ||
| return 0; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) | ||
| { | ||
| size_t pos = 0; | ||
| const char *string_start; | ||
|
|
||
| switch(vt->parser_state) { | ||
| case NORMAL: | ||
| string_start = NULL; | ||
| break; | ||
| case ESC: | ||
| case ESC_IN_OSC: | ||
| case ESC_IN_DCS: | ||
| case CSI: | ||
| case OSC: | ||
| case DCS: | ||
| string_start = bytes; | ||
| break; | ||
| } | ||
|
|
||
| #define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0) | ||
| #define ENTER_NORMAL_STATE() do { vt->parser_state = NORMAL; string_start = NULL; } while(0) | ||
|
|
||
| for( ; pos < len; pos++) { | ||
| unsigned char c = bytes[pos]; | ||
|
|
||
| if(c == 0x00 || c == 0x7f) { /* NUL, DEL */ | ||
| if(vt->parser_state != NORMAL) { | ||
| append_strbuffer(vt, string_start, bytes + pos - string_start); | ||
| string_start = bytes + pos + 1; | ||
| } | ||
| continue; | ||
| } | ||
| if(c == 0x18 || c == 0x1a) { /* CAN, SUB */ | ||
| ENTER_NORMAL_STATE(); | ||
| continue; | ||
| } | ||
| else if(c == 0x1b) { /* ESC */ | ||
| if(vt->parser_state == OSC) | ||
| vt->parser_state = ESC_IN_OSC; | ||
| else if(vt->parser_state == DCS) | ||
| vt->parser_state = ESC_IN_DCS; | ||
| else | ||
| ENTER_STRING_STATE(ESC); | ||
| continue; | ||
| } | ||
| else if(c == 0x07 && /* BEL, can stand for ST in OSC or DCS state */ | ||
| (vt->parser_state == OSC || vt->parser_state == DCS)) { | ||
| /* fallthrough */ | ||
| } | ||
| else if(c < 0x20) { /* other C0 */ | ||
| if(vt->parser_state != NORMAL) | ||
| append_strbuffer(vt, string_start, bytes + pos - string_start); | ||
| do_control(vt, c); | ||
| if(vt->parser_state != NORMAL) | ||
| string_start = bytes + pos + 1; | ||
| continue; | ||
| } | ||
| /* else fallthrough */ | ||
|
|
||
| switch(vt->parser_state) { | ||
| case ESC_IN_OSC: | ||
| case ESC_IN_DCS: | ||
| if(c == 0x5c) { /* ST */ | ||
| switch(vt->parser_state) { | ||
| case ESC_IN_OSC: vt->parser_state = OSC; break; | ||
| case ESC_IN_DCS: vt->parser_state = DCS; break; | ||
| default: break; | ||
| } | ||
| do_string(vt, string_start, bytes + pos - string_start - 1); | ||
| ENTER_NORMAL_STATE(); | ||
| break; | ||
| } | ||
| vt->parser_state = ESC; | ||
| string_start = bytes + pos; | ||
| /* else fallthrough */ | ||
|
|
||
| case ESC: | ||
| switch(c) { | ||
| case 0x50: /* DCS */ | ||
| ENTER_STRING_STATE(DCS); | ||
| break; | ||
| case 0x5b: /* CSI */ | ||
| ENTER_STRING_STATE(CSI); | ||
| break; | ||
| case 0x5d: /* OSC */ | ||
| ENTER_STRING_STATE(OSC); | ||
| break; | ||
| default: | ||
| if(c >= 0x30 && c < 0x7f) { | ||
| /* +1 to pos because we want to include this command byte as well */ | ||
| do_string(vt, string_start, bytes + pos - string_start + 1); | ||
| ENTER_NORMAL_STATE(); | ||
| } | ||
| else if(c >= 0x20 && c < 0x30) { | ||
| /* intermediate byte */ | ||
| } | ||
| else { | ||
| DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c); | ||
| } | ||
| } | ||
| break; | ||
|
|
||
| case CSI: | ||
| if(c >= 0x40 && c <= 0x7f) { | ||
| /* +1 to pos because we want to include this command byte as well */ | ||
| do_string(vt, string_start, bytes + pos - string_start + 1); | ||
| ENTER_NORMAL_STATE(); | ||
| } | ||
| break; | ||
|
|
||
| case OSC: | ||
| case DCS: | ||
| if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { | ||
| do_string(vt, string_start, bytes + pos - string_start); | ||
| ENTER_NORMAL_STATE(); | ||
| } | ||
| break; | ||
|
|
||
| case NORMAL: | ||
| if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { | ||
| switch(c) { | ||
| case 0x90: /* DCS */ | ||
| ENTER_STRING_STATE(DCS); | ||
| break; | ||
| case 0x9b: /* CSI */ | ||
| ENTER_STRING_STATE(CSI); | ||
| break; | ||
| case 0x9d: /* OSC */ | ||
| ENTER_STRING_STATE(OSC); | ||
| break; | ||
| default: | ||
| do_control(vt, c); | ||
| break; | ||
| } | ||
| } | ||
| else { | ||
| size_t text_eaten = do_string(vt, bytes + pos, len - pos); | ||
|
|
||
| if(text_eaten == 0) { | ||
| string_start = bytes + pos; | ||
| goto pause; | ||
| } | ||
|
|
||
| pos += (text_eaten - 1); /* we'll ++ it again in a moment */ | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| pause: | ||
| if(string_start && string_start < len + bytes) { | ||
| size_t remaining = len - (string_start - bytes); | ||
| append_strbuffer(vt, string_start, remaining); | ||
| } | ||
|
|
||
| return len; | ||
| } |