├── .gitignore ├── COPYING ├── Makefile ├── README.md ├── build ├── clang.mk ├── debug.mk └── release.mk ├── demo ├── capdump.c ├── keyboard.c ├── output.c ├── paint.c └── pkbd.c ├── sgr.c ├── sgr.h ├── termbox ├── README ├── bytebuffer.inl ├── input.inl ├── term.inl ├── termbox.c └── termbox.h ├── test ├── README ├── runtest ├── sgr_attrs_test.c ├── sgr_encode_test.c ├── sgr_test.c ├── sgr_unpack_test.c ├── terminfo │ ├── 6d │ ├── m │ │ └── minitel1 │ └── x │ │ ├── xterm-badfile │ │ ├── xterm-color │ │ ├── xterm-kitty │ │ └── xterm-new ├── ti-stress.sh ├── ti_getcaps_test.c ├── ti_load_test.c ├── ti_parm_test.c ├── tkbd_desc_test.c ├── tkbd_parse_test.c ├── tkbd_stresc_test.c └── utf8_test.c ├── ti.c ├── ti.h ├── tkbd.c ├── tkbd.h ├── tools ├── gencap-defs.sh └── gencap-names.sh ├── utf8.c └── utf8.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | 3 | libtermbox.so 4 | termbox.sa 5 | 6 | /demo/keyboard 7 | /demo/output 8 | /demo/paint 9 | /demo/capdump 10 | /demo/pkbd 11 | 12 | /test/*_test 13 | 14 | /tags 15 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010-2013 nsf 2 | Copyright (C) 2020 Auxrelius I 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Termlib Makefile 2 | .POSIX: 3 | .SUFFIXES: 4 | 5 | CC = cc 6 | CFLAGS = -std=c99 $(INCLUDE) $(WARN) $(OPTIMIZE) -fPIC 7 | WARN = -Wall -Wextra 8 | OPTIMIZE = -O2 9 | INCLUDE = -iquote termbox -iquote . 10 | LDFLAGS = 11 | LDLIBS = 12 | 13 | OBJS = sgr.o ti.o tkbd.o utf8.o termbox/termbox.o 14 | SO_NAME = libtermlib.so 15 | SA_NAME = termlib.sa 16 | LIBS = $(SO_NAME) $(SA_NAME) 17 | 18 | DEMO_OBJS = demo/keyboard.o demo/output.o demo/paint.o demo/capdump.o demo/pkbd.o 19 | DEMO_CMDS = demo/keyboard demo/output demo/paint demo/capdump demo/pkbd 20 | 21 | TESTS = test/ti_load_test test/ti_getcaps_test test/ti_parm_test \ 22 | test/sgr_test test/sgr_unpack_test test/sgr_encode_test test/sgr_attrs_test \ 23 | test/tkbd_parse_test test/tkbd_desc_test test/tkbd_stresc_test \ 24 | test/utf8_test 25 | 26 | # make profile=release (default) 27 | # make profile=debug 28 | # make profile=clang 29 | profile = release 30 | include build/$(profile).mk 31 | 32 | # Build everything 33 | all: $(OBJS) $(LIBS) demo $(TESTS) 34 | .PHONY: all 35 | 36 | # Main objects and their dependencies 37 | sgr.o: sgr.h 38 | ti.o: ti.h 39 | tkbd.o: tkbd.h 40 | utf8.o: utf8.h 41 | 42 | # Termbox compatibility 43 | termbox/termbox.o: termbox/termbox.h termbox/bytebuffer.inl termbox/term.inl termbox/input.inl 44 | 45 | # Shared and static libraries 46 | $(SO_NAME): $(OBJS) 47 | $(CC) -shared -o $@ $(OBJS) 48 | $(SA_NAME): $(OBJS) 49 | ar rcs $@ $(OBJS) 50 | 51 | # Demo programs 52 | demo/keyboard: demo/keyboard.o $(OBJS) 53 | demo/output: demo/output.o $(OBJS) 54 | demo/paint: demo/paint.o $(OBJS) 55 | demo/capdump: demo/capdump.o $(OBJS) 56 | demo/pkbd: demo/pkbd.o $(OBJS) 57 | demo: $(DEMO_CMDS) 58 | .PHONY: demo 59 | 60 | # Test programs 61 | TEST_CC = $(CC) $(CFLAGS) $(CFLAGS_EXTRA) -Wno-missing-field-initializers $(LDFLAGS) 62 | $(TESTS): 63 | $(TEST_CC) $< -o $@ 64 | test/ti_load_test: test/ti_load_test.c ti.c ti.h 65 | test/ti_getcaps_test: test/ti_getcaps_test.c ti.c ti.h 66 | test/ti_parm_test: test/ti_parm_test.c ti.c ti.h 67 | test/sgr_test: test/sgr_test.c sgr.c sgr.h 68 | test/sgr_unpack_test: test/sgr_unpack_test.c sgr.c sgr.h 69 | test/sgr_encode_test: test/sgr_encode_test.c sgr.c sgr.h 70 | test/sgr_attrs_test: test/sgr_attrs_test.c sgr.c sgr.h 71 | test/tkbd_parse_test: test/tkbd_parse_test.c tkbd.c tkbd.h 72 | test/tkbd_desc_test: test/tkbd_desc_test.c tkbd.c tkbd.h 73 | test/tkbd_stresc_test: test/tkbd_stresc_test.c tkbd.c tkbd.h 74 | test/utf8_test: test/utf8_test.c utf8.c utf8.h 75 | test: $(TESTS) 76 | test/runtest $(TESTS) 77 | .PHONY: test 78 | 79 | # Clean everything 80 | clean: 81 | rm -f $(DEMO_OBJS) 82 | rm -f $(DEMO_CMDS) 83 | rm -f $(OBJS) 84 | rm -f $(LIBS) 85 | rm -f $(TESTS) 86 | .PHONY: clean 87 | 88 | # Implicit rule to build object files from .c source files 89 | .SUFFIXES: .o .c 90 | .c.o: 91 | $(CC) $(CFLAGS) $(CFLAGS_EXTRA) -c $< -o $@ 92 | 93 | tags: 94 | ctags -R --totals 95 | .PHONY: tags 96 | 97 | Caps: # fetch latest curses Caps file from GitHub 98 | curl -o $@ https://raw.githubusercontent.com/ThomasDickey/ncurses-snapshots/master/include/Caps 99 | .PHONY: Caps 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Termlib 2 | 3 | *termlib* is a collection of clean, modern, single-file C libraries for 4 | building Unix terminal programs. 5 | 6 | Termlib started as a fork of [Termbox][] and continues in the spirit of being a 7 | lightweight, approachable alternative to ncurses programming. Unlike Termbox, 8 | termlib aims to provide library routines useful for many types of 9 | terminal-enabled programs, not just curses-style full screen TUIs. 10 | 11 | ### Project status 12 | 13 | Under heavy development and reorganization. 14 | Serious use is not recommended at this time. 15 | 16 | ### Libraries 17 | 18 | #### tkbd.h 19 | 20 | The tkbd library decodes ECMA-48/VT/xterm keyboard, mouse, and UTF-8 character 21 | sequences into a simple struct. It can be used to remove most of the tedium 22 | involved with supporting the myriad encoding schemes employed by different 23 | popular terminal emulators. 24 | 25 | [Usage][tkbd.h] 26 | 27 | #### sgr.h 28 | 29 | The sgr library includes data structures and routines for constructing 30 | ANSI/ECMA-48 Select Graphic Render (SGR) sequences. It supports all typographic 31 | attributes——bold, faint, italic, underline, 32 | blink, cross-out, and reverse——as well background and 33 | foreground colors in 8-color, 16-color, 24-color greyscale, 216-color, 34 | 256-color, and 16M true color modes. 35 | 36 | [Usage][sgr.h] 37 | 38 | #### ti.h 39 | 40 | The ti library is a standalone [terminfo(5)][terminfo] processor. It can query 41 | the terminfo database for terminal capabilities and generate escape sequences 42 | without ncurses. 43 | 44 | [Usage][ti.h] 45 | 46 | #### utf8.h 47 | 48 | The utf8 library is a bare bones utf8 encoder / decoder with some useful utility 49 | routines and constants. It supports the minimum baseline of features all 50 | terminal programs should be ready to support. 51 | 52 | [Usage][utf8.h] 53 | 54 | ### Acknowledgements 55 | 56 | This project was originally forked from [Termbox][] by nsf and contributors. 57 | 58 | The [ti.c][] parameter processing logic is based on the Golang [TCell 59 | implementation][tcell] of the same by Garrett D'Amore and contributors. 60 | 61 | 62 | [termbox]: https://github.com/nsf/termbox 63 | [sfl]: https://github.com/nothings/single_file_libs 64 | [tcell]: https://github.com/gdamore/tcell/blob/master/terminfo/terminfo.go 65 | [terminfo]: https://pubs.opengroup.org/onlinepubs/007908799/xcurses/terminfo.html 66 | 67 | [ti.h]: https://github.com/aux01/tbaux/blob/master/ti.h 68 | [ti.c]: https://github.com/aux01/tbaux/blob/master/ti.c 69 | [sgr.h]: https://github.com/aux01/tbaux/blob/master/sgr.h 70 | [sgr.c]: https://github.com/aux01/tbaux/blob/master/sgr.c 71 | [tkbd.h]: https://github.com/aux01/tbaux/blob/master/tkbd.h 72 | [tkbd.c]: https://github.com/aux01/tbaux/blob/master/tkbd.c 73 | [utf8.h]: https://github.com/aux01/tbaux/blob/master/utf8.h 74 | [utf8.c]: https://github.com/aux01/tbaux/blob/master/utf8.c 75 | -------------------------------------------------------------------------------- /build/clang.mk: -------------------------------------------------------------------------------- 1 | # make profile=clang 2 | CC = clang-9 3 | -------------------------------------------------------------------------------- /build/debug.mk: -------------------------------------------------------------------------------- 1 | # make profile=debug 2 | OPTIMIZE = -Og -g 3 | -------------------------------------------------------------------------------- /build/release.mk: -------------------------------------------------------------------------------- 1 | # default build profile 2 | # uses all defaults set in Makefile 3 | -------------------------------------------------------------------------------- /demo/capdump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * capdump.c - Write all terminal capabilities to standard output. 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * 7 | */ 8 | 9 | #include "../ti.h" 10 | 11 | #include 12 | #include // getenv 13 | 14 | int main(void) { 15 | char * term = getenv("TERM"); 16 | 17 | int err; 18 | ti_terminfo *ti = ti_load(term, &err); 19 | if (!ti) { 20 | fprintf(stderr, "error: %s\n", ti_strerror(err)); 21 | return 1; 22 | } 23 | 24 | printf("# %s\n", ti->term_names); 25 | for (int i = 0; i < ti->bools_count; i++) { 26 | printf("%s %s=%d\n", "std bool", ti_boolnames[i], ti_getbooli(ti, i)); 27 | } 28 | for (int i = 0; i < ti->ext_bools_count; i++) { 29 | printf("%s %s=%d\n", "ext bool", ti->ext_bool_names[i], ti->ext_bools[i]); 30 | } 31 | for (int i = 0; i < ti->nums_count; i++) { 32 | printf("%s %s=%d\n", "std num", ti_numnames[i], ti_getnumi(ti, i)); 33 | } 34 | for (int i = 0; i < ti->ext_nums_count; i++) { 35 | printf("%s %s=%d\n", "ext num", ti->ext_num_names[i], ti->ext_nums[i]); 36 | } 37 | 38 | char esc[1024]; // escape string buffer 39 | for (int i = 0; i < ti->strs_count; i++) { 40 | char *s = ti_getstri(ti, i); 41 | if (s) { 42 | ti_stresc(esc, s, sizeof(esc)); 43 | s = esc; 44 | } 45 | printf("%s %s=%s\n", "std str", ti_strnames[i], s); 46 | } 47 | for (int i = 0; i < ti->ext_strs_count; i++) { 48 | char *s = ti->ext_strs[i]; 49 | if (s) { 50 | ti_stresc(esc, s, sizeof(esc)); 51 | s = esc; 52 | } 53 | printf("%s %s=%s\n", "ext str", ti->ext_str_names[i], s); 54 | } 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /demo/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "termbox.h" 7 | 8 | struct key { 9 | unsigned char x; 10 | unsigned char y; 11 | uint32_t ch; 12 | }; 13 | 14 | #define STOP {0,0,0} 15 | struct key K_ESC[] = {{1,1,'E'},{2,1,'S'},{3,1,'C'},STOP}; 16 | struct key K_F1[] = {{6,1,'F'},{7,1,'1'},STOP}; 17 | struct key K_F2[] = {{9,1,'F'},{10,1,'2'},STOP}; 18 | struct key K_F3[] = {{12,1,'F'},{13,1,'3'},STOP}; 19 | struct key K_F4[] = {{15,1,'F'},{16,1,'4'},STOP}; 20 | struct key K_F5[] = {{19,1,'F'},{20,1,'5'},STOP}; 21 | struct key K_F6[] = {{22,1,'F'},{23,1,'6'},STOP}; 22 | struct key K_F7[] = {{25,1,'F'},{26,1,'7'},STOP}; 23 | struct key K_F8[] = {{28,1,'F'},{29,1,'8'},STOP}; 24 | struct key K_F9[] = {{33,1,'F'},{34,1,'9'},STOP}; 25 | struct key K_F10[] = {{36,1,'F'},{37,1,'1'},{38,1,'0'},STOP}; 26 | struct key K_F11[] = {{40,1,'F'},{41,1,'1'},{42,1,'1'},STOP}; 27 | struct key K_F12[] = {{44,1,'F'},{45,1,'1'},{46,1,'2'},STOP}; 28 | struct key K_PRN[] = {{50,1,'P'},{51,1,'R'},{52,1,'N'},STOP}; 29 | struct key K_SCR[] = {{54,1,'S'},{55,1,'C'},{56,1,'R'},STOP}; 30 | struct key K_BRK[] = {{58,1,'B'},{59,1,'R'},{60,1,'K'},STOP}; 31 | struct key K_LED1[] = {{66,1,'-'},STOP}; 32 | struct key K_LED2[] = {{70,1,'-'},STOP}; 33 | struct key K_LED3[] = {{74,1,'-'},STOP}; 34 | 35 | struct key K_TILDE[] = {{1,4,'`'},STOP}; 36 | struct key K_TILDE_SHIFT[] = {{1,4,'~'},STOP}; 37 | struct key K_1[] = {{4,4,'1'},STOP}; 38 | struct key K_1_SHIFT[] = {{4,4,'!'},STOP}; 39 | struct key K_2[] = {{7,4,'2'},STOP}; 40 | struct key K_2_SHIFT[] = {{7,4,'@'},STOP}; 41 | struct key K_3[] = {{10,4,'3'},STOP}; 42 | struct key K_3_SHIFT[] = {{10,4,'#'},STOP}; 43 | struct key K_4[] = {{13,4,'4'},STOP}; 44 | struct key K_4_SHIFT[] = {{13,4,'$'},STOP}; 45 | struct key K_5[] = {{16,4,'5'},STOP}; 46 | struct key K_5_SHIFT[] = {{16,4,'%'},STOP}; 47 | struct key K_6[] = {{19,4,'6'},STOP}; 48 | struct key K_6_SHIFT[] = {{19,4,'^'},STOP}; 49 | struct key K_7[] = {{22,4,'7'},STOP}; 50 | struct key K_7_SHIFT[] = {{22,4,'&'},STOP}; 51 | struct key K_8[] = {{25,4,'8'},STOP}; 52 | struct key K_8_SHIFT[] = {{25,4,'*'},STOP}; 53 | struct key K_9[] = {{28,4,'9'},STOP}; 54 | struct key K_9_SHIFT[] = {{28,4,'('},STOP}; 55 | struct key K_0[] = {{31,4,'0'},STOP}; 56 | struct key K_0_SHIFT[] = {{31,4,')'},STOP}; 57 | struct key K_MINUS[] = {{34,4,'-'},STOP}; 58 | struct key K_MINUS_SHIFT[] = {{34,4,'_'},STOP}; 59 | struct key K_EQUALS[] = {{37,4,'='},STOP}; 60 | struct key K_EQUALS_SHIFT[] = {{37,4,'+'},STOP}; 61 | struct key K_BACKSLASH[] = {{40,4,'\\'},STOP}; 62 | struct key K_BACKSLASH_SHIFT[] = {{40,4,'|'},STOP}; 63 | struct key K_BACKSPACE[] = {{44,4,0x2190},{45,4,0x2500},{46,4,0x2500},STOP}; 64 | struct key K_INS[] = {{50,4,'I'},{51,4,'N'},{52,4,'S'},STOP}; 65 | struct key K_HOM[] = {{54,4,'H'},{55,4,'O'},{56,4,'M'},STOP}; 66 | struct key K_PGU[] = {{58,4,'P'},{59,4,'G'},{60,4,'U'},STOP}; 67 | struct key K_K_NUMLOCK[] = {{65,4,'N'},STOP}; 68 | struct key K_K_SLASH[] = {{68,4,'/'},STOP}; 69 | struct key K_K_STAR[] = {{71,4,'*'},STOP}; 70 | struct key K_K_MINUS[] = {{74,4,'-'},STOP}; 71 | 72 | struct key K_TAB[] = {{1,6,'T'},{2,6,'A'},{3,6,'B'},STOP}; 73 | struct key K_q[] = {{6,6,'q'},STOP}; 74 | struct key K_Q[] = {{6,6,'Q'},STOP}; 75 | struct key K_w[] = {{9,6,'w'},STOP}; 76 | struct key K_W[] = {{9,6,'W'},STOP}; 77 | struct key K_e[] = {{12,6,'e'},STOP}; 78 | struct key K_E[] = {{12,6,'E'},STOP}; 79 | struct key K_r[] = {{15,6,'r'},STOP}; 80 | struct key K_R[] = {{15,6,'R'},STOP}; 81 | struct key K_t[] = {{18,6,'t'},STOP}; 82 | struct key K_T[] = {{18,6,'T'},STOP}; 83 | struct key K_y[] = {{21,6,'y'},STOP}; 84 | struct key K_Y[] = {{21,6,'Y'},STOP}; 85 | struct key K_u[] = {{24,6,'u'},STOP}; 86 | struct key K_U[] = {{24,6,'U'},STOP}; 87 | struct key K_i[] = {{27,6,'i'},STOP}; 88 | struct key K_I[] = {{27,6,'I'},STOP}; 89 | struct key K_o[] = {{30,6,'o'},STOP}; 90 | struct key K_O[] = {{30,6,'O'},STOP}; 91 | struct key K_p[] = {{33,6,'p'},STOP}; 92 | struct key K_P[] = {{33,6,'P'},STOP}; 93 | struct key K_LSQB[] = {{36,6,'['},STOP}; 94 | struct key K_LCUB[] = {{36,6,'{'},STOP}; 95 | struct key K_RSQB[] = {{39,6,']'},STOP}; 96 | struct key K_RCUB[] = {{39,6,'}'},STOP}; 97 | struct key K_ENTER[] = { 98 | {43,6,0x2591},{44,6,0x2591},{45,6,0x2591},{46,6,0x2591}, 99 | {43,7,0x2591},{44,7,0x2591},{45,7,0x21B5},{46,7,0x2591}, 100 | {41,8,0x2591},{42,8,0x2591},{43,8,0x2591},{44,8,0x2591}, 101 | {45,8,0x2591},{46,8,0x2591},STOP 102 | }; 103 | struct key K_DEL[] = {{50,6,'D'},{51,6,'E'},{52,6,'L'},STOP}; 104 | struct key K_END[] = {{54,6,'E'},{55,6,'N'},{56,6,'D'},STOP}; 105 | struct key K_PGD[] = {{58,6,'P'},{59,6,'G'},{60,6,'D'},STOP}; 106 | struct key K_K_7[] = {{65,6,'7'},STOP}; 107 | struct key K_K_8[] = {{68,6,'8'},STOP}; 108 | struct key K_K_9[] = {{71,6,'9'},STOP}; 109 | struct key K_K_PLUS[] = {{74,6,' '},{74,7,'+'},{74,8,' '},STOP}; 110 | 111 | struct key K_CAPS[] = {{1,8,'C'},{2,8,'A'},{3,8,'P'},{4,8,'S'},STOP}; 112 | struct key K_a[] = {{7,8,'a'},STOP}; 113 | struct key K_A[] = {{7,8,'A'},STOP}; 114 | struct key K_s[] = {{10,8,'s'},STOP}; 115 | struct key K_S[] = {{10,8,'S'},STOP}; 116 | struct key K_d[] = {{13,8,'d'},STOP}; 117 | struct key K_D[] = {{13,8,'D'},STOP}; 118 | struct key K_f[] = {{16,8,'f'},STOP}; 119 | struct key K_F[] = {{16,8,'F'},STOP}; 120 | struct key K_g[] = {{19,8,'g'},STOP}; 121 | struct key K_G[] = {{19,8,'G'},STOP}; 122 | struct key K_h[] = {{22,8,'h'},STOP}; 123 | struct key K_H[] = {{22,8,'H'},STOP}; 124 | struct key K_j[] = {{25,8,'j'},STOP}; 125 | struct key K_J[] = {{25,8,'J'},STOP}; 126 | struct key K_k[] = {{28,8,'k'},STOP}; 127 | struct key K_K[] = {{28,8,'K'},STOP}; 128 | struct key K_l[] = {{31,8,'l'},STOP}; 129 | struct key K_L[] = {{31,8,'L'},STOP}; 130 | struct key K_SEMICOLON[] = {{34,8,';'},STOP}; 131 | struct key K_PARENTHESIS[] = {{34,8,':'},STOP}; 132 | struct key K_QUOTE[] = {{37,8,'\''},STOP}; 133 | struct key K_DOUBLEQUOTE[] = {{37,8,'"'},STOP}; 134 | struct key K_K_4[] = {{65,8,'4'},STOP}; 135 | struct key K_K_5[] = {{68,8,'5'},STOP}; 136 | struct key K_K_6[] = {{71,8,'6'},STOP}; 137 | 138 | struct key K_LSHIFT[] = {{1,10,'S'},{2,10,'H'},{3,10,'I'},{4,10,'F'},{5,10,'T'},STOP}; 139 | struct key K_z[] = {{9,10,'z'},STOP}; 140 | struct key K_Z[] = {{9,10,'Z'},STOP}; 141 | struct key K_x[] = {{12,10,'x'},STOP}; 142 | struct key K_X[] = {{12,10,'X'},STOP}; 143 | struct key K_c[] = {{15,10,'c'},STOP}; 144 | struct key K_C[] = {{15,10,'C'},STOP}; 145 | struct key K_v[] = {{18,10,'v'},STOP}; 146 | struct key K_V[] = {{18,10,'V'},STOP}; 147 | struct key K_b[] = {{21,10,'b'},STOP}; 148 | struct key K_B[] = {{21,10,'B'},STOP}; 149 | struct key K_n[] = {{24,10,'n'},STOP}; 150 | struct key K_N[] = {{24,10,'N'},STOP}; 151 | struct key K_m[] = {{27,10,'m'},STOP}; 152 | struct key K_M[] = {{27,10,'M'},STOP}; 153 | struct key K_COMMA[] = {{30,10,','},STOP}; 154 | struct key K_LANB[] = {{30,10,'<'},STOP}; 155 | struct key K_PERIOD[] = {{33,10,'.'},STOP}; 156 | struct key K_RANB[] = {{33,10,'>'},STOP}; 157 | struct key K_SLASH[] = {{36,10,'/'},STOP}; 158 | struct key K_QUESTION[] = {{36,10,'?'},STOP}; 159 | struct key K_RSHIFT[] = {{42,10,'S'},{43,10,'H'},{44,10,'I'},{45,10,'F'},{46,10,'T'},STOP}; 160 | struct key K_ARROW_UP[] = {{54,10,'('},{55,10,0x2191},{56,10,')'},STOP}; 161 | struct key K_K_1[] = {{65,10,'1'},STOP}; 162 | struct key K_K_2[] = {{68,10,'2'},STOP}; 163 | struct key K_K_3[] = {{71,10,'3'},STOP}; 164 | struct key K_K_ENTER[] = {{74,10,0x2591},{74,11,0x2591},{74,12,0x2591},STOP}; 165 | 166 | struct key K_LCTRL[] = {{1,12,'C'},{2,12,'T'},{3,12,'R'},{4,12,'L'},STOP}; 167 | struct key K_LWIN[] = {{6,12,'W'},{7,12,'I'},{8,12,'N'},STOP}; 168 | struct key K_LALT[] = {{10,12,'A'},{11,12,'L'},{12,12,'T'},STOP}; 169 | struct key K_SPACE[] = { 170 | {14,12,' '},{15,12,' '},{16,12,' '},{17,12,' '},{18,12,' '}, 171 | {19,12,'S'},{20,12,'P'},{21,12,'A'},{22,12,'C'},{23,12,'E'}, 172 | {24,12,' '},{25,12,' '},{26,12,' '},{27,12,' '},{28,12,' '}, 173 | STOP 174 | }; 175 | struct key K_RALT[] = {{30,12,'A'},{31,12,'L'},{32,12,'T'},STOP}; 176 | struct key K_RWIN[] = {{34,12,'W'},{35,12,'I'},{36,12,'N'},STOP}; 177 | struct key K_RPROP[] = {{38,12,'P'},{39,12,'R'},{40,12,'O'},{41,12,'P'},STOP}; 178 | struct key K_RCTRL[] = {{43,12,'C'},{44,12,'T'},{45,12,'R'},{46,12,'L'},STOP}; 179 | struct key K_ARROW_LEFT[] = {{50,12,'('},{51,12,0x2190},{52,12,')'},STOP}; 180 | struct key K_ARROW_DOWN[] = {{54,12,'('},{55,12,0x2193},{56,12,')'},STOP}; 181 | struct key K_ARROW_RIGHT[] = {{58,12,'('},{59,12,0x2192},{60,12,')'},STOP}; 182 | struct key K_K_0[] = {{65,12,' '},{66,12,'0'},{67,12,' '},{68,12,' '},STOP}; 183 | struct key K_K_PERIOD[] = {{71,12,'.'},STOP}; 184 | 185 | struct combo { 186 | struct key *keys[6]; 187 | }; 188 | 189 | struct combo combos[] = { 190 | {{K_TILDE, K_2, K_LCTRL, K_RCTRL, 0}}, 191 | {{K_A, K_LCTRL, K_RCTRL, 0}}, 192 | {{K_B, K_LCTRL, K_RCTRL, 0}}, 193 | {{K_C, K_LCTRL, K_RCTRL, 0}}, 194 | {{K_D, K_LCTRL, K_RCTRL, 0}}, 195 | {{K_E, K_LCTRL, K_RCTRL, 0}}, 196 | {{K_F, K_LCTRL, K_RCTRL, 0}}, 197 | {{K_G, K_LCTRL, K_RCTRL, 0}}, 198 | {{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}}, 199 | {{K_I, K_TAB, K_LCTRL, K_RCTRL, 0}}, 200 | {{K_J, K_LCTRL, K_RCTRL, 0}}, 201 | {{K_K, K_LCTRL, K_RCTRL, 0}}, 202 | {{K_L, K_LCTRL, K_RCTRL, 0}}, 203 | {{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL, 0}}, 204 | {{K_N, K_LCTRL, K_RCTRL, 0}}, 205 | {{K_O, K_LCTRL, K_RCTRL, 0}}, 206 | {{K_P, K_LCTRL, K_RCTRL, 0}}, 207 | {{K_Q, K_LCTRL, K_RCTRL, 0}}, 208 | {{K_R, K_LCTRL, K_RCTRL, 0}}, 209 | {{K_S, K_LCTRL, K_RCTRL, 0}}, 210 | {{K_T, K_LCTRL, K_RCTRL, 0}}, 211 | {{K_U, K_LCTRL, K_RCTRL, 0}}, 212 | {{K_V, K_LCTRL, K_RCTRL, 0}}, 213 | {{K_W, K_LCTRL, K_RCTRL, 0}}, 214 | {{K_X, K_LCTRL, K_RCTRL, 0}}, 215 | {{K_Y, K_LCTRL, K_RCTRL, 0}}, 216 | {{K_Z, K_LCTRL, K_RCTRL, 0}}, 217 | {{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL, 0}}, 218 | {{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL, 0}}, 219 | {{K_RSQB, K_5, K_LCTRL, K_RCTRL, 0}}, 220 | {{K_6, K_LCTRL, K_RCTRL, 0}}, 221 | {{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL, 0}}, 222 | {{K_SPACE,0}}, 223 | {{K_1_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 224 | {{K_DOUBLEQUOTE,K_LSHIFT,K_RSHIFT,0}}, 225 | {{K_3_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 226 | {{K_4_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 227 | {{K_5_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 228 | {{K_7_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 229 | {{K_QUOTE,0}}, 230 | {{K_9_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 231 | {{K_0_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 232 | {{K_8_SHIFT,K_K_STAR,K_LSHIFT,K_RSHIFT,0}}, 233 | {{K_EQUALS_SHIFT,K_K_PLUS,K_LSHIFT,K_RSHIFT,0}}, 234 | {{K_COMMA,0}}, 235 | {{K_MINUS,K_K_MINUS,0}}, 236 | {{K_PERIOD,K_K_PERIOD,0}}, 237 | {{K_SLASH,K_K_SLASH,0}}, 238 | {{K_0,K_K_0,0}}, 239 | {{K_1,K_K_1,0}}, 240 | {{K_2,K_K_2,0}}, 241 | {{K_3,K_K_3,0}}, 242 | {{K_4,K_K_4,0}}, 243 | {{K_5,K_K_5,0}}, 244 | {{K_6,K_K_6,0}}, 245 | {{K_7,K_K_7,0}}, 246 | {{K_8,K_K_8,0}}, 247 | {{K_9,K_K_9,0}}, 248 | {{K_PARENTHESIS,K_LSHIFT,K_RSHIFT,0}}, 249 | {{K_SEMICOLON,0}}, 250 | {{K_LANB,K_LSHIFT,K_RSHIFT,0}}, 251 | {{K_EQUALS,0}}, 252 | {{K_RANB,K_LSHIFT,K_RSHIFT,0}}, 253 | {{K_QUESTION,K_LSHIFT,K_RSHIFT,0}}, 254 | {{K_2_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 255 | {{K_A,K_LSHIFT,K_RSHIFT,0}}, 256 | {{K_B,K_LSHIFT,K_RSHIFT,0}}, 257 | {{K_C,K_LSHIFT,K_RSHIFT,0}}, 258 | {{K_D,K_LSHIFT,K_RSHIFT,0}}, 259 | {{K_E,K_LSHIFT,K_RSHIFT,0}}, 260 | {{K_F,K_LSHIFT,K_RSHIFT,0}}, 261 | {{K_G,K_LSHIFT,K_RSHIFT,0}}, 262 | {{K_H,K_LSHIFT,K_RSHIFT,0}}, 263 | {{K_I,K_LSHIFT,K_RSHIFT,0}}, 264 | {{K_J,K_LSHIFT,K_RSHIFT,0}}, 265 | {{K_K,K_LSHIFT,K_RSHIFT,0}}, 266 | {{K_L,K_LSHIFT,K_RSHIFT,0}}, 267 | {{K_M,K_LSHIFT,K_RSHIFT,0}}, 268 | {{K_N,K_LSHIFT,K_RSHIFT,0}}, 269 | {{K_O,K_LSHIFT,K_RSHIFT,0}}, 270 | {{K_P,K_LSHIFT,K_RSHIFT,0}}, 271 | {{K_Q,K_LSHIFT,K_RSHIFT,0}}, 272 | {{K_R,K_LSHIFT,K_RSHIFT,0}}, 273 | {{K_S,K_LSHIFT,K_RSHIFT,0}}, 274 | {{K_T,K_LSHIFT,K_RSHIFT,0}}, 275 | {{K_U,K_LSHIFT,K_RSHIFT,0}}, 276 | {{K_V,K_LSHIFT,K_RSHIFT,0}}, 277 | {{K_W,K_LSHIFT,K_RSHIFT,0}}, 278 | {{K_X,K_LSHIFT,K_RSHIFT,0}}, 279 | {{K_Y,K_LSHIFT,K_RSHIFT,0}}, 280 | {{K_Z,K_LSHIFT,K_RSHIFT,0}}, 281 | {{K_LSQB,0}}, 282 | {{K_BACKSLASH,0}}, 283 | {{K_RSQB,0}}, 284 | {{K_6_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 285 | {{K_MINUS_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 286 | {{K_TILDE,0}}, 287 | {{K_a,0}}, 288 | {{K_b,0}}, 289 | {{K_c,0}}, 290 | {{K_d,0}}, 291 | {{K_e,0}}, 292 | {{K_f,0}}, 293 | {{K_g,0}}, 294 | {{K_h,0}}, 295 | {{K_i,0}}, 296 | {{K_j,0}}, 297 | {{K_k,0}}, 298 | {{K_l,0}}, 299 | {{K_m,0}}, 300 | {{K_n,0}}, 301 | {{K_o,0}}, 302 | {{K_p,0}}, 303 | {{K_q,0}}, 304 | {{K_r,0}}, 305 | {{K_s,0}}, 306 | {{K_t,0}}, 307 | {{K_u,0}}, 308 | {{K_v,0}}, 309 | {{K_w,0}}, 310 | {{K_x,0}}, 311 | {{K_y,0}}, 312 | {{K_z,0}}, 313 | {{K_LCUB,K_LSHIFT,K_RSHIFT,0}}, 314 | {{K_BACKSLASH_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 315 | {{K_RCUB,K_LSHIFT,K_RSHIFT,0}}, 316 | {{K_TILDE_SHIFT,K_LSHIFT,K_RSHIFT,0}}, 317 | {{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}} 318 | }; 319 | 320 | struct combo func_combos[] = { 321 | {{K_F1,0}}, 322 | {{K_F2,0}}, 323 | {{K_F3,0}}, 324 | {{K_F4,0}}, 325 | {{K_F5,0}}, 326 | {{K_F6,0}}, 327 | {{K_F7,0}}, 328 | {{K_F8,0}}, 329 | {{K_F9,0}}, 330 | {{K_F10,0}}, 331 | {{K_F11,0}}, 332 | {{K_F12,0}}, 333 | {{K_INS,0}}, 334 | {{K_DEL,0}}, 335 | {{K_HOM,0}}, 336 | {{K_END,0}}, 337 | {{K_PGU,0}}, 338 | {{K_PGD,0}}, 339 | {{K_ARROW_UP,0}}, 340 | {{K_ARROW_DOWN,0}}, 341 | {{K_ARROW_LEFT,0}}, 342 | {{K_ARROW_RIGHT,0}} 343 | }; 344 | 345 | void print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg) 346 | { 347 | while (*str) { 348 | uint32_t uni; 349 | str += tb_utf8_char_to_unicode(&uni, str); 350 | tb_change_cell(x, y, uni, fg, bg); 351 | x++; 352 | } 353 | } 354 | 355 | void printf_tb(int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...) 356 | { 357 | char buf[4096]; 358 | va_list vl; 359 | va_start(vl, fmt); 360 | vsnprintf(buf, sizeof(buf), fmt, vl); 361 | va_end(vl); 362 | print_tb(buf, x, y, fg, bg); 363 | } 364 | 365 | void draw_key(struct key *k, uint16_t fg, uint16_t bg) 366 | { 367 | while (k->x) { 368 | tb_change_cell(k->x+2, k->y+4, k->ch, fg, bg); 369 | k++; 370 | } 371 | } 372 | 373 | void draw_keyboard() 374 | { 375 | int i; 376 | tb_change_cell(0, 0, 0x250C, TB_WHITE, TB_DEFAULT); 377 | tb_change_cell(79, 0, 0x2510, TB_WHITE, TB_DEFAULT); 378 | tb_change_cell(0, 23, 0x2514, TB_WHITE, TB_DEFAULT); 379 | tb_change_cell(79, 23, 0x2518, TB_WHITE, TB_DEFAULT); 380 | 381 | for (i = 1; i < 79; ++i) { 382 | tb_change_cell(i, 0, 0x2500, TB_WHITE, TB_DEFAULT); 383 | tb_change_cell(i, 23, 0x2500, TB_WHITE, TB_DEFAULT); 384 | tb_change_cell(i, 17, 0x2500, TB_WHITE, TB_DEFAULT); 385 | tb_change_cell(i, 4, 0x2500, TB_WHITE, TB_DEFAULT); 386 | } 387 | for (i = 1; i < 23; ++i) { 388 | tb_change_cell(0, i, 0x2502, TB_WHITE, TB_DEFAULT); 389 | tb_change_cell(79, i, 0x2502, TB_WHITE, TB_DEFAULT); 390 | } 391 | tb_change_cell(0, 17, 0x251C, TB_WHITE, TB_DEFAULT); 392 | tb_change_cell(79, 17, 0x2524, TB_WHITE, TB_DEFAULT); 393 | tb_change_cell(0, 4, 0x251C, TB_WHITE, TB_DEFAULT); 394 | tb_change_cell(79, 4, 0x2524, TB_WHITE, TB_DEFAULT); 395 | for (i = 5; i < 17; ++i) { 396 | tb_change_cell(1, i, 0x2588, TB_YELLOW, TB_YELLOW); 397 | tb_change_cell(78, i, 0x2588, TB_YELLOW, TB_YELLOW); 398 | } 399 | 400 | draw_key(K_ESC, TB_WHITE, TB_BLUE); 401 | draw_key(K_F1, TB_WHITE, TB_BLUE); 402 | draw_key(K_F2, TB_WHITE, TB_BLUE); 403 | draw_key(K_F3, TB_WHITE, TB_BLUE); 404 | draw_key(K_F4, TB_WHITE, TB_BLUE); 405 | draw_key(K_F5, TB_WHITE, TB_BLUE); 406 | draw_key(K_F6, TB_WHITE, TB_BLUE); 407 | draw_key(K_F7, TB_WHITE, TB_BLUE); 408 | draw_key(K_F8, TB_WHITE, TB_BLUE); 409 | draw_key(K_F9, TB_WHITE, TB_BLUE); 410 | draw_key(K_F10, TB_WHITE, TB_BLUE); 411 | draw_key(K_F11, TB_WHITE, TB_BLUE); 412 | draw_key(K_F12, TB_WHITE, TB_BLUE); 413 | draw_key(K_PRN, TB_WHITE, TB_BLUE); 414 | draw_key(K_SCR, TB_WHITE, TB_BLUE); 415 | draw_key(K_BRK, TB_WHITE, TB_BLUE); 416 | draw_key(K_LED1, TB_WHITE, TB_BLUE); 417 | draw_key(K_LED2, TB_WHITE, TB_BLUE); 418 | draw_key(K_LED3, TB_WHITE, TB_BLUE); 419 | 420 | draw_key(K_TILDE, TB_WHITE, TB_BLUE); 421 | draw_key(K_1, TB_WHITE, TB_BLUE); 422 | draw_key(K_2, TB_WHITE, TB_BLUE); 423 | draw_key(K_3, TB_WHITE, TB_BLUE); 424 | draw_key(K_4, TB_WHITE, TB_BLUE); 425 | draw_key(K_5, TB_WHITE, TB_BLUE); 426 | draw_key(K_6, TB_WHITE, TB_BLUE); 427 | draw_key(K_7, TB_WHITE, TB_BLUE); 428 | draw_key(K_8, TB_WHITE, TB_BLUE); 429 | draw_key(K_9, TB_WHITE, TB_BLUE); 430 | draw_key(K_0, TB_WHITE, TB_BLUE); 431 | draw_key(K_MINUS, TB_WHITE, TB_BLUE); 432 | draw_key(K_EQUALS, TB_WHITE, TB_BLUE); 433 | draw_key(K_BACKSLASH, TB_WHITE, TB_BLUE); 434 | draw_key(K_BACKSPACE, TB_WHITE, TB_BLUE); 435 | draw_key(K_INS, TB_WHITE, TB_BLUE); 436 | draw_key(K_HOM, TB_WHITE, TB_BLUE); 437 | draw_key(K_PGU, TB_WHITE, TB_BLUE); 438 | draw_key(K_K_NUMLOCK, TB_WHITE, TB_BLUE); 439 | draw_key(K_K_SLASH, TB_WHITE, TB_BLUE); 440 | draw_key(K_K_STAR, TB_WHITE, TB_BLUE); 441 | draw_key(K_K_MINUS, TB_WHITE, TB_BLUE); 442 | 443 | draw_key(K_TAB, TB_WHITE, TB_BLUE); 444 | draw_key(K_q, TB_WHITE, TB_BLUE); 445 | draw_key(K_w, TB_WHITE, TB_BLUE); 446 | draw_key(K_e, TB_WHITE, TB_BLUE); 447 | draw_key(K_r, TB_WHITE, TB_BLUE); 448 | draw_key(K_t, TB_WHITE, TB_BLUE); 449 | draw_key(K_y, TB_WHITE, TB_BLUE); 450 | draw_key(K_u, TB_WHITE, TB_BLUE); 451 | draw_key(K_i, TB_WHITE, TB_BLUE); 452 | draw_key(K_o, TB_WHITE, TB_BLUE); 453 | draw_key(K_p, TB_WHITE, TB_BLUE); 454 | draw_key(K_LSQB, TB_WHITE, TB_BLUE); 455 | draw_key(K_RSQB, TB_WHITE, TB_BLUE); 456 | draw_key(K_ENTER, TB_WHITE, TB_BLUE); 457 | draw_key(K_DEL, TB_WHITE, TB_BLUE); 458 | draw_key(K_END, TB_WHITE, TB_BLUE); 459 | draw_key(K_PGD, TB_WHITE, TB_BLUE); 460 | draw_key(K_K_7, TB_WHITE, TB_BLUE); 461 | draw_key(K_K_8, TB_WHITE, TB_BLUE); 462 | draw_key(K_K_9, TB_WHITE, TB_BLUE); 463 | draw_key(K_K_PLUS, TB_WHITE, TB_BLUE); 464 | 465 | draw_key(K_CAPS, TB_WHITE, TB_BLUE); 466 | draw_key(K_a, TB_WHITE, TB_BLUE); 467 | draw_key(K_s, TB_WHITE, TB_BLUE); 468 | draw_key(K_d, TB_WHITE, TB_BLUE); 469 | draw_key(K_f, TB_WHITE, TB_BLUE); 470 | draw_key(K_g, TB_WHITE, TB_BLUE); 471 | draw_key(K_h, TB_WHITE, TB_BLUE); 472 | draw_key(K_j, TB_WHITE, TB_BLUE); 473 | draw_key(K_k, TB_WHITE, TB_BLUE); 474 | draw_key(K_l, TB_WHITE, TB_BLUE); 475 | draw_key(K_SEMICOLON, TB_WHITE, TB_BLUE); 476 | draw_key(K_QUOTE, TB_WHITE, TB_BLUE); 477 | draw_key(K_K_4, TB_WHITE, TB_BLUE); 478 | draw_key(K_K_5, TB_WHITE, TB_BLUE); 479 | draw_key(K_K_6, TB_WHITE, TB_BLUE); 480 | 481 | draw_key(K_LSHIFT, TB_WHITE, TB_BLUE); 482 | draw_key(K_z, TB_WHITE, TB_BLUE); 483 | draw_key(K_x, TB_WHITE, TB_BLUE); 484 | draw_key(K_c, TB_WHITE, TB_BLUE); 485 | draw_key(K_v, TB_WHITE, TB_BLUE); 486 | draw_key(K_b, TB_WHITE, TB_BLUE); 487 | draw_key(K_n, TB_WHITE, TB_BLUE); 488 | draw_key(K_m, TB_WHITE, TB_BLUE); 489 | draw_key(K_COMMA, TB_WHITE, TB_BLUE); 490 | draw_key(K_PERIOD, TB_WHITE, TB_BLUE); 491 | draw_key(K_SLASH, TB_WHITE, TB_BLUE); 492 | draw_key(K_RSHIFT, TB_WHITE, TB_BLUE); 493 | draw_key(K_ARROW_UP, TB_WHITE, TB_BLUE); 494 | draw_key(K_K_1, TB_WHITE, TB_BLUE); 495 | draw_key(K_K_2, TB_WHITE, TB_BLUE); 496 | draw_key(K_K_3, TB_WHITE, TB_BLUE); 497 | draw_key(K_K_ENTER, TB_WHITE, TB_BLUE); 498 | 499 | draw_key(K_LCTRL, TB_WHITE, TB_BLUE); 500 | draw_key(K_LWIN, TB_WHITE, TB_BLUE); 501 | draw_key(K_LALT, TB_WHITE, TB_BLUE); 502 | draw_key(K_SPACE, TB_WHITE, TB_BLUE); 503 | draw_key(K_RCTRL, TB_WHITE, TB_BLUE); 504 | draw_key(K_RPROP, TB_WHITE, TB_BLUE); 505 | draw_key(K_RWIN, TB_WHITE, TB_BLUE); 506 | draw_key(K_RALT, TB_WHITE, TB_BLUE); 507 | draw_key(K_ARROW_LEFT, TB_WHITE, TB_BLUE); 508 | draw_key(K_ARROW_DOWN, TB_WHITE, TB_BLUE); 509 | draw_key(K_ARROW_RIGHT, TB_WHITE, TB_BLUE); 510 | draw_key(K_K_0, TB_WHITE, TB_BLUE); 511 | draw_key(K_K_PERIOD, TB_WHITE, TB_BLUE); 512 | 513 | printf_tb(33, 1, TB_MAGENTA | TB_BOLD, TB_DEFAULT, "Keyboard demo!"); 514 | printf_tb(21, 2, TB_MAGENTA, TB_DEFAULT, "(press CTRL+X and then CTRL+Q to exit)"); 515 | printf_tb(15, 3, TB_MAGENTA, TB_DEFAULT, "(press CTRL+X and then CTRL+C to change input mode)"); 516 | 517 | int inputmode = tb_select_input_mode(0); 518 | char inputmode_str[64]; 519 | 520 | if (inputmode & TB_INPUT_ESC) 521 | sprintf(inputmode_str, "TB_INPUT_ESC"); 522 | if (inputmode & TB_INPUT_ALT) 523 | sprintf(inputmode_str, "TB_INPUT_ALT"); 524 | 525 | if (inputmode & TB_INPUT_MOUSE) { 526 | char temp[32]; 527 | strcpy(temp, inputmode_str); 528 | sprintf(inputmode_str, "%s | TB_INPUT_MOUSE", temp); 529 | } 530 | 531 | printf_tb(3, 18, TB_WHITE, TB_DEFAULT, "Input mode: %s", inputmode_str); 532 | } 533 | 534 | const char *funckeymap(int k) 535 | { 536 | static const char *fcmap[] = { 537 | "CTRL+2, CTRL+~", 538 | "CTRL+A", 539 | "CTRL+B", 540 | "CTRL+C", 541 | "CTRL+D", 542 | "CTRL+E", 543 | "CTRL+F", 544 | "CTRL+G", 545 | "CTRL+H, BACKSPACE", 546 | "CTRL+I, TAB", 547 | "CTRL+J", 548 | "CTRL+K", 549 | "CTRL+L", 550 | "CTRL+M, ENTER", 551 | "CTRL+N", 552 | "CTRL+O", 553 | "CTRL+P", 554 | "CTRL+Q", 555 | "CTRL+R", 556 | "CTRL+S", 557 | "CTRL+T", 558 | "CTRL+U", 559 | "CTRL+V", 560 | "CTRL+W", 561 | "CTRL+X", 562 | "CTRL+Y", 563 | "CTRL+Z", 564 | "CTRL+3, ESC, CTRL+[", 565 | "CTRL+4, CTRL+\\", 566 | "CTRL+5, CTRL+]", 567 | "CTRL+6", 568 | "CTRL+7, CTRL+/, CTRL+_", 569 | "SPACE" 570 | }; 571 | static const char *fkmap[] = { 572 | "F1", 573 | "F2", 574 | "F3", 575 | "F4", 576 | "F5", 577 | "F6", 578 | "F7", 579 | "F8", 580 | "F9", 581 | "F10", 582 | "F11", 583 | "F12", 584 | "INSERT", 585 | "DELETE", 586 | "HOME", 587 | "END", 588 | "PGUP", 589 | "PGDN", 590 | "ARROW UP", 591 | "ARROW DOWN", 592 | "ARROW LEFT", 593 | "ARROW RIGHT" 594 | }; 595 | 596 | if (k == TB_KEY_CTRL_8) 597 | return "CTRL+8, BACKSPACE 2"; /* 0x7F */ 598 | else if (k >= TB_KEY_ARROW_RIGHT && k <= 0xFFFF) 599 | return fkmap[0xFFFF-k]; 600 | else if (k <= TB_KEY_SPACE) 601 | return fcmap[k]; 602 | return "UNKNOWN"; 603 | } 604 | 605 | void pretty_print_press(struct tb_event *ev) 606 | { 607 | char buf[7]; 608 | buf[tb_utf8_unicode_to_char(buf, ev->ch)] = '\0'; 609 | printf_tb(3, 19, TB_WHITE , TB_DEFAULT, "Key: "); 610 | printf_tb(8, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->key); 611 | printf_tb(8, 20, TB_GREEN , TB_DEFAULT, "hex: 0x%X", ev->key); 612 | printf_tb(8, 21, TB_CYAN , TB_DEFAULT, "octal: 0%o", ev->key); 613 | printf_tb(8, 22, TB_RED , TB_DEFAULT, "string: %s", funckeymap(ev->key)); 614 | 615 | printf_tb(54, 19, TB_WHITE , TB_DEFAULT, "Char: "); 616 | printf_tb(60, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->ch); 617 | printf_tb(60, 20, TB_GREEN , TB_DEFAULT, "hex: 0x%X", ev->ch); 618 | printf_tb(60, 21, TB_CYAN , TB_DEFAULT, "octal: 0%o", ev->ch); 619 | printf_tb(60, 22, TB_RED , TB_DEFAULT, "string: %s", buf); 620 | 621 | printf_tb(54, 18, TB_WHITE, TB_DEFAULT, "Modifier: %s", 622 | (ev->mod) ? "TB_MOD_ALT" : "none"); 623 | 624 | } 625 | 626 | void pretty_print_resize(struct tb_event *ev) 627 | { 628 | printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Resize event: %d x %d", ev->w, ev->h); 629 | } 630 | 631 | int counter = 0; 632 | 633 | void pretty_print_mouse(struct tb_event *ev) { 634 | printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Mouse event: %d x %d", ev->x, ev->y); 635 | char *btn = ""; 636 | switch (ev->key) { 637 | case TB_KEY_MOUSE_LEFT: 638 | btn = "MouseLeft: %d"; 639 | break; 640 | case TB_KEY_MOUSE_MIDDLE: 641 | btn = "MouseMiddle: %d"; 642 | break; 643 | case TB_KEY_MOUSE_RIGHT: 644 | btn = "MouseRight: %d"; 645 | break; 646 | case TB_KEY_MOUSE_WHEEL_UP: 647 | btn = "MouseWheelUp: %d"; 648 | break; 649 | case TB_KEY_MOUSE_WHEEL_DOWN: 650 | btn = "MouseWheelDown: %d"; 651 | break; 652 | case TB_KEY_MOUSE_RELEASE: 653 | btn = "MouseRelease: %d"; 654 | } 655 | counter++; 656 | printf_tb(43, 19, TB_WHITE, TB_DEFAULT, "Key: "); 657 | printf_tb(48, 19, TB_YELLOW, TB_DEFAULT, btn, counter); 658 | } 659 | 660 | void dispatch_press(struct tb_event *ev) 661 | { 662 | if (ev->mod & TB_MOD_ALT) { 663 | draw_key(K_LALT, TB_WHITE, TB_RED); 664 | draw_key(K_RALT, TB_WHITE, TB_RED); 665 | } 666 | 667 | struct combo *k = 0; 668 | if (ev->key >= TB_KEY_ARROW_RIGHT) 669 | k = &func_combos[0xFFFF-ev->key]; 670 | else if (ev->ch < 128) { 671 | if (ev->ch == 0 && ev->key < 128) 672 | k = &combos[ev->key]; 673 | else 674 | k = &combos[ev->ch]; 675 | } 676 | if (!k) 677 | return; 678 | 679 | struct key **keys = k->keys; 680 | while (*keys) { 681 | draw_key(*keys, TB_WHITE, TB_RED); 682 | keys++; 683 | } 684 | } 685 | 686 | int main(int argc, char **argv) 687 | { 688 | (void) argc; (void) argv; 689 | int ret; 690 | 691 | ret = tb_init(); 692 | if (ret) { 693 | fprintf(stderr, "tb_init() failed with error code %d\n", ret); 694 | return 1; 695 | } 696 | 697 | tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); 698 | struct tb_event ev; 699 | 700 | tb_clear(); 701 | draw_keyboard(); 702 | tb_present(); 703 | int inputmode = 0; 704 | int ctrlxpressed = 0; 705 | 706 | while (tb_poll_event(&ev)) { 707 | switch (ev.type) { 708 | case TB_EVENT_KEY: 709 | if (ev.key == TB_KEY_CTRL_Q && ctrlxpressed) { 710 | tb_shutdown(); 711 | return 0; 712 | } 713 | if (ev.key == TB_KEY_CTRL_C && ctrlxpressed) { 714 | static int chmap[] = { 715 | TB_INPUT_ESC | TB_INPUT_MOUSE, /* 101 */ 716 | TB_INPUT_ALT | TB_INPUT_MOUSE, /* 110 */ 717 | TB_INPUT_ESC, /* 001 */ 718 | TB_INPUT_ALT, /* 010 */ 719 | }; 720 | inputmode++; 721 | if (inputmode >= 4) { 722 | inputmode = 0; 723 | } 724 | tb_select_input_mode(chmap[inputmode]); 725 | } 726 | if (ev.key == TB_KEY_CTRL_X) 727 | ctrlxpressed = 1; 728 | else 729 | ctrlxpressed = 0; 730 | 731 | tb_clear(); 732 | draw_keyboard(); 733 | dispatch_press(&ev); 734 | pretty_print_press(&ev); 735 | tb_present(); 736 | break; 737 | case TB_EVENT_RESIZE: 738 | tb_clear(); 739 | draw_keyboard(); 740 | pretty_print_resize(&ev); 741 | tb_present(); 742 | break; 743 | case TB_EVENT_MOUSE: 744 | tb_clear(); 745 | draw_keyboard(); 746 | pretty_print_mouse(&ev); 747 | tb_present(); 748 | break; 749 | default: 750 | break; 751 | } 752 | } 753 | tb_shutdown(); 754 | return 0; 755 | } 756 | -------------------------------------------------------------------------------- /demo/output.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "termbox.h" 4 | 5 | static const char chars[] = "nnnnnnnnnbbbbbbbbbfffffffffuuuuuuuuuiiiiiiiiicccccccccBBBBBBBBB"; 6 | 7 | static const uint16_t all_attrs[] = { 8 | 0, 9 | TB_BOLD, 10 | TB_FAINT, 11 | TB_UNDERLINE, 12 | TB_ITALIC, 13 | TB_CROSSOUT, 14 | TB_BOLD | TB_FAINT | TB_UNDERLINE | TB_ITALIC | TB_CROSSOUT, 15 | }; 16 | static const int all_attrs_n = sizeof(all_attrs) / sizeof(uint16_t); 17 | 18 | static int next_char(int current) { 19 | current++; 20 | if (!chars[current]) 21 | current = 0; 22 | return current; 23 | } 24 | 25 | static void draw_line(int x, int y, uint16_t bg) { 26 | int a, c; 27 | int current_char = 0; 28 | for (a = 0; a < all_attrs_n; a++) { 29 | for (c = TB_DEFAULT; c <= TB_WHITE; c++) { 30 | uint16_t fg = all_attrs[a] | c; 31 | tb_change_cell(x, y, chars[current_char], fg, bg); 32 | current_char = next_char(current_char); 33 | x++; 34 | } 35 | } 36 | } 37 | 38 | static void print_combinations_table(int sx, int sy, const uint16_t *attrs, int attrs_n) { 39 | int i, c; 40 | for (i = 0; i < attrs_n; i++) { 41 | for (c = TB_DEFAULT; c <= TB_WHITE; c++) { 42 | uint16_t bg = attrs[i] | c; 43 | draw_line(sx, sy, bg); 44 | sy++; 45 | } 46 | } 47 | } 48 | 49 | static void draw_all() { 50 | tb_clear(); 51 | 52 | tb_select_output_mode(TB_OUTPUT_NORMAL); 53 | static const uint16_t col1[] = {0, TB_BOLD}; 54 | static const uint16_t col2[] = {TB_REVERSE}; 55 | print_combinations_table(1, 1, col1, 2); 56 | print_combinations_table(2 + strlen(chars), 1, col2, 1); 57 | tb_present(); 58 | 59 | tb_select_output_mode(TB_OUTPUT_GRAYSCALE); 60 | int c, x, y; 61 | for (x = 0, y = 23; x < 24; ++x) { 62 | tb_change_cell(x, y, '@', x, 0); 63 | tb_change_cell(x+25, y, ' ', 0, x); 64 | } 65 | tb_present(); 66 | 67 | tb_select_output_mode(TB_OUTPUT_216); 68 | y++; 69 | for (c = 0, x = 0; c < 216; ++c, ++x) { 70 | if (!(x%24)) { 71 | x = 0; 72 | ++y; 73 | } 74 | tb_change_cell(x, y, '@', c, 0); 75 | tb_change_cell(x+25, y, ' ', 0, c); 76 | } 77 | tb_present(); 78 | 79 | tb_select_output_mode(TB_OUTPUT_256); 80 | y++; 81 | for (c = 0, x = 0; c < 256; ++c, ++x) { 82 | if (!(x%24)) { 83 | x = 0; 84 | ++y; 85 | } 86 | tb_change_cell(x, y, '+', c | ((y & 1) ? TB_UNDERLINE : 0), 0); 87 | tb_change_cell(x+25, y, ' ', 0, c); 88 | } 89 | tb_present(); 90 | } 91 | 92 | int main(int argc, char **argv) { 93 | (void)argc; (void)argv; 94 | int ret = tb_init(); 95 | if (ret) { 96 | fprintf(stderr, "tb_init() failed with error code %d\n", ret); 97 | return 1; 98 | } 99 | 100 | draw_all(); 101 | 102 | struct tb_event ev; 103 | while (tb_poll_event(&ev)) { 104 | switch (ev.type) { 105 | case TB_EVENT_KEY: 106 | switch (ev.key) { 107 | case TB_KEY_ESC: 108 | goto done; 109 | break; 110 | } 111 | break; 112 | case TB_EVENT_RESIZE: 113 | draw_all(); 114 | break; 115 | } 116 | } 117 | done: 118 | tb_shutdown(); 119 | return 0; 120 | } 121 | 122 | // vim: noexpandtab 123 | -------------------------------------------------------------------------------- /demo/paint.c: -------------------------------------------------------------------------------- 1 | #include "termbox.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static int curCol = 0; 7 | static int curRune = 0; 8 | static struct tb_cell *backbuf; 9 | static int bbw = 0, bbh = 0; 10 | 11 | static const uint32_t runes[] = { 12 | 0x20, // ' ' 13 | 0x2591, // '░' 14 | 0x2592, // '▒' 15 | 0x2593, // '▓' 16 | 0x2588, // '█' 17 | }; 18 | 19 | #define len(a) (sizeof(a)/sizeof(a[0])) 20 | 21 | static const uint16_t colors[] = { 22 | TB_BLACK, 23 | TB_RED, 24 | TB_GREEN, 25 | TB_YELLOW, 26 | TB_BLUE, 27 | TB_MAGENTA, 28 | TB_CYAN, 29 | TB_WHITE, 30 | }; 31 | 32 | void updateAndDrawButtons(int *current, int x, int y, int mx, int my, int n, void (*attrFunc)(int, uint32_t*, uint16_t*, uint16_t*)) { 33 | int lx = x; 34 | int ly = y; 35 | for (int i = 0; i < n; i++) { 36 | if (lx <= mx && mx <= lx+3 && ly <= my && my <= ly+1) { 37 | *current = i; 38 | } 39 | uint32_t r; 40 | uint16_t fg, bg; 41 | (*attrFunc)(i, &r, &fg, &bg); 42 | tb_change_cell(lx+0, ly+0, r, fg, bg); 43 | tb_change_cell(lx+1, ly+0, r, fg, bg); 44 | tb_change_cell(lx+2, ly+0, r, fg, bg); 45 | tb_change_cell(lx+3, ly+0, r, fg, bg); 46 | tb_change_cell(lx+0, ly+1, r, fg, bg); 47 | tb_change_cell(lx+1, ly+1, r, fg, bg); 48 | tb_change_cell(lx+2, ly+1, r, fg, bg); 49 | tb_change_cell(lx+3, ly+1, r, fg, bg); 50 | lx += 4; 51 | } 52 | lx = x; 53 | ly = y; 54 | for (int i = 0; i < n; i++) { 55 | if (*current == i) { 56 | uint16_t fg = TB_RED | TB_BOLD; 57 | uint16_t bg = TB_DEFAULT; 58 | tb_change_cell(lx+0, ly+2, '^', fg, bg); 59 | tb_change_cell(lx+1, ly+2, '^', fg, bg); 60 | tb_change_cell(lx+2, ly+2, '^', fg, bg); 61 | tb_change_cell(lx+3, ly+2, '^', fg, bg); 62 | } 63 | lx += 4; 64 | } 65 | } 66 | 67 | void runeAttrFunc(int i, uint32_t *r, uint16_t *fg, uint16_t *bg) { 68 | *r = runes[i]; 69 | *fg = TB_DEFAULT; 70 | *bg = TB_DEFAULT; 71 | } 72 | 73 | void colorAttrFunc(int i, uint32_t *r, uint16_t *fg, uint16_t *bg) { 74 | *r = ' '; 75 | *fg = TB_DEFAULT; 76 | *bg = colors[i]; 77 | } 78 | 79 | void updateAndRedrawAll(int mx, int my) { 80 | tb_clear(); 81 | if (mx != -1 && my != -1) { 82 | backbuf[bbw*my+mx].ch = runes[curRune]; 83 | backbuf[bbw*my+mx].sgr.at |= SGR_BG; 84 | backbuf[bbw*my+mx].sgr.bg = colors[curCol] - 1; 85 | } 86 | memcpy(tb_cell_buffer(), backbuf, sizeof(struct tb_cell)*bbw*bbh); 87 | int h = tb_height(); 88 | updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), runeAttrFunc); 89 | updateAndDrawButtons(&curCol, 0, h-3, mx, my, len(colors), colorAttrFunc); 90 | tb_present(); 91 | } 92 | 93 | void reallocBackBuffer(int w, int h) { 94 | bbw = w; 95 | bbh = h; 96 | if (backbuf) 97 | free(backbuf); 98 | backbuf = calloc(sizeof(struct tb_cell), w*h); 99 | } 100 | 101 | int main(int argv, char **argc) { 102 | (void)argc; (void)argv; 103 | int code = tb_init(); 104 | if (code < 0) { 105 | fprintf(stderr, "termbox init failed, code: %d\n", code); 106 | return -1; 107 | } 108 | 109 | tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); 110 | int w = tb_width(); 111 | int h = tb_height(); 112 | reallocBackBuffer(w, h); 113 | updateAndRedrawAll(-1, -1); 114 | for (;;) { 115 | struct tb_event ev; 116 | int mx = -1; 117 | int my = -1; 118 | int t = tb_poll_event(&ev); 119 | if (t == -1) { 120 | tb_shutdown(); 121 | fprintf(stderr, "termbox poll event error\n"); 122 | return -1; 123 | } 124 | 125 | switch (t) { 126 | case TB_EVENT_KEY: 127 | if (ev.key == TB_KEY_ESC) { 128 | tb_shutdown(); 129 | return 0; 130 | } 131 | break; 132 | case TB_EVENT_MOUSE: 133 | if (ev.key == TB_KEY_MOUSE_LEFT) { 134 | mx = ev.x; 135 | my = ev.y; 136 | } 137 | break; 138 | case TB_EVENT_RESIZE: 139 | reallocBackBuffer(ev.w, ev.h); 140 | break; 141 | } 142 | updateAndRedrawAll(mx, my); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /demo/pkbd.c: -------------------------------------------------------------------------------- 1 | #include "../tkbd.h" 2 | 3 | #include // printf 4 | #include // STDIN_FILENO 5 | #include // errno 6 | #include // strerror 7 | 8 | #define rmcup "\033[?1049l" // enter ca mode 9 | #define smcup "\033[?1049h" // exit ca mode 10 | 11 | int main(int argc, char **argv) 12 | { 13 | int appmode = (argc > 1 && strcmp(argv[1], "-a") == 0); 14 | 15 | // attach to standard input and enter raw mode 16 | struct tkbd_stream s = {0}; 17 | int err = tkbd_attach(&s, STDIN_FILENO); 18 | if (err) { 19 | err = errno; 20 | fprintf(stderr, "error: tkbd_attach: %s %d\n", strerror(err), err); 21 | return 1; 22 | } 23 | 24 | // enter application mode 25 | if (appmode) 26 | printf("%s", rmcup); 27 | 28 | // read keys and print info 29 | for (;;) { 30 | struct tkbd_seq seq = {0}; 31 | int n = tkbd_read(&s, &seq); 32 | 33 | // timeout before data arrived 34 | if (n == 0) 35 | continue; 36 | 37 | char desc[64]; 38 | tkbd_desc(desc, sizeof(desc), &seq); 39 | 40 | char seqesc[TKBD_SEQ_MAX*4]; 41 | tkbd_stresc(seqesc, seq.data, seq.len); 42 | 43 | printf("%-22s %-14s key=0x%02hhx, mod=0x%02hhx, ch=0x%02hhx, sz=%d\n", 44 | desc, seqesc, seq.key, seq.mod, seq.ch, n); 45 | 46 | if (seq.key == TKBD_KEY_Q && seq.mod == TKBD_MOD_NONE) 47 | break; 48 | } 49 | 50 | // exit application mode 51 | if (appmode) 52 | printf("%s", smcup); 53 | 54 | // detach from standard input and exit raw mode 55 | err = tkbd_detach(&s); 56 | if (err) { 57 | err = errno; 58 | fprintf(stderr, "error: tkbd_detach: %s %d\n", strerror(err), err); 59 | return 1; 60 | } 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /sgr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * sgr.c 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * See sgr.h for usage and interface documentation. 7 | * 8 | * 9 | */ 10 | 11 | #include "sgr.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // Fills an array of ints with SGR formatting codes for the sgr structure. 19 | // This is mostly used internally to drive encoding to SGR string sequence. 20 | int sgr_unpack(uint16_t codes[], struct sgr sgr) 21 | { 22 | int pos = 0; 23 | int at = sgr.at; 24 | 25 | // flip on/off attributes off instead of on 26 | int neg = (at&SGR_NEGATE) ? 20 : 0; 27 | 28 | // reset all attributes before applying subsequent ones 29 | if (at&SGR_RESET) codes[pos++] = 0; 30 | 31 | // bold on/off is a special case because code 21 is double underline 32 | if (at&SGR_BOLD) 33 | codes[pos++] = neg ? 22 : 1; 34 | 35 | // on/off typographic and background attributes 36 | if (at&SGR_FAINT) codes[pos++] = neg + 2; 37 | if (at&SGR_ITALIC) codes[pos++] = neg + 3; 38 | if (at&SGR_UNDERLINE) codes[pos++] = neg + 4; 39 | if (at&SGR_BLINK) codes[pos++] = neg + 5; 40 | if (at&SGR_REVERSE) codes[pos++] = neg + 7; 41 | if (at&SGR_STRIKE) codes[pos++] = neg + 9; 42 | 43 | // make it possible to loop over fg/bg colors with mode 44 | // note that sgr.fg and bg members only have 24 bits. 45 | struct { int mode; uint32_t color; } fgbg[2] = { 46 | { at&SGR_FG_MASK, sgr.fg }, 47 | { at&SGR_BG_MASK, sgr.bg }, 48 | }; 49 | 50 | for (int i = 0; i < 2; i++) { 51 | int mode = fgbg[i].mode; 52 | if (!mode) continue; 53 | 54 | uint32_t color = fgbg[i].color; 55 | 56 | int is_bg = mode & SGR_BG_MASK; 57 | int is_8 = (mode == SGR_FG || mode == SGR_BG); 58 | int is_16 = (mode == SGR_FG16 || mode == SGR_BG16); 59 | int is_24 = (mode == SGR_FG24 || mode == SGR_BG24); 60 | int is_216 = (mode == SGR_FG216 || mode == SGR_BG216); 61 | int is_256 = (mode == SGR_FG256 || mode == SGR_BG256); 62 | int is_16m = (mode == SGR_FG16M || mode == SGR_BG16M); 63 | 64 | if (is_8 || is_16 || neg) { 65 | // SGR_NEGATE specified: use default color 66 | if (neg) color = 9; 67 | 68 | // truncate to 8 colors but allow 9 (default fg/bg) 69 | if (color > 7 && color != SGR_DEFAULT) 70 | color = 7; 71 | 72 | color += 30; // normal fg [3x range] 73 | if (is_bg) color += 10; // normal bg [4x range] 74 | 75 | if (is_16) color += 60; // bright fg [9x] or bg [10x] 76 | codes[pos++] = (uint16_t)color; 77 | 78 | } else if (is_24 || is_216 || is_256) { 79 | if (is_bg) codes[pos++] = 48; // set bg 80 | else codes[pos++] = 38; // set fg 81 | codes[pos++] = 5; 82 | 83 | // the 24, 216, and 256 color modes all use the same 84 | // 256-color palette; we just adjust the index here 85 | // for convenience. 86 | if (is_24) { 87 | if (color > 23) color = 23; 88 | color += 232; 89 | } else if (is_216) { 90 | if (color > 215) color = 215; 91 | color += 16; 92 | } 93 | 94 | codes[pos++] = (uint16_t)color; 95 | } else if (is_16m) { 96 | // https://gist.github.com/XVilka/8346728 97 | if (is_bg) codes[pos++] = 48; // set bg 98 | else codes[pos++] = 38; // set fg 99 | codes[pos++] = 2; 100 | 101 | codes[pos++] = (color&0xFF0000) >> 16; // red 102 | codes[pos++] = (color&0x00FF00) >> 8; // green 103 | codes[pos++] = (color&0x0000FF); // blue 104 | } 105 | } 106 | 107 | return pos; 108 | } 109 | 110 | 111 | // Simple itoa that only handles positive numbers. 112 | // Returns the number of chars written to buf not including the \0 terminator. 113 | static inline int uitoa(uint16_t n, char *buf) 114 | { 115 | // write numerics into string backwards 116 | int i = 0; 117 | do { 118 | buf[i++] = n % 10 + '0'; 119 | } while ((n /= 10) > 0); 120 | buf[i] = '\0'; 121 | 122 | // now reverse it 123 | for (int j = 0; j < i / 2; j++) { 124 | int k = i - j - 1; 125 | char a = buf[j]; 126 | buf[j] = buf[k]; 127 | buf[k] = a; 128 | } 129 | 130 | return i; 131 | } 132 | 133 | // Emit string characters for an SGR value. 134 | // This lets us keep the uint32_t -> string logic in one place but allow writing 135 | // to different types of mediums (FILE, fd, char* buffer, etc.). 136 | int sgr_encode(void *p, void (*func)(void *, char *, int), struct sgr sgr) 137 | { 138 | uint16_t codes[SGR_ELMS_MAX]; 139 | int ncodes = sgr_unpack(codes, sgr); 140 | if (ncodes == 0) return 0; 141 | 142 | int sz = 0; 143 | 144 | // write SGR open: "\e[" 145 | func(p, SGR_OPEN, sizeof(SGR_OPEN) - 1); 146 | sz += sizeof(SGR_OPEN) - 1; 147 | 148 | // write formatting codes separated by ";" 149 | int n; 150 | char buf[8]; 151 | for (int i = 0; i < ncodes; i++) { 152 | if (i > 0) { 153 | // write SGR separator: ";" 154 | func(p, SGR_SEP, sizeof(SGR_SEP) - 1); 155 | sz += sizeof(SGR_SEP) - 1; 156 | } 157 | n = uitoa(codes[i], buf); 158 | func(p, buf, n); 159 | sz += n; 160 | } 161 | 162 | // write SGR close: "m" 163 | func(p, SGR_CLOSE, sizeof(SGR_CLOSE) - 1); 164 | sz += sizeof(SGR_CLOSE) - 1; 165 | 166 | return sz; 167 | } 168 | 169 | 170 | struct strbuf { 171 | int pos; 172 | char *str; 173 | }; 174 | 175 | static void strbuf_write(void *dest, char *src, int n) 176 | { 177 | struct strbuf *buf = (struct strbuf*)dest; 178 | memcpy(buf->str + buf->pos, src, n); 179 | buf->pos += n; 180 | } 181 | 182 | int sgr_str(char *dest, struct sgr sgr) 183 | { 184 | struct strbuf buf = { 0, dest }; 185 | int sz = sgr_encode(&buf, strbuf_write, sgr); 186 | dest[sz] = '\0'; 187 | return sz; 188 | 189 | } 190 | 191 | 192 | struct fdinfo { 193 | int fd; 194 | int err; 195 | int sz; 196 | }; 197 | 198 | // Note: be careful not to accidentally clear errno since that's 199 | // how the caller of sgr_write() will access error info. 200 | static void fd_write(void *dest, char *src, int n) 201 | { 202 | struct fdinfo *f = dest; 203 | if (f->err) return; 204 | 205 | int sz = write(f->fd, src, n); 206 | if (sz == -1) { 207 | f->err = errno; 208 | return; 209 | } 210 | 211 | f->sz += sz; 212 | } 213 | 214 | int sgr_write(int fd, struct sgr sgr) 215 | { 216 | struct fdinfo f = { fd, 0, 0 }; 217 | sgr_encode(&f, fd_write, sgr); 218 | if (f.err) { 219 | return -1; 220 | } 221 | return f.sz; 222 | } 223 | 224 | 225 | 226 | struct streaminfo { 227 | FILE *stream; 228 | int sz; 229 | int err; 230 | int eof; 231 | }; 232 | 233 | static void stream_write(void *dest, char *src, int n) 234 | { 235 | struct streaminfo *f = dest; 236 | if (f->err || f->eof) return; 237 | 238 | size_t sz = fwrite(src, 1, n, f->stream); 239 | f->sz += sz; 240 | 241 | if (sz < (size_t)n) { 242 | f->eof = feof(f->stream); 243 | f->err = ferror(f->stream); 244 | } 245 | } 246 | 247 | int sgr_fwrite(FILE *stream, struct sgr sgr) 248 | { 249 | struct streaminfo f = { stream, 0, 0, 0 }; 250 | sgr_encode(&f, stream_write, sgr); 251 | if (f.err || f.eof) { 252 | return -1; 253 | } 254 | return f.sz; 255 | } 256 | 257 | // vim: noexpandtab 258 | -------------------------------------------------------------------------------- /sgr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * sgr.h - Select Graphic Rendition (SGR) escape sequence generation library 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * Generate ANSI/ECMA-48/VT escape sequences for controlling typographic 7 | * features--like bold, italic, underline, faint, blink, reverse, and cross-out; 8 | * as well as foreground and background colors--in a variety of color modes. 9 | * 10 | * This library generates SGR escape sequences only. It does not attempt to 11 | * query terminfo for terminal capability strings. 12 | * 13 | * 14 | */ 15 | 16 | #pragma once 17 | 18 | #include 19 | #include 20 | 21 | /* 22 | * SGR struct 23 | * 24 | * The struct is designed to pack all possible typographic attributes and color 25 | * information into 64 bits, making it practical to store sgr information for 26 | * each cell in a terminal display. An array holding sgr information for each 27 | * cell in a 100x100 terminal would occupy about 80KB memory. 28 | * 29 | * Note that each member is represented as a 64bit integer but packed down to 30 | * 16 bits for typographic attributes and 24 bits for each color. 31 | * 32 | */ 33 | struct sgr { 34 | uint64_t at : 16; // attributes bitflags (defined below) 35 | uint64_t fg : 24; // foreground color 36 | uint64_t bg : 24; // background color 37 | }; 38 | 39 | /* 40 | * Basic 8-color mode colors. 41 | * 42 | * The SGR_DEFAULT color specifies the terminal's default foreground or 43 | * background color, which may be different from color 0 / color 7. 44 | * 45 | */ 46 | #define SGR_BLACK 0x00 47 | #define SGR_RED 0x01 48 | #define SGR_GREEN 0x02 49 | #define SGR_YELLOW 0x03 50 | #define SGR_BLUE 0x04 51 | #define SGR_MAGENTA 0x05 52 | #define SGR_CYAN 0x06 53 | #define SGR_WHITE 0x07 54 | #define SGR_DEFAULT 0x09 55 | 56 | /* 57 | * SGR attributes are defined as bitflags below and combined to specify 58 | * rendering features. These MASKs are used to extract ranges of flags from 59 | * the 16-bit attribute integer. 60 | * 61 | * All attributes are stored in the least significant 16-bits and look like this 62 | * in binary little endian: 63 | * 64 | * nrbbbfffS.RLUIFB (flag) 65 | * 1111111100000000 (octal position) 66 | * 7654321076543210 67 | * 68 | * B=bold, F=faint, I=italic, U=underline, L=blink, R=reverse, S=strike 69 | * f=foreground color mode, b=background color mode 70 | * r=reset, n=negate 71 | * .=unused 72 | * 73 | */ 74 | #define SGR_ATTR_MASK 0x00ff // all typographic attribute bits 75 | #define SGR_FG_MASK 0x0700 // all fg color mode selection bits 76 | #define SGR_BG_MASK 0x3800 // all bg color mode selection bits 77 | #define SGR_CTRL_MASK 0xc000 // all control bits 78 | 79 | /* 80 | * Typographic and cell display attributes 81 | * 82 | * These control typographical aspects of the text and character cell and may be 83 | * combined in any configuration: 84 | * 85 | * struct sgr seq = { SGR_BOLD|SGR_UNDERLINE|SGR_ITALIC|SGR_REVERSE } 86 | * 87 | */ 88 | #define SGR_BOLD 0x0001 // text is bold 89 | #define SGR_FAINT 0x0002 // text is faint or dim 90 | #define SGR_ITALIC 0x0004 // text is rendered in italic font 91 | #define SGR_UNDERLINE 0x0008 // text is underlined 92 | #define SGR_BLINK 0x0010 // cell is blinking 93 | #define SGR_REVERSE 0x0020 // cell colors are reversed 94 | #define SGR_CONCEAL 0x0040 // TODO: cell is concealed 95 | #define SGR_STRIKE 0x0080 // text is strike-through 96 | 97 | /* 98 | * Color mode attributes 99 | * 100 | * These control whether the foreground and background colors are applied and 101 | * the color mode palette of the color. 102 | * 103 | * IMPORTANT: No color sequences are generated unless one of the color modes 104 | * is specified for both the foreground and background colors. 105 | * 106 | * Example SGR sequence with bold cyan text on a bright yellow background: 107 | * 108 | * struct sgr seq = { SGR_BOLD|SGR_FG|SGR_BG16, SGR_CYAN, SGR_YELLOW } 109 | * 110 | */ 111 | #define SGR_FG 0x0100 // fg is normal 8-color mode color 112 | #define SGR_FG16 0x0200 // fg is bright 16-color mode color 113 | #define SGR_FG24 0x0300 // fg is 24-color greyscale color 114 | #define SGR_FG216 0x0400 // fg is 216-color mode color 115 | #define SGR_FG256 0x0500 // fg is 256-color mode color 116 | #define SGR_FG16M 0x0600 // fg is 16M-color "true color" mode color 117 | 118 | #define SGR_BG 0x0800 // bg is normal 8-color mode color 119 | #define SGR_BG16 0x1000 // bg is bright 16-color mode color 120 | #define SGR_BG24 0x1800 // bg is 24-color greyscale color 121 | #define SGR_BG216 0x2000 // bg is 216-color mode color 122 | #define SGR_BG256 0x2800 // bg is 256-color mode color 123 | #define SGR_BG16M 0x3000 // bg is 16M-color "true color" mode color 124 | 125 | /* 126 | * Render control attributes 127 | * 128 | * When the SGR_RESET attribute is set, all attributes are reset to their 129 | * default values before current attributes are applied. 130 | * 131 | * When the SGR_NEGATE attribute is set, all set attributes are reset to their 132 | * default values. i.e., SGR_NEGATE turns attributes off. 133 | */ 134 | #define SGR_INHERIT 0x0000 // inherit unset attributes (default) 135 | #define SGR_RESET 0x4000 // sgr0 all attribute reset before applying 136 | #define SGR_NEGATE 0x8000 // turn all set attrs off instead of on 137 | 138 | /* 139 | * Fixed buffer size constants 140 | * 141 | * Use SGR_STR_MAX when allocating char buffers for sgr_str(). 142 | */ 143 | #define SGR_STR_MAX 256 // max bytes in a single SGR sequence string 144 | #define SGR_ELMS_MAX 32 // max number of format codes in a SGR sequence 145 | 146 | /* 147 | * SGR sequence construction strings 148 | * 149 | */ 150 | #define SGR_OPEN "\033[" 151 | #define SGR_CLOSE "m" 152 | #define SGR_SEP ";" 153 | 154 | /* 155 | * Write SGR attributes as an escape sequence to the char buffer pointed to 156 | * by dest. There must be SGR_STR_MAX bytes available after the dest pointer 157 | * or a buffer overflow could occur. 158 | * 159 | * Returns the number of bytes written. 160 | */ 161 | int sgr_str(char *dest, struct sgr); 162 | 163 | /* 164 | * Write SGR attributes as an escape sequence to a file descriptor. 165 | * Behaves similar to write(2). 166 | * 167 | * Returns number of bytes written if successful, -1 on error. 168 | * Error information is available via errno(2). 169 | */ 170 | int sgr_write(int fd, struct sgr); 171 | 172 | /* 173 | * Write SGR attributes as an escape sequence to a FILE stream. 174 | * Behaves similar to fwrite(2). 175 | * 176 | * Returns number of bytes written if successful, -1 on error. 177 | * Error information should be available via ferror(3) and feof(3). 178 | */ 179 | int sgr_fwrite(FILE *stream, struct sgr); 180 | 181 | /* Generic SGR value encoder. Takes a pointer to anything and a function that 182 | * will be called with the pointer any time chars should be emitted. This is 183 | * used to implement the sgr_str, sgr_write, and sgr_fwrite functions and may 184 | * be useful if you're writing to a unique output medium. 185 | * 186 | * Returns the number of bytes sent to func. 187 | */ 188 | int sgr_encode(void *p, void (*func)(void *, char *, int), struct sgr); 189 | 190 | /* 191 | * Unpack a SGR value into an array of int formatting codes. 192 | * The codes buffer should be allocated to hold up to SGR_ELMS_MAX elements. 193 | * 194 | * Returns the number of formatting code ints written to the codes buffer. 195 | */ 196 | int sgr_unpack(uint16_t codes[], struct sgr); 197 | -------------------------------------------------------------------------------- /termbox/README: -------------------------------------------------------------------------------- 1 | Termbox compatibility library 2 | 3 | termlib started life as a fork of termbox¹ but grew pretty quickly into a 4 | different type of project. 5 | 6 | These are the original forked termbox sources modified to work with termlib. The 7 | idea is for them to eventually be a compatibility shim that could be used to 8 | quickly port existing termbox programs to termlib. 9 | 10 | The termbox interface consists of only 12 functions: 11 | 12 | tb_init() // initialization 13 | tb_shutdown() // shutdown 14 | 15 | tb_width() // width of the terminal screen 16 | tb_height() // height of the terminal screen 17 | 18 | tb_clear() // clear buffer 19 | tb_present() // sync internal buffer with terminal 20 | 21 | tb_put_cell() 22 | tb_change_cell() 23 | 24 | tb_select_input_mode() // change input mode 25 | tb_peek_event() // peek a keyboard event 26 | tb_poll_event() // wait for a keyboard event 27 | 28 | See the the termbox.h file for more details. 29 | 30 | ¹ 31 | -------------------------------------------------------------------------------- /termbox/bytebuffer.inl: -------------------------------------------------------------------------------- 1 | /* bytebuffer.inl */ 2 | 3 | struct bytebuffer { 4 | char *buf; 5 | int len; 6 | int cap; 7 | }; 8 | 9 | static void bytebuffer_reserve(struct bytebuffer *b, int cap) { 10 | if (b->cap >= cap) { 11 | return; 12 | } 13 | 14 | // prefer doubling capacity 15 | if (b->cap * 2 >= cap) { 16 | cap = b->cap * 2; 17 | } 18 | 19 | char *newbuf = realloc(b->buf, cap); 20 | b->buf = newbuf; 21 | b->cap = cap; 22 | } 23 | 24 | static void bytebuffer_init(struct bytebuffer *b, int cap) { 25 | b->cap = 0; 26 | b->len = 0; 27 | b->buf = 0; 28 | 29 | if (cap > 0) { 30 | b->cap = cap; 31 | b->buf = malloc(cap); // just assume malloc works always 32 | } 33 | } 34 | 35 | static void bytebuffer_free(struct bytebuffer *b) { 36 | if (b->buf) 37 | free(b->buf); 38 | } 39 | 40 | static void bytebuffer_clear(struct bytebuffer *b) { 41 | b->len = 0; 42 | } 43 | 44 | static void bytebuffer_append(struct bytebuffer *b, const char *data, int len) { 45 | bytebuffer_reserve(b, b->len + len); 46 | memcpy(b->buf + b->len, data, len); 47 | b->len += len; 48 | } 49 | 50 | static void bytebuffer_puts(struct bytebuffer *b, const char *str) { 51 | bytebuffer_append(b, str, strlen(str)); 52 | } 53 | 54 | static void bytebuffer_resize(struct bytebuffer *b, int len) { 55 | bytebuffer_reserve(b, len); 56 | b->len = len; 57 | } 58 | 59 | static void bytebuffer_flush(struct bytebuffer *b, int fd) { 60 | if (write(fd, b->buf, b->len) < b->len) { 61 | // short write or error. buffer likely not flushed properly. 62 | // TODO: return error code; probably dont clear. 63 | } 64 | bytebuffer_clear(b); 65 | } 66 | 67 | static void bytebuffer_truncate(struct bytebuffer *b, int n) { 68 | if (n <= 0) 69 | return; 70 | if (n > b->len) 71 | n = b->len; 72 | const int nmove = b->len - n; 73 | memmove(b->buf, b->buf+n, nmove); 74 | b->len -= n; 75 | } 76 | 77 | // vim: noexpandtab 78 | -------------------------------------------------------------------------------- /termbox/input.inl: -------------------------------------------------------------------------------- 1 | /* input.inl */ 2 | 3 | // if s1 starts with s2 returns true, else false 4 | // len is the length of s1 5 | // s2 should be null-terminated 6 | static bool starts_with(const char *s1, int len, const char *s2) 7 | { 8 | int n = 0; 9 | while (*s2 && n < len) { 10 | if (*s1++ != *s2++) 11 | return false; 12 | n++; 13 | } 14 | return *s2 == 0; 15 | } 16 | 17 | static int parse_mouse_event(struct tb_event *event, const char *buf, int len) { 18 | if (len >= 6 && starts_with(buf, len, "\033[M")) { 19 | // X10 mouse encoding, the simplest one 20 | // \033 [ M Cb Cx Cy 21 | int b = buf[3] - 32; 22 | switch (b & 3) { 23 | case 0: 24 | if ((b & 64) != 0) 25 | event->key = TB_KEY_MOUSE_WHEEL_UP; 26 | else 27 | event->key = TB_KEY_MOUSE_LEFT; 28 | break; 29 | case 1: 30 | if ((b & 64) != 0) 31 | event->key = TB_KEY_MOUSE_WHEEL_DOWN; 32 | else 33 | event->key = TB_KEY_MOUSE_MIDDLE; 34 | break; 35 | case 2: 36 | event->key = TB_KEY_MOUSE_RIGHT; 37 | break; 38 | case 3: 39 | event->key = TB_KEY_MOUSE_RELEASE; 40 | break; 41 | default: 42 | return -6; 43 | } 44 | event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default 45 | if ((b & 32) != 0) 46 | event->mod |= TB_MOD_MOTION; 47 | 48 | // the coord is 1,1 for upper left 49 | event->x = (uint8_t)buf[4] - 1 - 32; 50 | event->y = (uint8_t)buf[5] - 1 - 32; 51 | 52 | return 6; 53 | } else if (starts_with(buf, len, "\033[<") || starts_with(buf, len, "\033[")) { 54 | // xterm 1006 extended mode or urxvt 1015 extended mode 55 | // xterm: \033 [ < Cb ; Cx ; Cy (M or m) 56 | // urxvt: \033 [ Cb ; Cx ; Cy M 57 | int i, mi = -1, starti = -1; 58 | int isM, isU, s1 = -1, s2 = -1; 59 | int n1 = 0, n2 = 0, n3 = 0; 60 | 61 | for (i = 0; i < len; i++) { 62 | // We search the first (s1) and the last (s2) ';' 63 | if (buf[i] == ';') { 64 | if (s1 == -1) 65 | s1 = i; 66 | s2 = i; 67 | } 68 | 69 | // We search for the first 'm' or 'M' 70 | if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) { 71 | mi = i; 72 | break; 73 | } 74 | } 75 | if (mi == -1) 76 | return 0; 77 | 78 | // whether it's a capital M or not 79 | isM = (buf[mi] == 'M'); 80 | 81 | if (buf[2] == '<') { 82 | isU = 0; 83 | starti = 3; 84 | } else { 85 | isU = 1; 86 | starti = 2; 87 | } 88 | 89 | if (s1 == -1 || s2 == -1 || s1 == s2) 90 | return 0; 91 | 92 | n1 = strtoul(&buf[starti], NULL, 10); 93 | n2 = strtoul(&buf[s1 + 1], NULL, 10); 94 | n3 = strtoul(&buf[s2 + 1], NULL, 10); 95 | 96 | if (isU) 97 | n1 -= 32; 98 | 99 | switch (n1 & 3) { 100 | case 0: 101 | if ((n1&64) != 0) { 102 | event->key = TB_KEY_MOUSE_WHEEL_UP; 103 | } else { 104 | event->key = TB_KEY_MOUSE_LEFT; 105 | } 106 | break; 107 | case 1: 108 | if ((n1&64) != 0) { 109 | event->key = TB_KEY_MOUSE_WHEEL_DOWN; 110 | } else { 111 | event->key = TB_KEY_MOUSE_MIDDLE; 112 | } 113 | break; 114 | case 2: 115 | event->key = TB_KEY_MOUSE_RIGHT; 116 | break; 117 | case 3: 118 | event->key = TB_KEY_MOUSE_RELEASE; 119 | break; 120 | default: 121 | return mi + 1; 122 | } 123 | 124 | if (!isM) { 125 | // on xterm mouse release is signaled by lowercase m 126 | event->key = TB_KEY_MOUSE_RELEASE; 127 | } 128 | 129 | event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default 130 | if ((n1&32) != 0) 131 | event->mod |= TB_MOD_MOTION; 132 | 133 | event->x = (uint8_t)n2 - 1; 134 | event->y = (uint8_t)n3 - 1; 135 | 136 | return mi + 1; 137 | } 138 | 139 | return 0; 140 | } 141 | 142 | // convert escape sequence to event, and return consumed bytes on success (failure == 0) 143 | static int parse_escape_seq(struct tb_event *event, const char *buf, int len) 144 | { 145 | int mouse_parsed = parse_mouse_event(event, buf, len); 146 | 147 | if (mouse_parsed != 0) 148 | return mouse_parsed; 149 | 150 | // it's pretty simple here, find 'starts_with' match and return 151 | // success, else return failure 152 | int i; 153 | for (i = 0; keys[i]; i++) { 154 | if (starts_with(buf, len, keys[i])) { 155 | event->ch = 0; 156 | event->key = 0xFFFF-i; 157 | return strlen(keys[i]); 158 | } 159 | } 160 | return 0; 161 | } 162 | 163 | static bool extract_event(struct tb_event *event, struct bytebuffer *inbuf, int inputmode) 164 | { 165 | const char *buf = inbuf->buf; 166 | const int len = inbuf->len; 167 | if (len == 0) 168 | return false; 169 | 170 | if (buf[0] == '\033') { 171 | int n = parse_escape_seq(event, buf, len); 172 | if (n != 0) { 173 | bool success = true; 174 | if (n < 0) { 175 | success = false; 176 | n = -n; 177 | } 178 | bytebuffer_truncate(inbuf, n); 179 | return success; 180 | } else { 181 | // it's not escape sequence, then it's ALT or ESC, 182 | // check inputmode 183 | if (inputmode&TB_INPUT_ESC) { 184 | // if we're in escape mode, fill ESC event, pop 185 | // buffer, return success 186 | event->ch = 0; 187 | event->key = TB_KEY_ESC; 188 | event->mod = 0; 189 | bytebuffer_truncate(inbuf, 1); 190 | return true; 191 | } else if (inputmode&TB_INPUT_ALT) { 192 | // if we're in alt mode, set ALT modifier to 193 | // event and redo parsing 194 | event->mod = TB_MOD_ALT; 195 | bytebuffer_truncate(inbuf, 1); 196 | return extract_event(event, inbuf, inputmode); 197 | } 198 | assert(!"never got here"); 199 | } 200 | } 201 | 202 | // if we're here, this is not an escape sequence and not an alt sequence 203 | // so, it's a FUNCTIONAL KEY or a UNICODE character 204 | 205 | // first of all check if it's a functional key 206 | if ((unsigned char)buf[0] <= TB_KEY_SPACE || 207 | (unsigned char)buf[0] == TB_KEY_BACKSPACE2) 208 | { 209 | // fill event, pop buffer, return success */ 210 | event->ch = 0; 211 | event->key = (uint16_t)buf[0]; 212 | bytebuffer_truncate(inbuf, 1); 213 | return true; 214 | } 215 | 216 | // feh... we got utf8 here 217 | 218 | // check if there is all bytes 219 | if (len >= tb_utf8_char_length(buf[0])) { 220 | /* everything ok, fill event, pop buffer, return success */ 221 | tb_utf8_char_to_unicode(&event->ch, buf); 222 | event->key = 0; 223 | bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0])); 224 | return true; 225 | } 226 | 227 | // event isn't recognized, perhaps there is not enough bytes in utf8 228 | // sequence 229 | return false; 230 | } 231 | -------------------------------------------------------------------------------- /termbox/term.inl: -------------------------------------------------------------------------------- 1 | /* term.inl */ 2 | #include "ti.h" 3 | 4 | enum { 5 | T_ENTER_CA, 6 | T_EXIT_CA, 7 | T_SHOW_CURSOR, 8 | T_HIDE_CURSOR, 9 | T_CLEAR_SCREEN, 10 | T_SGR0, 11 | T_ENTER_KEYPAD, 12 | T_EXIT_KEYPAD, 13 | 14 | T_ENTER_MOUSE, 15 | T_EXIT_MOUSE, 16 | 17 | T_FUNCS_NUM, 18 | }; 19 | 20 | #define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" 21 | #define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" 22 | 23 | #define EUNSUPPORTED_TERM -1 24 | 25 | static ti_terminfo *ti; 26 | static const char **keys; 27 | static const char **funcs; 28 | 29 | #define TB_KEYS_NUM 22 30 | 31 | static const int16_t ti_funcs[] = { 32 | ti_smcup, // T_ENTER_CA 33 | ti_rmcup, // T_EXIT_CA 34 | ti_cnorm, // T_SHOW_CURSOR 35 | ti_civis, // T_HIDE_CURSOR 36 | ti_clear, // T_CLEAR_SCREEN 37 | ti_sgr0, // T_SGR0 38 | ti_smkx, // T_ENTER_KEYPAD 39 | ti_rmkx, // T_EXIT_KEYPAD 40 | }; 41 | 42 | 43 | static const int16_t ti_keys[] = { 44 | ti_kf1, // TB_KEY_F1 45 | ti_kf2, // TB_KEY_F2 46 | ti_kf3, // TB_KEY_F3 47 | ti_kf4, // TB_KEY_F4 48 | ti_kf5, // TB_KEY_F5 49 | ti_kf6, // TB_KEY_F6 50 | ti_kf7, // TB_KEY_F7 51 | ti_kf8, // TB_KEY_F8 52 | ti_kf9, // TB_KEY_F9 53 | ti_kf10, // TB_KEY_F10 54 | ti_kf11, // TB_KEY_F11 55 | ti_kf12, // TB_KEY_F12 56 | ti_kich1, // TB_KEY_INSERT 57 | ti_kdch1, // TB_KEY_DELETE 58 | ti_khome, // TB_KEY_HOME 59 | ti_kend, // TB_KEY_END 60 | ti_kpp, // TB_KEY_PGUP 61 | ti_knp, // TB_KEY_PGDN 62 | ti_kcuu1, // TB_KEY_ARROW_UP 63 | ti_kcud1, // TB_KEY_ARROW_DOWN 64 | ti_kcub1, // TB_KEY_ARROW_LEFT 65 | ti_kcuf1, // TB_KEY_ARROW_RIGHT 66 | }; 67 | 68 | // Loads terminal escape sequences from terminfo. 69 | static int init_term(void) { 70 | int err; 71 | ti = ti_load(NULL, &err); 72 | if (!ti) return EUNSUPPORTED_TERM; 73 | 74 | keys = malloc(sizeof(char*) * (TB_KEYS_NUM+1)); 75 | for (int i = 0; i < TB_KEYS_NUM; i++) { 76 | keys[i] = ti_getstri(ti, ti_keys[i]); 77 | } 78 | keys[TB_KEYS_NUM] = 0; 79 | 80 | funcs = malloc(sizeof(char*) * T_FUNCS_NUM); 81 | // the last two entries are reserved for mouse extensions. 82 | // because the table offset is not there, the entries have to fill in manually 83 | for (int i = 0; i < T_FUNCS_NUM-2; i++) { 84 | funcs[i] = ti_getstri(ti, ti_funcs[i]); 85 | } 86 | 87 | // TODO: load from extended format terminfo capabilities 88 | funcs[T_FUNCS_NUM-2] = ENTER_MOUSE_SEQ; 89 | funcs[T_FUNCS_NUM-1] = EXIT_MOUSE_SEQ; 90 | 91 | return 0; 92 | } 93 | 94 | static void shutdown_term(void) { 95 | free(keys); keys = NULL; 96 | free(funcs); funcs = NULL; 97 | ti_free(ti); ti = NULL; 98 | } 99 | 100 | // vim: noexpandtab 101 | -------------------------------------------------------------------------------- /termbox/termbox.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 700 // wcwidth, sigaction 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "termbox.h" 20 | 21 | #include "bytebuffer.inl" 22 | #include "term.inl" 23 | #include "input.inl" 24 | 25 | struct cellbuf { 26 | int width; 27 | int height; 28 | struct tb_cell *cells; 29 | }; 30 | 31 | #define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)] 32 | #define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1) 33 | #define LAST_COORD_INIT -1 34 | 35 | static struct termios orig_tios; 36 | 37 | static struct cellbuf back_buffer; 38 | static struct cellbuf front_buffer; 39 | static struct bytebuffer output_buffer; 40 | static struct bytebuffer input_buffer; 41 | 42 | static int termw = -1; 43 | static int termh = -1; 44 | 45 | static int inputmode = TB_INPUT_ESC; 46 | static int outputmode = TB_OUTPUT_NORMAL; 47 | 48 | static int inout; 49 | static int winch_fds[2]; 50 | 51 | static int lastx = LAST_COORD_INIT; 52 | static int lasty = LAST_COORD_INIT; 53 | static int cursor_x = -1; 54 | static int cursor_y = -1; 55 | 56 | static struct sgr default_sgr = { 0 }; 57 | 58 | static void write_cursor(int x, int y); 59 | 60 | static void cellbuf_init(struct cellbuf *buf, int width, int height); 61 | static void cellbuf_resize(struct cellbuf *buf, int width, int height); 62 | static void cellbuf_clear(struct cellbuf *buf); 63 | static void cellbuf_free(struct cellbuf *buf); 64 | 65 | static void update_size(void); 66 | static void update_term_size(void); 67 | static void send_attr(struct sgr sgr); 68 | static void send_char(int x, int y, uint32_t c); 69 | static void send_clear(void); 70 | static void sigwinch_handler(int xxx); 71 | static int wait_fill_event(struct tb_event *event, struct timeval *timeout); 72 | 73 | /* may happen in a different thread */ 74 | static volatile int buffer_size_change_request; 75 | 76 | /* -------------------------------------------------------- */ 77 | 78 | int tb_init_fd(int inout_) 79 | { 80 | inout = inout_; 81 | if (inout == -1) { 82 | return TB_EFAILED_TO_OPEN_TTY; 83 | } 84 | 85 | if (init_term() < 0) { 86 | close(inout); 87 | return TB_EUNSUPPORTED_TERMINAL; 88 | } 89 | 90 | if (pipe(winch_fds) < 0) { 91 | close(inout); 92 | return TB_EPIPE_TRAP_ERROR; 93 | } 94 | 95 | struct sigaction sa; 96 | memset(&sa, 0, sizeof(sa)); 97 | sa.sa_handler = sigwinch_handler; 98 | sa.sa_flags = 0; 99 | sigaction(SIGWINCH, &sa, 0); 100 | 101 | tcgetattr(inout, &orig_tios); 102 | 103 | struct termios tios; 104 | memcpy(&tios, &orig_tios, sizeof(tios)); 105 | 106 | tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP 107 | | INLCR | IGNCR | ICRNL | IXON); 108 | tios.c_oflag &= ~OPOST; 109 | tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 110 | tios.c_cflag &= ~(CSIZE | PARENB); 111 | tios.c_cflag |= CS8; 112 | tios.c_cc[VMIN] = 0; 113 | tios.c_cc[VTIME] = 0; 114 | tcsetattr(inout, TCSAFLUSH, &tios); 115 | 116 | bytebuffer_init(&input_buffer, 128); 117 | bytebuffer_init(&output_buffer, 32 * 1024); 118 | 119 | bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]); 120 | bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]); 121 | bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]); 122 | send_clear(); 123 | 124 | update_term_size(); 125 | cellbuf_init(&back_buffer, termw, termh); 126 | cellbuf_init(&front_buffer, termw, termh); 127 | cellbuf_clear(&back_buffer); 128 | cellbuf_clear(&front_buffer); 129 | 130 | return 0; 131 | } 132 | 133 | int tb_init_file(const char* name){ 134 | return tb_init_fd(open(name, O_RDWR)); 135 | } 136 | 137 | int tb_init(void) 138 | { 139 | return tb_init_file("/dev/tty"); 140 | } 141 | 142 | void tb_shutdown(void) 143 | { 144 | if (termw == -1) { 145 | fputs("tb_shutdown() should not be called twice.", stderr); 146 | abort(); 147 | } 148 | 149 | bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]); 150 | bytebuffer_puts(&output_buffer, funcs[T_SGR0]); 151 | bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]); 152 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]); 153 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]); 154 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]); 155 | bytebuffer_flush(&output_buffer, inout); 156 | tcsetattr(inout, TCSAFLUSH, &orig_tios); 157 | 158 | shutdown_term(); 159 | close(inout); 160 | close(winch_fds[0]); 161 | close(winch_fds[1]); 162 | 163 | cellbuf_free(&back_buffer); 164 | cellbuf_free(&front_buffer); 165 | bytebuffer_free(&output_buffer); 166 | bytebuffer_free(&input_buffer); 167 | termw = termh = -1; 168 | } 169 | 170 | void tb_present(void) 171 | { 172 | int x,y,w,i; 173 | struct tb_cell *back, *front; 174 | 175 | /* invalidate cursor position */ 176 | lastx = LAST_COORD_INIT; 177 | lasty = LAST_COORD_INIT; 178 | 179 | if (buffer_size_change_request) { 180 | update_size(); 181 | buffer_size_change_request = 0; 182 | } 183 | 184 | for (y = 0; y < front_buffer.height; ++y) { 185 | for (x = 0; x < front_buffer.width; ) { 186 | back = &CELL(&back_buffer, x, y); 187 | front = &CELL(&front_buffer, x, y); 188 | w = wcwidth(back->ch); 189 | if (w < 1) w = 1; 190 | if (memcmp(back, front, sizeof(struct tb_cell)) == 0) { 191 | x += w; 192 | continue; 193 | } 194 | memcpy(front, back, sizeof(struct tb_cell)); 195 | send_attr(back->sgr); 196 | if (w > 1 && x >= front_buffer.width - (w - 1)) { 197 | // Not enough room for wide ch, so send spaces 198 | for (i = x; i < front_buffer.width; ++i) { 199 | send_char(i, y, ' '); 200 | } 201 | } else { 202 | send_char(x, y, back->ch); 203 | for (i = 1; i < w; ++i) { 204 | front = &CELL(&front_buffer, x + i, y); 205 | front->ch = 0; 206 | front->sgr = back->sgr; 207 | } 208 | } 209 | x += w; 210 | } 211 | } 212 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) 213 | write_cursor(cursor_x, cursor_y); 214 | bytebuffer_flush(&output_buffer, inout); 215 | } 216 | 217 | void tb_set_cursor(int cx, int cy) 218 | { 219 | if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy)) 220 | bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]); 221 | 222 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy)) 223 | bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]); 224 | 225 | cursor_x = cx; 226 | cursor_y = cy; 227 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) 228 | write_cursor(cursor_x, cursor_y); 229 | } 230 | 231 | void tb_put_cell(int x, int y, const struct tb_cell *cell) 232 | { 233 | if ((unsigned)x >= (unsigned)back_buffer.width) 234 | return; 235 | if ((unsigned)y >= (unsigned)back_buffer.height) 236 | return; 237 | CELL(&back_buffer, x, y) = *cell; 238 | } 239 | 240 | static void sgr_set_fg(struct sgr *sgr, uint16_t fg) { 241 | uint16_t fgcol = fg&0xFF; 242 | if (fgcol != TB_DEFAULT) { 243 | sgr->fg = fgcol; 244 | 245 | // XXX Logic kept verbatim from original send_attr() 246 | // implementation for compatibility. Some odd decisions 247 | // i dont totally understand here. -aux01 248 | if (outputmode == TB_OUTPUT_NORMAL) { 249 | sgr->fg -= 1; 250 | sgr->at |= SGR_FG; 251 | } else if (outputmode == TB_OUTPUT_256) { 252 | sgr->at |= SGR_FG256; 253 | } else if (outputmode == TB_OUTPUT_216) { 254 | if (fgcol > 215) sgr->fg = 7; 255 | sgr->at |= SGR_FG216; 256 | } else if (outputmode == TB_OUTPUT_GRAYSCALE) { 257 | if (fgcol > 23) sgr->fg = 23; 258 | sgr->at |= SGR_FG24; 259 | } 260 | } 261 | } 262 | 263 | static void sgr_set_bg(struct sgr *sgr, uint16_t bg) { 264 | uint16_t bgcol = bg&0xFF; 265 | if (bgcol != TB_DEFAULT) { 266 | sgr->bg = bgcol; 267 | 268 | // XXX Logic kept verbatim from original send_attr() 269 | // implementation for compatibility. Some odd decisions 270 | // i dont totally understand here. -aux01 271 | if (outputmode == TB_OUTPUT_NORMAL) { 272 | sgr->bg -= 1; 273 | sgr->at |= SGR_BG; 274 | } else if (outputmode == TB_OUTPUT_256) { 275 | sgr->at |= SGR_BG256; 276 | } else if (outputmode == TB_OUTPUT_216) { 277 | if (bgcol > 215) sgr->bg = 0; 278 | sgr->at |= SGR_BG216; 279 | } else if (outputmode == TB_OUTPUT_GRAYSCALE) { 280 | if (bgcol > 23) sgr->bg = 0; 281 | sgr->at |= SGR_BG24; 282 | } 283 | } 284 | } 285 | 286 | void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg) 287 | { 288 | struct sgr sgr = {0}; 289 | 290 | if (fg&TB_BOLD) sgr.at |= SGR_BOLD; 291 | if (fg&TB_FAINT) sgr.at |= SGR_FAINT; 292 | if (fg&TB_ITALIC) sgr.at |= SGR_ITALIC; 293 | if (fg&TB_UNDERLINE) sgr.at |= SGR_UNDERLINE; 294 | if (fg&TB_CROSSOUT) sgr.at |= SGR_STRIKE; 295 | 296 | if (fg&TB_BLINK || bg&TB_BLINK || bg&TB_BOLD) 297 | sgr.at |= SGR_BLINK; 298 | if (fg&TB_REVERSE || bg&TB_REVERSE) 299 | sgr.at |= SGR_REVERSE; 300 | 301 | sgr_set_fg(&sgr, fg); 302 | sgr_set_bg(&sgr, bg); 303 | 304 | struct tb_cell c = {ch, sgr}; 305 | tb_put_cell(x, y, &c); 306 | } 307 | 308 | void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells) 309 | { 310 | if (x + w < 0 || x >= back_buffer.width) 311 | return; 312 | if (y + h < 0 || y >= back_buffer.height) 313 | return; 314 | int xo = 0, yo = 0, ww = w, hh = h; 315 | if (x < 0) { 316 | xo = -x; 317 | ww -= xo; 318 | x = 0; 319 | } 320 | if (y < 0) { 321 | yo = -y; 322 | hh -= yo; 323 | y = 0; 324 | } 325 | if (ww > back_buffer.width - x) 326 | ww = back_buffer.width - x; 327 | if (hh > back_buffer.height - y) 328 | hh = back_buffer.height - y; 329 | 330 | int sy; 331 | struct tb_cell *dst = &CELL(&back_buffer, x, y); 332 | const struct tb_cell *src = cells + yo * w + xo; 333 | size_t size = sizeof(struct tb_cell) * ww; 334 | 335 | for (sy = 0; sy < hh; ++sy) { 336 | memcpy(dst, src, size); 337 | dst += back_buffer.width; 338 | src += w; 339 | } 340 | } 341 | 342 | struct tb_cell *tb_cell_buffer(void) 343 | { 344 | return back_buffer.cells; 345 | } 346 | 347 | int tb_poll_event(struct tb_event *event) 348 | { 349 | return wait_fill_event(event, 0); 350 | } 351 | 352 | int tb_peek_event(struct tb_event *event, int timeout) 353 | { 354 | struct timeval tv; 355 | tv.tv_sec = timeout / 1000; 356 | tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; 357 | return wait_fill_event(event, &tv); 358 | } 359 | 360 | int tb_width(void) 361 | { 362 | return termw; 363 | } 364 | 365 | int tb_height(void) 366 | { 367 | return termh; 368 | } 369 | 370 | void tb_clear(void) 371 | { 372 | if (buffer_size_change_request) { 373 | update_size(); 374 | buffer_size_change_request = 0; 375 | } 376 | cellbuf_clear(&back_buffer); 377 | } 378 | 379 | int tb_select_input_mode(int mode) 380 | { 381 | if (mode) { 382 | if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) 383 | mode |= TB_INPUT_ESC; 384 | 385 | /* technically termbox can handle that, but let's be nice and show here 386 | what mode is actually used */ 387 | if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) 388 | mode &= ~TB_INPUT_ALT; 389 | 390 | inputmode = mode; 391 | if (mode&TB_INPUT_MOUSE) { 392 | bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]); 393 | bytebuffer_flush(&output_buffer, inout); 394 | } else { 395 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]); 396 | bytebuffer_flush(&output_buffer, inout); 397 | } 398 | } 399 | return inputmode; 400 | } 401 | 402 | int tb_select_output_mode(int mode) 403 | { 404 | if (mode) 405 | outputmode = mode; 406 | return outputmode; 407 | } 408 | 409 | void tb_set_clear_attributes(uint16_t fg, uint16_t bg) { 410 | default_sgr = (struct sgr){0}; 411 | sgr_set_fg(&default_sgr, fg); 412 | sgr_set_bg(&default_sgr, bg); 413 | } 414 | 415 | /* -------------------------------------------------------- */ 416 | 417 | static int convertnum(uint32_t num, char* buf) { 418 | int i, l = 0; 419 | int ch; 420 | do { 421 | buf[l++] = '0' + (num % 10); 422 | num /= 10; 423 | } while (num); 424 | for(i = 0; i < l / 2; i++) { 425 | ch = buf[i]; 426 | buf[i] = buf[l - 1 - i]; 427 | buf[l - 1 - i] = ch; 428 | } 429 | return l; 430 | } 431 | 432 | #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1) 433 | #define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf)) 434 | 435 | static void write_cursor(int x, int y) { 436 | char buf[32]; 437 | WRITE_LITERAL("\033["); 438 | WRITE_INT(y+1); 439 | WRITE_LITERAL(";"); 440 | WRITE_INT(x+1); 441 | WRITE_LITERAL("H"); 442 | } 443 | 444 | static void cellbuf_init(struct cellbuf *buf, int width, int height) 445 | { 446 | buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height); 447 | assert(buf->cells); 448 | buf->width = width; 449 | buf->height = height; 450 | } 451 | 452 | static void cellbuf_resize(struct cellbuf *buf, int width, int height) 453 | { 454 | if (buf->width == width && buf->height == height) 455 | return; 456 | 457 | int oldw = buf->width; 458 | int oldh = buf->height; 459 | struct tb_cell *oldcells = buf->cells; 460 | 461 | cellbuf_init(buf, width, height); 462 | cellbuf_clear(buf); 463 | 464 | int minw = (width < oldw) ? width : oldw; 465 | int minh = (height < oldh) ? height : oldh; 466 | int i; 467 | 468 | for (i = 0; i < minh; ++i) { 469 | struct tb_cell *csrc = oldcells + (i * oldw); 470 | struct tb_cell *cdst = buf->cells + (i * width); 471 | memcpy(cdst, csrc, sizeof(struct tb_cell) * minw); 472 | } 473 | 474 | free(oldcells); 475 | } 476 | 477 | static void cellbuf_clear(struct cellbuf *buf) 478 | { 479 | int i; 480 | int ncells = buf->width * buf->height; 481 | 482 | for (i = 0; i < ncells; ++i) { 483 | buf->cells[i].ch = ' '; 484 | buf->cells[i].sgr = default_sgr; 485 | } 486 | } 487 | 488 | static void cellbuf_free(struct cellbuf *buf) 489 | { 490 | free(buf->cells); 491 | } 492 | 493 | static void get_term_size(int *w, int *h) 494 | { 495 | struct winsize sz; 496 | memset(&sz, 0, sizeof(sz)); 497 | 498 | ioctl(inout, TIOCGWINSZ, &sz); 499 | 500 | if (w) *w = sz.ws_col; 501 | if (h) *h = sz.ws_row; 502 | } 503 | 504 | static void update_term_size(void) 505 | { 506 | struct winsize sz; 507 | memset(&sz, 0, sizeof(sz)); 508 | 509 | ioctl(inout, TIOCGWINSZ, &sz); 510 | 511 | termw = sz.ws_col; 512 | termh = sz.ws_row; 513 | } 514 | 515 | static void send_attr(struct sgr sgr) 516 | { 517 | static struct sgr last = {0,0,0}; 518 | if (memcmp(&sgr, &last, sizeof(struct sgr)) == 0) { 519 | return; 520 | } 521 | 522 | bytebuffer_append(&output_buffer, funcs[T_SGR0], strlen(funcs[T_SGR0])); 523 | 524 | bytebuffer_reserve(&output_buffer, output_buffer.len + SGR_STR_MAX); 525 | int sz = sgr_str(output_buffer.buf + output_buffer.len, sgr); 526 | output_buffer.len += sz; 527 | 528 | last = sgr; 529 | } 530 | 531 | static void send_char(int x, int y, uint32_t c) 532 | { 533 | char buf[7]; 534 | int bw = tb_utf8_unicode_to_char(buf, c); 535 | if (x-1 != lastx || y != lasty) 536 | write_cursor(x, y); 537 | lastx = x; lasty = y; 538 | if(!c) buf[0] = ' '; // replace 0 with whitespace 539 | bytebuffer_append(&output_buffer, buf, bw); 540 | } 541 | 542 | static void send_clear(void) 543 | { 544 | send_attr(default_sgr); 545 | bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]); 546 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) 547 | write_cursor(cursor_x, cursor_y); 548 | bytebuffer_flush(&output_buffer, inout); 549 | 550 | /* we need to invalidate cursor position too and these two vars are 551 | * used only for simple cursor positioning optimization, cursor 552 | * actually may be in the correct place, but we simply discard 553 | * optimization once and it gives us simple solution for the case when 554 | * cursor moved */ 555 | lastx = LAST_COORD_INIT; 556 | lasty = LAST_COORD_INIT; 557 | } 558 | 559 | static void sigwinch_handler(int xxx) 560 | { 561 | (void) xxx; 562 | const int zzz = 1; 563 | if (write(winch_fds[1], &zzz, sizeof(int)) < (ssize_t)sizeof(int)) { 564 | // short write or error. resize event may not fire. 565 | // TODO: log this 566 | } 567 | } 568 | 569 | static void update_size(void) 570 | { 571 | update_term_size(); 572 | cellbuf_resize(&back_buffer, termw, termh); 573 | cellbuf_resize(&front_buffer, termw, termh); 574 | cellbuf_clear(&front_buffer); 575 | send_clear(); 576 | } 577 | 578 | static int read_up_to(int n) { 579 | assert(n > 0); 580 | const int prevlen = input_buffer.len; 581 | bytebuffer_resize(&input_buffer, prevlen + n); 582 | 583 | int read_n = 0; 584 | while (read_n <= n) { 585 | ssize_t r = 0; 586 | if (read_n < n) { 587 | r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n); 588 | } 589 | #ifdef __CYGWIN__ 590 | // While linux man for tty says when VMIN == 0 && VTIME == 0, read 591 | // should return 0 when there is nothing to read, cygwin's read returns 592 | // -1. Not sure why and if it's correct to ignore it, but let's pretend 593 | // it's zero. 594 | if (r < 0) r = 0; 595 | #endif 596 | if (r < 0) { 597 | // EAGAIN / EWOULDBLOCK shouldn't occur here 598 | assert(errno != EAGAIN && errno != EWOULDBLOCK); 599 | return -1; 600 | } else if (r > 0) { 601 | read_n += r; 602 | } else { 603 | bytebuffer_resize(&input_buffer, prevlen + read_n); 604 | return read_n; 605 | } 606 | } 607 | assert(!"unreachable"); 608 | return 0; 609 | } 610 | 611 | static int wait_fill_event(struct tb_event *event, struct timeval *timeout) 612 | { 613 | // ;-) 614 | #define ENOUGH_DATA_FOR_PARSING 64 615 | fd_set events; 616 | memset(event, 0, sizeof(struct tb_event)); 617 | 618 | // try to extract event from input buffer, return on success 619 | event->type = TB_EVENT_KEY; 620 | if (extract_event(event, &input_buffer, inputmode)) 621 | return event->type; 622 | 623 | // it looks like input buffer is incomplete, let's try the short path, 624 | // but first make sure there is enough space 625 | int n = read_up_to(ENOUGH_DATA_FOR_PARSING); 626 | if (n < 0) 627 | return -1; 628 | if (n > 0 && extract_event(event, &input_buffer, inputmode)) 629 | return event->type; 630 | 631 | // n == 0, or not enough data, let's go to select 632 | while (1) { 633 | FD_ZERO(&events); 634 | FD_SET(inout, &events); 635 | FD_SET(winch_fds[0], &events); 636 | int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout; 637 | int result = select(maxfd+1, &events, 0, 0, timeout); 638 | if (!result) 639 | return 0; 640 | 641 | if (FD_ISSET(inout, &events)) { 642 | event->type = TB_EVENT_KEY; 643 | n = read_up_to(ENOUGH_DATA_FOR_PARSING); 644 | if (n < 0) 645 | return -1; 646 | 647 | if (n == 0) 648 | continue; 649 | 650 | if (extract_event(event, &input_buffer, inputmode)) 651 | return event->type; 652 | } 653 | if (FD_ISSET(winch_fds[0], &events)) { 654 | event->type = TB_EVENT_RESIZE; 655 | int zzz = 0; 656 | if (read(winch_fds[0], &zzz, sizeof(int)) < (ssize_t)sizeof(int)) { 657 | // ignore short read / error 658 | // could be due to signal. 659 | } 660 | buffer_size_change_request = 1; 661 | get_term_size(&event->w, &event->h); 662 | return TB_EVENT_RESIZE; 663 | } 664 | } 665 | } 666 | 667 | /* 668 | * utf8 processing 669 | */ 670 | 671 | static const unsigned char utf8_length[256] = { 672 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 673 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 674 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 675 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 676 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 677 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 678 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 679 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 680 | }; 681 | 682 | static const unsigned char utf8_mask[6] = { 683 | 0x7F, 684 | 0x1F, 685 | 0x0F, 686 | 0x07, 687 | 0x03, 688 | 0x01, 689 | }; 690 | 691 | int tb_utf8_char_length(char c) 692 | { 693 | return utf8_length[(unsigned char)c]; 694 | } 695 | 696 | int tb_utf8_char_to_unicode(uint32_t *out, const char *c) 697 | { 698 | if (*c == 0) 699 | return -1; 700 | 701 | int i; 702 | unsigned char len = tb_utf8_char_length(*c); 703 | unsigned char mask = utf8_mask[len-1]; 704 | uint32_t result = c[0] & mask; 705 | for (i = 1; i < len; ++i) { 706 | result <<= 6; 707 | result |= c[i] & 0x3f; 708 | } 709 | 710 | *out = result; 711 | return (int)len; 712 | } 713 | 714 | int tb_utf8_unicode_to_char(char *out, uint32_t c) 715 | { 716 | int len = 0; 717 | int first; 718 | int i; 719 | 720 | if (c < 0x80) { 721 | first = 0; 722 | len = 1; 723 | } else if (c < 0x800) { 724 | first = 0xc0; 725 | len = 2; 726 | } else if (c < 0x10000) { 727 | first = 0xe0; 728 | len = 3; 729 | } else if (c < 0x200000) { 730 | first = 0xf0; 731 | len = 4; 732 | } else if (c < 0x4000000) { 733 | first = 0xf8; 734 | len = 5; 735 | } else { 736 | first = 0xfc; 737 | len = 6; 738 | } 739 | 740 | for (i = len - 1; i > 0; --i) { 741 | out[i] = (c & 0x3f) | 0x80; 742 | c >>= 6; 743 | } 744 | out[0] = c | first; 745 | 746 | return len; 747 | } 748 | 749 | // vim: noexpandtab 750 | -------------------------------------------------------------------------------- /termbox/termbox.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "sgr.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /* Key constants. See also struct tb_event's key field. 11 | * 12 | * These are a safe subset of terminfo keys, which exist on all popular 13 | * terminals. Termbox uses only them to stay truly portable. 14 | */ 15 | #define TB_KEY_F1 (0xFFFF-0) 16 | #define TB_KEY_F2 (0xFFFF-1) 17 | #define TB_KEY_F3 (0xFFFF-2) 18 | #define TB_KEY_F4 (0xFFFF-3) 19 | #define TB_KEY_F5 (0xFFFF-4) 20 | #define TB_KEY_F6 (0xFFFF-5) 21 | #define TB_KEY_F7 (0xFFFF-6) 22 | #define TB_KEY_F8 (0xFFFF-7) 23 | #define TB_KEY_F9 (0xFFFF-8) 24 | #define TB_KEY_F10 (0xFFFF-9) 25 | #define TB_KEY_F11 (0xFFFF-10) 26 | #define TB_KEY_F12 (0xFFFF-11) 27 | #define TB_KEY_INSERT (0xFFFF-12) 28 | #define TB_KEY_DELETE (0xFFFF-13) 29 | #define TB_KEY_HOME (0xFFFF-14) 30 | #define TB_KEY_END (0xFFFF-15) 31 | #define TB_KEY_PGUP (0xFFFF-16) 32 | #define TB_KEY_PGDN (0xFFFF-17) 33 | #define TB_KEY_ARROW_UP (0xFFFF-18) 34 | #define TB_KEY_ARROW_DOWN (0xFFFF-19) 35 | #define TB_KEY_ARROW_LEFT (0xFFFF-20) 36 | #define TB_KEY_ARROW_RIGHT (0xFFFF-21) 37 | #define TB_KEY_MOUSE_LEFT (0xFFFF-22) 38 | #define TB_KEY_MOUSE_RIGHT (0xFFFF-23) 39 | #define TB_KEY_MOUSE_MIDDLE (0xFFFF-24) 40 | #define TB_KEY_MOUSE_RELEASE (0xFFFF-25) 41 | #define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26) 42 | #define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) 43 | 44 | /* These are all ASCII code points below SPACE character and a BACKSPACE key. */ 45 | #define TB_KEY_CTRL_TILDE 0x00 46 | #define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ 47 | #define TB_KEY_CTRL_A 0x01 48 | #define TB_KEY_CTRL_B 0x02 49 | #define TB_KEY_CTRL_C 0x03 50 | #define TB_KEY_CTRL_D 0x04 51 | #define TB_KEY_CTRL_E 0x05 52 | #define TB_KEY_CTRL_F 0x06 53 | #define TB_KEY_CTRL_G 0x07 54 | #define TB_KEY_BACKSPACE 0x08 55 | #define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ 56 | #define TB_KEY_TAB 0x09 57 | #define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */ 58 | #define TB_KEY_CTRL_J 0x0A 59 | #define TB_KEY_CTRL_K 0x0B 60 | #define TB_KEY_CTRL_L 0x0C 61 | #define TB_KEY_ENTER 0x0D 62 | #define TB_KEY_CTRL_M 0x0D /* clash with 'ENTER' */ 63 | #define TB_KEY_CTRL_N 0x0E 64 | #define TB_KEY_CTRL_O 0x0F 65 | #define TB_KEY_CTRL_P 0x10 66 | #define TB_KEY_CTRL_Q 0x11 67 | #define TB_KEY_CTRL_R 0x12 68 | #define TB_KEY_CTRL_S 0x13 69 | #define TB_KEY_CTRL_T 0x14 70 | #define TB_KEY_CTRL_U 0x15 71 | #define TB_KEY_CTRL_V 0x16 72 | #define TB_KEY_CTRL_W 0x17 73 | #define TB_KEY_CTRL_X 0x18 74 | #define TB_KEY_CTRL_Y 0x19 75 | #define TB_KEY_CTRL_Z 0x1A 76 | #define TB_KEY_ESC 0x1B 77 | #define TB_KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */ 78 | #define TB_KEY_CTRL_3 0x1B /* clash with 'ESC' */ 79 | #define TB_KEY_CTRL_4 0x1C 80 | #define TB_KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */ 81 | #define TB_KEY_CTRL_5 0x1D 82 | #define TB_KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */ 83 | #define TB_KEY_CTRL_6 0x1E 84 | #define TB_KEY_CTRL_7 0x1F 85 | #define TB_KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */ 86 | #define TB_KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */ 87 | #define TB_KEY_SPACE 0x20 88 | #define TB_KEY_BACKSPACE2 0x7F 89 | #define TB_KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */ 90 | 91 | /* These are non-existing ones. 92 | * 93 | * #define TB_KEY_CTRL_1 clash with '1' 94 | * #define TB_KEY_CTRL_9 clash with '9' 95 | * #define TB_KEY_CTRL_0 clash with '0' 96 | */ 97 | 98 | /* 99 | * Alt modifier constant, see tb_event.mod field and tb_select_input_mode function. 100 | * Mouse-motion modifier 101 | */ 102 | #define TB_MOD_ALT 0x01 103 | #define TB_MOD_MOTION 0x02 104 | 105 | /* Colors (see struct tb_cell's fg and bg fields). */ 106 | #define TB_DEFAULT 0x00 107 | #define TB_BLACK 0x01 108 | #define TB_RED 0x02 109 | #define TB_GREEN 0x03 110 | #define TB_YELLOW 0x04 111 | #define TB_BLUE 0x05 112 | #define TB_MAGENTA 0x06 113 | #define TB_CYAN 0x07 114 | #define TB_WHITE 0x08 115 | 116 | /* Attributes, it is possible to use multiple attributes by combining them 117 | * using bitwise OR ('|'). Although, colors cannot be combined. But you can 118 | * combine attributes and a single color. See also struct tb_cell's fg and bg 119 | * fields. 120 | */ 121 | #define TB_BOLD 0x0100 122 | #define TB_FAINT 0x0200 123 | #define TB_ITALIC 0x0400 124 | #define TB_UNDERLINE 0x0800 125 | #define TB_BLINK 0x1000 126 | #define TB_REVERSE 0x2000 127 | #define TB_CROSSOUT 0x4000 128 | 129 | /* A cell, single conceptual entity on the terminal screen. The terminal screen 130 | * is basically a 2d array of cells. It has the following fields: 131 | * - 'ch' is a unicode character 132 | * - 'sgr' includes typographical and color information 133 | */ 134 | struct tb_cell { 135 | uint32_t ch; 136 | struct sgr sgr; 137 | }; 138 | 139 | #define TB_EVENT_KEY 1 140 | #define TB_EVENT_RESIZE 2 141 | #define TB_EVENT_MOUSE 3 142 | 143 | /* An event, single interaction from the user. The 'mod' and 'ch' fields are 144 | * valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type' 145 | * is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is 146 | * TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY 147 | * or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only 148 | * one of them can be non-zero at a time. 149 | */ 150 | struct tb_event { 151 | uint8_t type; 152 | uint8_t mod; /* modifiers to either 'key' or 'ch' below */ 153 | uint16_t key; /* one of the TB_KEY_* constants */ 154 | uint32_t ch; /* unicode character */ 155 | int32_t w; 156 | int32_t h; 157 | int32_t x; 158 | int32_t y; 159 | }; 160 | 161 | /* Error codes returned by tb_init(). All of them are self-explanatory, except 162 | * the pipe trap error. Termbox uses unix pipes in order to deliver a message 163 | * from a signal handler (SIGWINCH) to the main event reading loop. Honestly in 164 | * most cases you should just check the returned code as < 0. 165 | */ 166 | #define TB_EUNSUPPORTED_TERMINAL -1 167 | #define TB_EFAILED_TO_OPEN_TTY -2 168 | #define TB_EPIPE_TRAP_ERROR -3 169 | 170 | /* Initializes the termbox library. This function should be called before any 171 | * other functions. Function tb_init is same as tb_init_file("/dev/tty"). 172 | * After successful initialization, the library must be 173 | * finalized using the tb_shutdown() function. 174 | */ 175 | int tb_init(void); 176 | int tb_init_file(const char* name); 177 | int tb_init_fd(int inout); 178 | void tb_shutdown(void); 179 | 180 | /* Returns the size of the internal back buffer (which is the same as 181 | * terminal's window size in characters). The internal buffer can be resized 182 | * after tb_clear() or tb_present() function calls. Both dimensions have an 183 | * unspecified negative value when called before tb_init() or after 184 | * tb_shutdown(). 185 | */ 186 | int tb_width(void); 187 | int tb_height(void); 188 | 189 | /* Clears the internal back buffer using TB_DEFAULT color or the 190 | * color/attributes set by tb_set_clear_attributes() function. 191 | */ 192 | void tb_clear(void); 193 | void tb_set_clear_attributes(uint16_t fg, uint16_t bg); 194 | 195 | /* Synchronizes the internal back buffer with the terminal. */ 196 | void tb_present(void); 197 | 198 | #define TB_HIDE_CURSOR -1 199 | 200 | /* Sets the position of the cursor. Upper-left character is (0, 0). If you pass 201 | * TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor 202 | * is hidden by default. 203 | */ 204 | void tb_set_cursor(int cx, int cy); 205 | 206 | /* Changes cell's parameters in the internal back buffer at the specified 207 | * position. 208 | */ 209 | void tb_put_cell(int x, int y, const struct tb_cell *cell); 210 | void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg); 211 | 212 | /* Copies the buffer from 'cells' at the specified position, assuming the 213 | * buffer is a two-dimensional array of size ('w' x 'h'), represented as a 214 | * one-dimensional buffer containing lines of cells starting from the top. 215 | * 216 | * (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own) 217 | */ 218 | void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells); 219 | 220 | /* Returns a pointer to internal cell back buffer. You can get its dimensions 221 | * using tb_width() and tb_height() functions. The pointer stays valid as long 222 | * as no tb_clear() and tb_present() calls are made. The buffer is 223 | * one-dimensional buffer containing lines of cells starting from the top. 224 | */ 225 | struct tb_cell *tb_cell_buffer(void); 226 | 227 | #define TB_INPUT_CURRENT 0 /* 000 */ 228 | #define TB_INPUT_ESC 1 /* 001 */ 229 | #define TB_INPUT_ALT 2 /* 010 */ 230 | #define TB_INPUT_MOUSE 4 /* 100 */ 231 | 232 | /* Sets the termbox input mode. Termbox has two input modes: 233 | * 1. Esc input mode. 234 | * When ESC sequence is in the buffer and it doesn't match any known 235 | * ESC sequence => ESC means TB_KEY_ESC. 236 | * 2. Alt input mode. 237 | * When ESC sequence is in the buffer and it doesn't match any known 238 | * sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event. 239 | * 240 | * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the 241 | * modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes 242 | * were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some 243 | * reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it 244 | * will behave as if only TB_INPUT_ESC was selected. 245 | * 246 | * If 'mode' is TB_INPUT_CURRENT, it returns the current input mode. 247 | * 248 | * Default termbox input mode is TB_INPUT_ESC. 249 | */ 250 | int tb_select_input_mode(int mode); 251 | 252 | #define TB_OUTPUT_CURRENT 0 253 | #define TB_OUTPUT_NORMAL 1 254 | #define TB_OUTPUT_256 2 255 | #define TB_OUTPUT_216 3 256 | #define TB_OUTPUT_GRAYSCALE 4 257 | 258 | /* Sets the termbox output mode. Termbox has three output options: 259 | * 1. TB_OUTPUT_NORMAL => [1..8] 260 | * This mode provides 8 different colors: 261 | * black, red, green, yellow, blue, magenta, cyan, white 262 | * Shortcut: TB_BLACK, TB_RED, ... 263 | * Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE 264 | * 265 | * Example usage: 266 | * tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); 267 | * 268 | * 2. TB_OUTPUT_256 => [0..256] 269 | * In this mode you can leverage the 256 terminal mode: 270 | * 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL 271 | * 0x08 - 0x0f: TB_* | TB_BOLD 272 | * 0x10 - 0xe7: 216 different colors 273 | * 0xe8 - 0xff: 24 different shades of grey 274 | * 275 | * Example usage: 276 | * tb_change_cell(x, y, '@', 184, 240); 277 | * tb_change_cell(x, y, '@', 0xb8, 0xf0); 278 | * 279 | * 3. TB_OUTPUT_216 => [0..216] 280 | * This mode supports the 3rd range of the 256 mode only. 281 | * But you don't need to provide an offset. 282 | * 283 | * 4. TB_OUTPUT_GRAYSCALE => [0..23] 284 | * This mode supports the 4th range of the 256 mode only. 285 | * But you dont need to provide an offset. 286 | * 287 | * Execute build/src/demo/output to see its impact on your terminal. 288 | * 289 | * If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode. 290 | * 291 | * Default termbox output mode is TB_OUTPUT_NORMAL. 292 | */ 293 | int tb_select_output_mode(int mode); 294 | 295 | /* Wait for an event up to 'timeout' milliseconds and fill the 'event' 296 | * structure with it, when the event is available. Returns the type of the 297 | * event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case 298 | * there were no event during 'timeout' period. 299 | */ 300 | int tb_peek_event(struct tb_event *event, int timeout); 301 | 302 | /* Wait for an event forever and fill the 'event' structure with it, when the 303 | * event is available. Returns the type of the event (one of TB_EVENT_* 304 | * constants) or -1 if there was an error. 305 | */ 306 | int tb_poll_event(struct tb_event *event); 307 | 308 | /* Utility utf8 functions. */ 309 | #define TB_EOF -1 310 | int tb_utf8_char_length(char c); 311 | int tb_utf8_char_to_unicode(uint32_t *out, const char *c); 312 | int tb_utf8_unicode_to_char(char *out, uint32_t c); 313 | 314 | #ifdef __cplusplus 315 | } 316 | #endif 317 | 318 | // vim: noexpandtab 319 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | Tests in this directory are just normal programs that generate output and exit 2 | zero on success and non-zero on failure. There is no framework per se. 3 | 4 | The `make test' target builds and runs all tests in this directory. 5 | 6 | The `runtest' program in this directory runs one or more tests and reports 7 | output only when a test failure occurs. Use it like this: 8 | 9 | test/runtest tkbd_parse_test 10 | 11 | Tests written in C typically use printf(3) to provide context information and 12 | assert(3) to check state and abort. On failure, test output is provided and the 13 | failed assertion should output the file and line number. That's typically enough 14 | to go on. 15 | 16 | It's also possible to load a test in gdb and break at an assertion failure to 17 | get a backtrace, inspect memory state, etc. 18 | 19 | make profile=test debug 20 | gdb test/tkbd_parse_test 21 | (gdb) run 22 | 23 | (gdb) bt 24 | 25 | -------------------------------------------------------------------------------- /test/runtest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Usage: runtest ... 3 | set -eu 4 | 5 | # some tests assume working dir is set to . 6 | cd "$(dirname "$0")" 7 | 8 | # check that we have 'cat -v' for escaping non-printable characters 9 | # since it's non-posix 10 | if cat -v /dev/null 1>&2; then 11 | catv="cat -v" 12 | else 13 | catv="cat" 14 | fi 15 | 16 | # run each test, showing output only on failure 17 | oks=0 fails=0 18 | while [ $# -gt 0 ]; do 19 | printf "test: %-25s%s" "$1" "..." 20 | if out=$(./"$(basename "$1")" 2>&1); then 21 | oks=$(( oks+1 )) 22 | echo " OK" 23 | else 24 | fails=$(( fails+1 )) 25 | echo " FAILED" 26 | printf "%s\n" "$out" | sed 's/^/ /' | $catv 27 | fi 28 | shift 29 | done 30 | 31 | echo "Ran $(( oks+fails )) tests: $oks ok, $fails failed." 32 | 33 | # exit non-zero if any tests failed 34 | test "$fails" -eq 0 35 | -------------------------------------------------------------------------------- /test/sgr_attrs_test.c: -------------------------------------------------------------------------------- 1 | #include "../sgr.c" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * This is more of a demo / visual test to see if combining attributes in 9 | * extreme ways causes issues with terminal display. 10 | */ 11 | 12 | static void write_attr_label(char *label, struct sgr sgr) 13 | { 14 | int sz; 15 | sgr_write(1, sgr); 16 | sz = write(1, label, strlen(label)); 17 | sgr_write(1, (struct sgr){ SGR_NEGATE|sgr.at }); 18 | sz = write(1, " ", 1); 19 | (void)sz; 20 | } 21 | 22 | #define writeln() do { sz=write(1, "\n", 1);(void)sz; } while(0); 23 | 24 | static uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) 25 | { 26 | return (r<<16 | g<<8 | b); 27 | } 28 | 29 | int main(void) 30 | { 31 | // make stdout line buffered 32 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 33 | 34 | int sz; 35 | struct sgr sgr = {0}; 36 | 37 | // basic typographic attributes 38 | write_attr_label("bold", (struct sgr){SGR_BOLD}); 39 | write_attr_label("faint", (struct sgr){SGR_FAINT}); 40 | write_attr_label("italic", (struct sgr){SGR_ITALIC}); 41 | write_attr_label("underline", (struct sgr){SGR_UNDERLINE}); 42 | write_attr_label("blink", (struct sgr){SGR_BLINK}); 43 | write_attr_label("reverse", (struct sgr){SGR_REVERSE}); 44 | write_attr_label("strike", (struct sgr){SGR_STRIKE}); 45 | writeln(); 46 | writeln(); 47 | 48 | char *colors[8] = { 49 | "black", "red", "green", "yellow", 50 | "blue", "magenta", "cyan", "white" 51 | }; 52 | 53 | // foreground colors 54 | for (int i = 0; i < 8; i++) { 55 | write_attr_label(colors[i], (struct sgr){SGR_FG, i}); 56 | } 57 | write_attr_label("default", (struct sgr){SGR_FG, SGR_DEFAULT}); 58 | writeln(); 59 | writeln(); 60 | 61 | // background colors 62 | for (int i = 0; i < 8; i++) { 63 | write_attr_label(colors[i], (struct sgr){SGR_BG, 0, i}); 64 | } 65 | write_attr_label("default", (struct sgr){SGR_BG, 0, SGR_DEFAULT}); 66 | writeln(); 67 | writeln(); 68 | 69 | // reversed foreground colors 70 | for (int i = 0; i < 8; i++) { 71 | sgr = (struct sgr){SGR_REVERSE, i}; 72 | write_attr_label(colors[i], sgr); 73 | } 74 | sgr = (struct sgr){SGR_REVERSE|SGR_FG, SGR_DEFAULT}; 75 | write_attr_label("default", sgr); 76 | writeln(); 77 | writeln(); 78 | 79 | // reversed background colors 80 | for (int i = 0; i < 8; i++) { 81 | sgr = (struct sgr){SGR_REVERSE|SGR_BG, 0, i}; 82 | write_attr_label(colors[i], sgr); 83 | } 84 | sgr = (struct sgr){SGR_REVERSE|SGR_BG, 0, SGR_DEFAULT}; 85 | write_attr_label("default", sgr); 86 | writeln(); 87 | writeln(); 88 | 89 | // 216-color mode cube 90 | for (int g = 0; g < 6; g++) { 91 | for (int r = 0; r < 6; r++) { 92 | for (int b = 0; b < 6; b++) { 93 | int color = (r * 36) + (g * 6) + b; 94 | char buf[4]; 95 | sprintf(buf, "%03d", color); 96 | sgr = (struct sgr){ SGR_BG216, 0, color }; 97 | write_attr_label(buf, sgr); 98 | } 99 | sz = write(1, " ", 1); (void)sz; 100 | } 101 | writeln(); 102 | } 103 | writeln(); 104 | 105 | // 256-color mode 106 | for (int i = 0; i < 256; i++) { 107 | char buf[4]; 108 | sprintf(buf, "%03d", i); 109 | write_attr_label(buf, (struct sgr){SGR_BG256, 0, i}); 110 | if ((i < 16 && (i+1) % 8 == 0) || 111 | (i > 16 && (i-16+1) % 12 == 0)) 112 | writeln(); 113 | } 114 | writeln(); 115 | 116 | // 256-color mode with many attributes 117 | for (int i = 0; i < 256; i++) { 118 | char buf[4]; 119 | sprintf(buf, "%03d", i); 120 | sgr = (struct sgr) { 121 | .at = SGR_BOLD|SGR_ITALIC|SGR_UNDERLINE| 122 | SGR_STRIKE|SGR_BG256, 123 | .bg = i 124 | }; 125 | write_attr_label(buf, sgr); 126 | if ((i < 16 && (i+1) % 8 == 0) || 127 | (i > 16 && (i-16+1) % 12 == 0)) 128 | writeln(); 129 | } 130 | writeln(); 131 | 132 | // true color mode 133 | for (int colnum = 0; colnum < 77; colnum++) { 134 | uint32_t r = 255-(colnum*255/76); 135 | uint32_t g = (colnum*510/76); 136 | uint32_t b = (colnum*255/76); 137 | if (g>255) g = 510-g; 138 | sgr = (struct sgr) { 139 | .at = SGR_FG16M|SGR_BG16M, 140 | .fg = rgb(255-r, 255-g, 255-b), 141 | .bg = rgb(r, g, b) 142 | }; 143 | sgr_write(1, sgr); 144 | sz = write(1, ":", 1); 145 | } 146 | writeln(); 147 | 148 | sgr_write(1, (struct sgr){SGR_RESET}); 149 | (void)sz; 150 | return 0; 151 | } 152 | 153 | // vim: noexpandtab 154 | -------------------------------------------------------------------------------- /test/sgr_encode_test.c: -------------------------------------------------------------------------------- 1 | #include "../sgr.c" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void test_uitoa(void) 9 | { 10 | // test that our uitoa function works correctly 11 | char buf[16]; 12 | int sz = uitoa(3567, buf); 13 | assert(sz == 4); 14 | assert(strcmp("3567", buf) == 0); 15 | } 16 | 17 | // struct to hold a string buffer and current position 18 | struct testbuf { 19 | int pos; 20 | char str[SGR_STR_MAX]; 21 | }; 22 | 23 | // write callback passed to sgr_encode 24 | static void testbuf_write(void *dest, char *src, int n) 25 | { 26 | struct testbuf *buf = (struct testbuf*)dest; 27 | memcpy(buf->str + buf->pos, src, n); 28 | buf->pos += n; 29 | } 30 | 31 | static void test_sgr_encode(void) 32 | { 33 | // Check that sgr_encode() calls the write callback 34 | struct testbuf buf = {0}; 35 | struct sgr sgr = {SGR_BOLD|SGR_FG, SGR_RED}; 36 | unsigned n = sgr_encode(&buf, testbuf_write, sgr); 37 | printf("n = %d, str = %s\n", n, buf.str); 38 | char *expect = "\x1b[1;31m"; 39 | assert(strcmp(expect, buf.str) == 0); 40 | assert(n == strlen(expect)); 41 | 42 | // More complicated example using more codes 43 | buf.pos = 0; 44 | memset(buf.str, 0, SGR_STR_MAX); 45 | sgr = (struct sgr){SGR_BOLD|SGR_ITALIC|SGR_UNDERLINE|SGR_BG216, 0, 128}; 46 | n = sgr_encode(&buf, testbuf_write, sgr); 47 | printf("n = %d, str = %s\n", n, buf.str); 48 | expect = "\x1b[1;3;4;48;5;144m"; 49 | assert(strcmp(expect, buf.str) == 0); 50 | assert(n == strlen(expect)); 51 | 52 | // Empty SGR value shouldn't generate any output 53 | buf.pos = 0; 54 | memset(buf.str, 0, SGR_STR_MAX); 55 | n = sgr_encode(&buf, testbuf_write, (struct sgr){0}); 56 | printf("n = %d, str = %s\n", n, buf.str); 57 | expect = ""; 58 | assert(strcmp(expect, buf.str) == 0); 59 | assert(n == strlen(expect)); 60 | } 61 | 62 | // Write SGR to string buffer. 63 | static void test_sgr_str(void) 64 | { 65 | char buf[SGR_STR_MAX]; 66 | 67 | unsigned n = sgr_str(buf, (struct sgr){SGR_BOLD|SGR_FG, SGR_RED}); 68 | printf("n = %d, str = %s\n", n, buf); 69 | char *expect = "\x1b[1;31m"; 70 | assert(strcmp(expect, buf) == 0); 71 | assert(n == strlen(expect)); 72 | 73 | // More complicated example using more codes 74 | n = sgr_str(buf, (struct sgr){ 75 | .at = SGR_BOLD|SGR_ITALIC|SGR_UNDERLINE|SGR_BG216, 76 | .fg = 0, 77 | .bg = 128 78 | }); 79 | printf("n = %d, str = %s\n", n, buf); 80 | expect = "\x1b[1;3;4;48;5;144m"; 81 | assert(strcmp(expect, buf) == 0); 82 | assert(n == strlen(expect)); 83 | 84 | // Empty SGR value shouldn't generate any output 85 | n = sgr_str(buf, (struct sgr){0}); 86 | printf("n = %d, str = %s\n", n, buf); 87 | expect = ""; 88 | assert(strcmp(expect, buf) == 0); 89 | assert(n == strlen(expect)); 90 | } 91 | 92 | // Write SGR to file descriptor. 93 | static void test_sgr_write(void) 94 | { 95 | int n = sgr_write(1, (struct sgr){SGR_BOLD|SGR_FG,SGR_RED}); 96 | char *expect = "\x1b[1;31m"; 97 | assert((unsigned)n == strlen(expect)); 98 | 99 | // More complicated example using more codes 100 | n = sgr_write(1, (struct sgr){ 101 | .at = SGR_BOLD|SGR_ITALIC|SGR_UNDERLINE|SGR_BG216, 102 | .bg = 128 103 | }); 104 | expect = "\x1b[1;3;4;48;5;144m"; 105 | assert((unsigned)n == strlen(expect)); 106 | 107 | // Empty SGR value shouldn't generate any output 108 | n = sgr_write(1, (struct sgr){0}); 109 | expect = ""; 110 | assert((unsigned)n == strlen(expect)); 111 | 112 | // Write error 113 | n = sgr_write(37, (struct sgr){SGR_BOLD, SGR_RED}); 114 | assert(errno != 0); 115 | assert(n == -1); 116 | } 117 | 118 | // Write SGR to file descriptor. 119 | static void test_sgr_fwrite(void) 120 | { 121 | int n = sgr_fwrite(stdout, (struct sgr){SGR_BOLD|SGR_FG, SGR_RED}); 122 | char *expect = "\x1b[1;31m"; 123 | printf("n = %d\n", n); 124 | assert((unsigned)n == strlen(expect)); 125 | 126 | // More complicated example using more codes 127 | n = sgr_fwrite(stdout, (struct sgr){ 128 | .at = SGR_BOLD|SGR_ITALIC|SGR_UNDERLINE|SGR_BG216, 129 | .bg = 128 130 | }); 131 | expect = "\x1b[1;3;4;48;5;144m"; 132 | assert((unsigned)n == strlen(expect)); 133 | 134 | // Empty SGR value shouldn't generate any output 135 | n = sgr_fwrite(stdout, (struct sgr){0}); 136 | expect = ""; 137 | assert((unsigned)n == strlen(expect)); 138 | 139 | // Write error 140 | n = sgr_fwrite(stdin, (struct sgr){SGR_BOLD, SGR_RED}); 141 | assert(n == -1); 142 | assert(ferror(stdin) != 0); 143 | } 144 | 145 | int main(void) 146 | { 147 | // make stdout line buffered 148 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 149 | 150 | test_uitoa(); 151 | test_sgr_encode(); 152 | test_sgr_str(); 153 | test_sgr_write(); 154 | test_sgr_fwrite(); 155 | 156 | return 0; 157 | } 158 | 159 | // vim: noexpandtab 160 | -------------------------------------------------------------------------------- /test/sgr_test.c: -------------------------------------------------------------------------------- 1 | 2 | #include "../sgr.c" 3 | 4 | #include 5 | #include 6 | 7 | int main(void) 8 | { 9 | // make stdout line buffered 10 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 11 | 12 | // check that sgr struct is 8 bytes / 64 bits 13 | size_t sz = sizeof(struct sgr); 14 | printf("sizeof(struct sgr) = %ld bytes / %ld bits\n", sz, sz*8); 15 | assert(sz == 8 || "sgr struct should be 64 bits"); 16 | 17 | return 0; 18 | } 19 | 20 | // vim: noexpandtab 21 | -------------------------------------------------------------------------------- /test/sgr_unpack_test.c: -------------------------------------------------------------------------------- 1 | #include "../sgr.c" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(void) 9 | { 10 | // make stdout line buffered 11 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 12 | 13 | // buffer for holding sgr codes 14 | uint16_t codes[SGR_ELMS_MAX]; 15 | 16 | // The sgr struct type has a set of attribute bitflags, a 17 | // foreground color, and a background color. 18 | struct sgr sgr = { 19 | .at = SGR_BOLD|SGR_ITALIC|SGR_FG|SGR_BG, 20 | .fg = SGR_RED, 21 | .bg = SGR_CYAN 22 | }; 23 | int n = sgr_unpack(codes, sgr); 24 | assert(n == 4); // number of codes written 25 | assert(codes[0] == 1); // bold text 26 | assert(codes[1] == 3); // italic text 27 | assert(codes[2] == 31); // red text/foreground color 28 | assert(codes[3] == 46); // cyan background color 29 | 30 | // You can also construct them on the fly using C99 compound literal 31 | // syntax. Here we create an SGR for bold text on a green background: 32 | n = sgr_unpack(codes, (struct sgr){.at=SGR_BOLD|SGR_BG, .bg=SGR_GREEN}); 33 | assert(n == 2); 34 | assert(codes[0] == 1); // bold text 35 | assert(codes[1] == 42); // green background 36 | 37 | // The default 8-color palette includes normal intensity colors. 38 | // You can access another 8 high intensity colors by applying the 39 | // SGR_FG16 or SGR_BG16 attribute instead of SG_FG/SG_BG: 40 | n = sgr_unpack(codes, (struct sgr){.at=SGR_FG16, .fg=SGR_YELLOW}); 41 | assert(n == 1); 42 | assert(codes[0] == 93); // bright yellow foreground 43 | 44 | // Apply all typographic and background attrs without changing color: 45 | n = sgr_unpack(codes, (struct sgr){SGR_BOLD|SGR_FAINT|SGR_ITALIC| 46 | SGR_UNDERLINE|SGR_BLINK|SGR_REVERSE| 47 | SGR_STRIKE}); 48 | assert(n == 7); 49 | assert(codes[0] == 1); // bold 50 | assert(codes[1] == 2); // faint 51 | assert(codes[2] == 3); // italic 52 | assert(codes[3] == 4); // underline 53 | assert(codes[4] == 5); // blink 54 | assert(codes[5] == 7); // reverse 55 | assert(codes[6] == 9); // strike 56 | 57 | // Attributes are inherited from the current context by default. So if 58 | // you send a SGR with bold on, all following text will be bold unless 59 | // explitly reset. You can reset all foreground, background, and color 60 | // attributes to their default off values: 61 | sgr = (struct sgr){SGR_RESET|SGR_FAINT|SGR_FG, SGR_MAGENTA}; 62 | n = sgr_unpack(codes, sgr); 63 | 64 | assert(n == 3); 65 | assert(codes[0] == 0); // reset 66 | assert(codes[1] == 2); // faint 67 | assert(codes[2] == 35); // magenta foreground 68 | 69 | // It's also possible for an SGR value to turn its attributes off. 70 | // This lets you reset specific attributes while leaving others in tact: 71 | sgr = (struct sgr){SGR_NEGATE|SGR_BOLD|SGR_REVERSE|SGR_FG}; 72 | n = sgr_unpack(codes, sgr); 73 | 74 | assert(n == 3); 75 | for (int i = 0; i < n; i++) 76 | printf("codes[%d] = %d\n", i, codes[i]); 77 | assert(codes[0] == 22); // bold off (normal intensity) 78 | assert(codes[1] == 27); // reverse off 79 | assert(codes[2] == 39); // default foreground color 80 | 81 | // There's also the concept of default foreground and background colors 82 | // that may be different from any of the 8 base colors: 83 | n = sgr_unpack(codes, (struct sgr){SGR_FG, SGR_DEFAULT}); 84 | assert(n == 1); 85 | assert(codes[0] == 39); 86 | 87 | // Same as above but apply the default foreground and background color: 88 | sgr = (struct sgr){SGR_FG|SGR_BG, SGR_DEFAULT, SGR_DEFAULT}; 89 | n = sgr_unpack(codes, sgr); 90 | 91 | assert(n == 2); 92 | assert(codes[0] == 39); // default foreground color 93 | assert(codes[1] == 49); // default background color 94 | 95 | // Like all other colors, default colors may be applied along with any 96 | // other typographical attributes. 97 | sgr = (struct sgr){SGR_UNDERLINE|SGR_BLINK|SGR_FG, SGR_DEFAULT}; 98 | n = sgr_unpack(codes, sgr); 99 | assert(n == 3); 100 | assert(codes[0] == 4); // underline 101 | assert(codes[1] == 5); // blink 102 | assert(codes[2] == 39); // default foreground color 103 | 104 | // The SGR_FG and SGR_BG attributes cause the fg and bg colors to be 105 | // interpreted as 8-color mode colors. You can switch into 216-color, 106 | // 256-color, and 24-color greyscale modes as well as 16M true color 107 | // mode. 108 | // 109 | // Switch to 216-color mode and apply color 172 to the fg: 110 | n = sgr_unpack(codes, (struct sgr){SGR_ITALIC|SGR_FG216, 172}); 111 | assert(n == 4); 112 | assert(codes[0] == 3); // italic text 113 | assert(codes[1] == 38); // set foreground color 114 | assert(codes[2] == 5); // ... 115 | assert(codes[3] == 188); // to 172 (orange) in the 216-color palette 116 | 117 | // Switch into 24-color greyscale mode and apply color 10 to the bg: 118 | n = sgr_unpack(codes, (struct sgr){SGR_UNDERLINE|SGR_BG24, 0, 10}); 119 | assert(n == 4); 120 | assert(codes[0] == 4); // underline text 121 | assert(codes[1] == 48); // set background color 122 | assert(codes[2] == 5); // 256 color selector 123 | assert(codes[3] == 242); // to grey 10 124 | 125 | // Construct colors for 16M color mode using 0xRRGGBB values: 126 | sgr = (struct sgr){SGR_UNDERLINE|SGR_BG16M, 0, 0xFF9911}; 127 | n = sgr_unpack(codes, sgr); 128 | assert(n == 6); 129 | assert(codes[0] == 4); // underline text 130 | assert(codes[1] == 48); // set background color 131 | assert(codes[2] == 2); // true color selector 132 | assert(codes[3] == 0xFF); // red 133 | assert(codes[4] == 0x99); // green 134 | assert(codes[5] == 0x11); // blue 135 | 136 | return 0; 137 | } 138 | 139 | // vim: noexpandtab 140 | -------------------------------------------------------------------------------- /test/terminfo/6d: -------------------------------------------------------------------------------- 1 | m -------------------------------------------------------------------------------- /test/terminfo/m/minitel1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aux01/termlib/4a955e0807fba20f6cc6a90169e8f1d86fd116ed/test/terminfo/m/minitel1 -------------------------------------------------------------------------------- /test/terminfo/x/xterm-badfile: -------------------------------------------------------------------------------- 1 | This is not a terminfo file 2 | -------------------------------------------------------------------------------- /test/terminfo/x/xterm-color: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aux01/termlib/4a955e0807fba20f6cc6a90169e8f1d86fd116ed/test/terminfo/x/xterm-color -------------------------------------------------------------------------------- /test/terminfo/x/xterm-kitty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aux01/termlib/4a955e0807fba20f6cc6a90169e8f1d86fd116ed/test/terminfo/x/xterm-kitty -------------------------------------------------------------------------------- /test/terminfo/x/xterm-new: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aux01/termlib/4a955e0807fba20f6cc6a90169e8f1d86fd116ed/test/terminfo/x/xterm-new -------------------------------------------------------------------------------- /test/ti-stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #/ Usage: test/ti-stress.sh 3 | #/ Loads all terminfo files on system in an attempt to surface bugs. 4 | set -eu 5 | 6 | wd="$(cd "$(dirname "$0")" && pwd)" 7 | 8 | terminfo_dirs=" 9 | /etc/terminfo 10 | /usr/lib/terminfo 11 | /usr/share/terminfo 12 | /usr/local/share/terminfo 13 | $HOME/.terminfo 14 | " 15 | 16 | # loop through all terminfo files and try to load via the capdump utility 17 | oks=0 fails=0 18 | for f in $(find 2>/dev/null $terminfo_dirs -type f | grep -v README); do 19 | term="$(basename "$f")" 20 | if output="$(TERM=$term "$wd/../demo/capdump" 2>&1 1>/dev/null)"; then 21 | oks=$(( oks+1 )) 22 | else 23 | fails=$(( fails+1 )) 24 | printf "FAILED: %s\n" "$term" 25 | echo "$output" | sed 's/^/ /' 26 | fi 27 | done 28 | 29 | # report 30 | printf "%d terminfo files loaded: %d ok, %d failed.\n" \ 31 | $((oks+fails)) $oks $fails 32 | 33 | # exit non-zero if any failures 34 | test $fails -eq 0 35 | -------------------------------------------------------------------------------- /test/ti_getcaps_test.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 700 // setenv 2 | 3 | #include "../ti.c" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void test_getcaps_by_name() { 11 | int err; 12 | 13 | // load the terminfo data into the global ti_term struct and associate 14 | // with standard output: 15 | ti_terminfo *ti = ti_load("xterm-color", &err); 16 | assert(ti != NULL); 17 | 18 | // read boolean capabilities with the ti_getbool() function 19 | // returns 1 if the terminal has the capability or 0 if not 20 | int has_meta_key = ti_getbool(ti, "km"), 21 | back_color_erase = ti_getbool(ti, "bce"), 22 | hard_copy = ti_getbool(ti, "hc"); 23 | assert(has_meta_key == 1); 24 | assert(back_color_erase == 0); 25 | assert(hard_copy == 0); 26 | 27 | // unrecognized boolean capabilities return 0 like unset capabilities so 28 | // be careful with spelling... 29 | int has_imaginery_cap = ti_getbool(ti, "imagineryboolname"); 30 | assert(has_imaginery_cap == 0); 31 | 32 | // read numeric capabilities with the ti_getnumi() function: 33 | int colors = ti_getnum(ti, "colors"); 34 | assert(colors == 8); 35 | 36 | // numeric capabilities not supported by the terminal return -1: 37 | int width_status_line = ti_getnum(ti, "wsl"); 38 | assert(width_status_line == -1); 39 | 40 | // unrecognized numeric capabilities return -1 like unsupported 41 | // capabilities so be careful with spelling... 42 | int imaginery_num_cap = ti_getnum(ti, "imaginerynumname"); 43 | assert(imaginery_num_cap == -1); 44 | 45 | // read string capabilities with the ti_getstr() function: 46 | char *clr_eol = ti_getstr(ti, "el"); 47 | assert(strcmp("\x1b[K", clr_eol) == 0); 48 | 49 | // string capabilties not specified in the terminfo file return NULL: 50 | char *insert_padding = ti_getstr(ti, "ip"); 51 | assert(insert_padding == NULL); 52 | 53 | // unrecognized string capabilities return NULL like unsupported 54 | // capabilities so be careful with spelling... 55 | char *imaginery_str_cap = ti_getstr(ti, "imaginerystrname"); 56 | assert(imaginery_str_cap == NULL); 57 | 58 | // when you're done, remember to free terminal info memory: 59 | ti_free(ti); 60 | } 61 | 62 | void test_getcaps_by_name_extended() { 63 | int err; 64 | 65 | // load the terminfo data into the global ti_term struct and associate 66 | // with standard output: 67 | ti_terminfo *ti = ti_load("xterm-new", &err); 68 | assert(ti != NULL); 69 | 70 | // read boolean extended capabilities with the ti_getbool() function 71 | // returns 1 if the terminal has the capability or 0 if not 72 | int has_set_color = ti_getbool(ti, "AX"), 73 | has_xt = ti_getbool(ti, "XT"), 74 | has_unknown = ti_getbool(ti, "NOTACAP"); 75 | assert(has_set_color == 1); 76 | assert(has_xt == 1); 77 | assert(has_unknown == 0); 78 | 79 | // read numeric capabilities with the ti_getnumi() function: 80 | // TODO: need a terminfo file with extended numeric capabilities 81 | 82 | // read string capabilities with the ti_getstr() function: 83 | char *cross_out_on = ti_getstr(ti, "smxx"), 84 | *cross_out_off = ti_getstr(ti, "rmxx"); 85 | assert(strcmp("\x1b[9m", cross_out_on) == 0); 86 | assert(strcmp("\x1b[29m", cross_out_off) == 0); 87 | 88 | // when you're done, remember to free terminal info memory: 89 | ti_free(ti); 90 | } 91 | 92 | void test_getcaps_by_index() { 93 | int err; 94 | 95 | // load the terminfo data into the global ti_term struct and associate 96 | // with standard output: 97 | ti_terminfo *ti = ti_load("xterm-color", &err); 98 | assert(ti != NULL); 99 | 100 | // read boolean capabilities with the ti_getbooli() function 101 | // returns 1 if the terminal has the capability or 0 if not 102 | int has_meta_key = ti_getbooli(ti, ti_km), 103 | back_color_erase = ti_getbooli(ti, ti_bce), 104 | hard_copy = ti_getbooli(ti, ti_hc); 105 | assert(has_meta_key == 1); 106 | assert(back_color_erase == 0); 107 | assert(hard_copy == 0); 108 | 109 | // read numeric capabilities with the ti_getnumi() function: 110 | int colors = ti_getnumi(ti, ti_colors); 111 | assert(colors == 8); 112 | 113 | // numeric capabilities not supported by the terminal return -1: 114 | int width_status_line = ti_getnumi(ti, ti_wsl); 115 | assert(width_status_line == -1); 116 | 117 | // read string capabilities with the ti_getstri() function: 118 | char *clr_eol = ti_getstri(ti, ti_el); 119 | assert(strcmp("\x1b[K", clr_eol) == 0); 120 | 121 | // string capabilties not specified in the terminfo file return NULL: 122 | char *insert_padding = ti_getstri(ti, ti_ip); 123 | assert(insert_padding == NULL); 124 | 125 | // when you're done, remember to free terminal info memory: 126 | ti_free(ti); 127 | } 128 | 129 | int main(void) { 130 | // load terminfo data from our test directory only 131 | setenv("TERMINFO", "./terminfo", 1); 132 | 133 | test_getcaps_by_index(); 134 | test_getcaps_by_name(); 135 | test_getcaps_by_name_extended(); 136 | 137 | return 0; 138 | } 139 | 140 | // vim: noexpandtab 141 | -------------------------------------------------------------------------------- /test/ti_load_test.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 700 // setenv 2 | 3 | #include "../ti.c" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void test_legacy_storage_format() { 11 | int err = 0; 12 | 13 | // load the terminfo data into the global ti_term struct and associate 14 | // with standard output: 15 | ti_terminfo *ti = ti_load("xterm-color", &err); 16 | printf("err=%d\n", err); 17 | assert(err == 0); 18 | assert(ti != NULL); 19 | 20 | // the terminfo data includes multiple alternative names separated by 21 | // pipe characters: 22 | printf("ti->term_names=%s\n", ti->term_names); 23 | char *term_names = "xterm-color|generic \"ANSI\" color xterm (X Window System)"; 24 | assert(strcmp(term_names, ti->term_names) == 0); 25 | 26 | // terminfo files have bool, numeric, and string capability entries. 27 | // verify the correct number of entries were loaded for xterm-color. 28 | printf("bools_count=%d, nums_count=%d, strs_count=%d\n", 29 | ti->bools_count, ti->nums_count, ti->strs_count); 30 | assert(ti->bools_count == 38); 31 | assert(ti->nums_count == 16); 32 | assert(ti->strs_count == 413); 33 | 34 | // read some capabilities to verify the db is being processed correctly 35 | int has_meta_key = ti_getbooli(ti, ti_km); 36 | assert(has_meta_key == 1); 37 | int colors = ti_getnumi(ti, ti_colors); 38 | assert(colors == 8); 39 | char *clr_eol = ti_getstri(ti, ti_el); 40 | assert(strcmp("\x1b[K", clr_eol) == 0); 41 | 42 | // when you're done, remember to free terminal info memory: 43 | ti_free(ti); 44 | } 45 | 46 | void test_extended_storage_format() { 47 | int err = 0; 48 | 49 | // load the terminfo data into the global ti_term struct and associate 50 | // with standard output: 51 | ti_terminfo *ti = ti_load("xterm-new", &err); 52 | printf("err=%d\n", err); 53 | assert(err == 0); 54 | assert(ti != NULL); 55 | 56 | // the terminfo data includes multiple alternative names separated by 57 | // pipe characters: 58 | printf("ti->term_names=%s\n", ti->term_names); 59 | char *term_names = "xterm-new|modern xterm terminal emulator"; 60 | assert(strcmp(term_names, ti->term_names) == 0); 61 | 62 | // terminfo files have bool, numeric, and string capability entries. 63 | // verify the correct number of entries were loaded. 64 | printf("bools_count=%d, nums_count=%d, strs_count=%d\n", 65 | ti->bools_count, ti->nums_count, ti->strs_count); 66 | assert(ti->bools_count == 38); 67 | assert(ti->nums_count == 15); 68 | assert(ti->strs_count == 413); 69 | 70 | // read some legacy capabilities 71 | int has_meta_key = ti_getbooli(ti, ti_km); 72 | assert(has_meta_key == 1); 73 | int colors = ti_getnumi(ti, ti_colors); 74 | assert(colors == 8); 75 | char *clr_eol = ti_getstri(ti, ti_el); 76 | assert(strcmp("\x1b[K", clr_eol) == 0); 77 | 78 | // terminfo files have extended bool, numeric, and string capability 79 | // entries. verify the correct number of entries were loaded. 80 | printf("ext_bools_count=%d, ext_nums_count=%d, ext_strs_count=%d\n", 81 | ti->ext_bools_count, ti->ext_nums_count, ti->ext_strs_count); 82 | assert(ti->ext_bools_count == 2); 83 | assert(ti->ext_nums_count == 0); 84 | assert(ti->ext_strs_count == 60); 85 | 86 | // read some extended capabilities 87 | int has_ax = ti_getbool(ti, "AX"); 88 | assert(has_ax == 1); 89 | char *smxx = ti_getstr(ti, "smxx"); 90 | assert(strcmp("\x1b[9m", smxx) == 0); 91 | 92 | // when you're done, remember to free terminal info memory: 93 | ti_free(ti); 94 | } 95 | 96 | void test_extended_storage_format_odd_alignment() { 97 | int err = 0; 98 | 99 | // load the terminfo data into the global ti_term struct and associate 100 | // with standard output: 101 | ti_terminfo *ti = ti_load("xterm-kitty", &err); 102 | printf("err=%d\n", err); 103 | assert(err == 0); 104 | assert(ti != NULL); 105 | 106 | // the terminfo data includes multiple alternative names separated by 107 | // pipe characters: 108 | printf("ti->term_names=%s\n", ti->term_names); 109 | char *term_names = "xterm-kitty|KovIdTTY"; 110 | assert(strcmp(term_names, ti->term_names) == 0); 111 | 112 | // terminfo files have bool, numeric, and string capability entries. 113 | // verify the correct number of entries were loaded. 114 | printf("bools_count=%d, nums_count=%d, strs_count=%d\n", 115 | ti->bools_count, ti->nums_count, ti->strs_count); 116 | assert(ti->bools_count == 28); 117 | assert(ti->nums_count == 15); 118 | assert(ti->strs_count == 361); 119 | 120 | // read some capabilities to verify the db is being processed correctly 121 | int has_meta_key = ti_getbooli(ti, ti_km); 122 | assert(has_meta_key == 1); 123 | int colors = ti_getnumi(ti, ti_colors); 124 | assert(colors == 256); 125 | char *clr_eol = ti_getstri(ti, ti_el); 126 | assert(strcmp("\x1b[K", clr_eol) == 0); 127 | 128 | // terminfo files have extended bool, numeric, and string capability 129 | // entries. verify the correct number of entries were loaded. 130 | printf("ext_bools_count=%d, ext_nums_count=%d, ext_strs_count=%d\n", 131 | ti->ext_bools_count, ti->ext_nums_count, ti->ext_strs_count); 132 | assert(ti->ext_bools_count == 3); 133 | assert(ti->ext_nums_count == 0); 134 | assert(ti->ext_strs_count == 56); 135 | 136 | // read some extended capabilities 137 | int fullkbd = ti_getbool(ti, "fullkbd"); 138 | assert(fullkbd == 1); 139 | char *smxx = ti_getstr(ti, "smxx"); 140 | assert(strcmp("\x1b[9m", smxx) == 0); 141 | 142 | // when you're done, remember to free terminal info memory: 143 | ti_free(ti); 144 | } 145 | 146 | // The minitel1 terminfo file is unique in a number of ways. There are many long 147 | // binary string sequences 148 | void test_minitel1() { 149 | int err = 0; 150 | 151 | // load the terminfo data into the global ti_term struct and associate 152 | // with standard output: 153 | ti_terminfo *ti = ti_load("minitel1", &err); 154 | printf("err=%d\n", err); 155 | assert(err == 0); 156 | assert(ti != NULL); 157 | 158 | // the terminfo data includes multiple alternative names separated by 159 | // pipe characters: 160 | printf("ti->term_names=%s\n", ti->term_names); 161 | char *term_names = "minitel1|minitel 1"; 162 | assert(strcmp(term_names, ti->term_names) == 0); 163 | 164 | // terminfo files have bool, numeric, and string capability entries. 165 | // verify the correct number of entries were loaded. 166 | printf("bools_count=%d, nums_count=%d, strs_count=%d\n", 167 | ti->bools_count, ti->nums_count, ti->strs_count); 168 | assert(ti->bools_count == 19); 169 | assert(ti->nums_count == 15); 170 | assert(ti->strs_count == 361); 171 | 172 | // read some capabilities to verify the db is being processed correctly 173 | int has_meta_key = ti_getbooli(ti, ti_km); 174 | assert(has_meta_key == 0); 175 | int colors = ti_getnumi(ti, ti_colors); 176 | assert(colors == 8); 177 | char *clr_eol = ti_getstri(ti, ti_el); 178 | assert(strcmp("\x18", clr_eol) == 0); 179 | 180 | // terminfo files have extended bool, numeric, and string capability 181 | // entries. verify the correct number of entries were loaded. 182 | printf("ext_bools_count=%d, ext_nums_count=%d, ext_strs_count=%d\n", 183 | ti->ext_bools_count, ti->ext_nums_count, ti->ext_strs_count); 184 | assert(ti->ext_bools_count == 1); 185 | assert(ti->ext_nums_count == 0); 186 | assert(ti->ext_strs_count == 4); 187 | 188 | // read some extended capabilities 189 | int fullkbd = ti_getbool(ti, "G0"); 190 | assert(fullkbd == 1); 191 | char *xc = ti_getstr(ti, "XC"); 192 | char *expect = "B\031%,\241!,\242\",\243#,\244$,\245%,\246&,\247',\250(" 193 | ",\253+,\257P,\2600,\2611,\2622,\2633,\2655,\2677,\272k," 194 | "\273;,\274<,\275=,\276>,\277?,\300AA,\301BA,\302CA,\303" 195 | "DA,\304HA,\305JA,\306a,\307KC,\310AE,\311BE,\312CE,\313" 196 | "HE,\314AI,\315BI,\316CI,\317HI,\320b,\321DN,\322AO,\323" 197 | "BO,\324CO,\325DO,\326HO,\3274,\330i,\331AU,\332BU,\333C" 198 | "U,\334HU,\335BY,\336l,\337{,\340Aa,\341Ba,\342Ca,\343Da" 199 | ",\344Ha,\345Ja,\346q,\347Kc,\350Ae,\351Be,\352Ce,\353He" 200 | ",\354Ai,\355Bi,\356Ci,\357Hi,\360r,\361Dn,\362Ao,\363Bo" 201 | ",\364Co,\365Do,\366Ho,\3678,\370y,\371Au,\372Bu,\373Cu," 202 | "\374Hu,\375By,\376|,\377Hy,\252c,,0\017\031%\016,}#,f0," 203 | "g1,\\,\\,,+.,./,0\177,--"; 204 | assert(strcmp(expect, xc) == 0); 205 | 206 | // when you're done, remember to free terminal info memory: 207 | ti_free(ti); 208 | } 209 | 210 | // Loading a file that doesn't exist causes an error. 211 | void test_missing_file() { 212 | int err = 0; 213 | ti_terminfo *ti = ti_load("xterm-missing", &err); 214 | printf("err=%d\n", err); 215 | assert(ti == NULL); 216 | assert(err == ENOENT); 217 | ti_free(ti); 218 | } 219 | 220 | // Loading a file that exists but isn't a terminfo file causes an error. 221 | void test_non_terminfo_file() { 222 | int err; 223 | ti_terminfo *ti = ti_load("xterm-badfile", &err); 224 | printf("err=%d\n", err); 225 | assert(ti == NULL); 226 | assert(err == TI_ERR_BAD_MAGIC); 227 | ti_free(ti); 228 | } 229 | 230 | int main(void) { 231 | // make stdout line buffered 232 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 233 | 234 | // load terminfo data from our test directory only 235 | setenv("TERMINFO", "./terminfo", 1); 236 | 237 | test_legacy_storage_format(); 238 | test_extended_storage_format(); 239 | test_extended_storage_format_odd_alignment(); 240 | test_minitel1(); 241 | test_missing_file(); 242 | test_non_terminfo_file(); 243 | 244 | return 0; 245 | } 246 | 247 | // vim: noexpandtab 248 | -------------------------------------------------------------------------------- /test/ti_parm_test.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 700 // fileno 2 | 3 | #include "../ti.h" 4 | #include "../ti.c" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main(void) { 12 | // make stdout line buffered 13 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 14 | 15 | size_t n; 16 | char buf[TI_PARM_OUTPUT_MAX]; 17 | 18 | // %% = print literal % 19 | n = ti_parm(buf, "hello %% there %%", 0); 20 | printf("buf: %s\n", buf); 21 | assert(strcmp(buf, "hello % there %") == 0); 22 | assert(n == strlen(buf)); 23 | 24 | // %i = increment first two params by one 25 | // %pn = push param n on stack 26 | // %d = pop int off stack and print 27 | n = ti_parm(buf, "%i%p1%p2%d%d%p3%d", 3, 16, 42, 50); 28 | printf("buf: %s\n", buf); 29 | assert(strcmp(buf, "431750") == 0); 30 | assert(n == strlen(buf)); 31 | 32 | // %'c' = push literal char 33 | // %{n} = push literal int 34 | // %c = pop char and print 35 | n = ti_parm(buf, "%'x'%c%{79}%c", 0); 36 | printf("buf: %s\n", buf); 37 | assert(strcmp(buf, "x79") == 0); 38 | assert(n == strlen(buf)); 39 | 40 | // %'c' = push literal char 41 | // %{n} = push literal int 42 | // %s = pop string and print 43 | n = ti_parm(buf, "%'y'%s%{80}%s", 0); 44 | printf("buf: %s\n", buf); 45 | assert(strcmp(buf, "y80") == 0); 46 | assert(n == strlen(buf)); 47 | 48 | // %{n} = push literal int 49 | // %PV = pop and store static var V 50 | // %gV = recall static var V and push 51 | // %s = pop string, print 52 | n = ti_parm(buf, "%{1234}%PI%gI%s", 0); 53 | printf("buf: %s\n", buf); 54 | assert(strcmp(buf, "1234") == 0); 55 | assert(n == strlen(buf)); 56 | 57 | // test handling recall of unset static vars 58 | n = ti_parm(buf, "%gJ%s", 0); 59 | printf("buf: %s\n", buf); 60 | assert(strcmp(buf, "") == 0); 61 | assert(n == strlen(buf)); 62 | 63 | // %{n} = push literal int 64 | // %Pv = pop and store dynamic var v 65 | // %gv = recall dynamic var v and push 66 | // %s = pop string, print 67 | n = ti_parm(buf, "%{5678}%Pi%gi%s", 0); 68 | printf("buf: %s\n", buf); 69 | assert(strcmp(buf, "5678") == 0); 70 | assert(n == strlen(buf)); 71 | 72 | // test handling of unset dynamic vars 73 | n = ti_parm(buf, "%gj%s", 0); 74 | printf("buf: %s\n", buf); 75 | assert(strcmp(buf, "") == 0); 76 | assert(n == strlen(buf)); 77 | 78 | // %'c' = push literal char 79 | // %l = pop string, push string length 80 | // %d = pop int, print 81 | n = ti_parm(buf, "%'y'%l%d", 0); 82 | printf("buf: %s\n", buf); 83 | assert(strcmp(buf, "1") == 0); 84 | assert(n == strlen(buf)); 85 | 86 | // MATH OPERATORS ================================================ 87 | 88 | // %pn = push param n on stack 89 | // %+ = pop int, pop int, add, push int 90 | n = ti_parm(buf, "%p1%p2%+%d", 2, 40, 2); 91 | printf("buf: %s\n", buf); 92 | assert(strcmp(buf, "42") == 0); 93 | assert(n == strlen(buf)); 94 | 95 | // %pn = push param n on stack 96 | // %- = pop int, pop int, add, push int 97 | n = ti_parm(buf, "%p1%p2%-%d", 2, 40, 2); 98 | printf("buf: %s\n", buf); 99 | assert(strcmp(buf, "38") == 0); 100 | assert(n == strlen(buf)); 101 | 102 | // %pn = push param n on stack 103 | // %* = pop int, pop int, multiply, push int 104 | n = ti_parm(buf, "%p1%p2%*%d", 2, 40, 2); 105 | printf("buf: %s\n", buf); 106 | assert(strcmp(buf, "80") == 0); 107 | assert(n == strlen(buf)); 108 | 109 | // %pn = push param n on stack 110 | // %/ = pop int, pop int, multiply, push int 111 | n = ti_parm(buf, "%p1%p2%/%d", 2, 40, 2); 112 | printf("buf: %s\n", buf); 113 | assert(strcmp(buf, "20") == 0); 114 | assert(n == strlen(buf)); 115 | 116 | // %pn = push param n on stack 117 | // %m = pop int, pop int, mod, push int 118 | n = ti_parm(buf, "%p1%p2%m%d", 2, 40, 7); 119 | printf("buf: %s\n", buf); 120 | assert(strcmp(buf, "5") == 0); 121 | assert(n == strlen(buf)); 122 | 123 | // BITWISE OPERATORS ============================================= 124 | 125 | // %pn = push param n on stack 126 | // %& = pop int, pop int, binary and, push int 127 | // %x = pop int, print hex lower 128 | n = ti_parm(buf, "%p1%p2%&%x", 2, 0xff, 0x0a); 129 | printf("buf: %s\n", buf); 130 | assert(strcmp(buf, "a") == 0); 131 | assert(n == strlen(buf)); 132 | 133 | // %pn = push param n on stack 134 | // %| = pop int, pop int, binary or, push int 135 | // %X = pop int, print hex upper 136 | n = ti_parm(buf, "%p1%p2%|%X", 2, 0xf1, 0x0a); 137 | printf("buf: %s\n", buf); 138 | assert(strcmp(buf, "FB") == 0); 139 | assert(n == strlen(buf)); 140 | 141 | // %pn = push param n on stack 142 | // %^ = pop int, pop int, xor, push int 143 | // %x = pop int, print hex upper 144 | n = ti_parm(buf, "%p1%p2%^%x", 2, 0xf1, 0x0a); 145 | printf("buf: %s\n", buf); 146 | assert(strcmp(buf, "fb") == 0); 147 | assert(n == strlen(buf)); 148 | 149 | // %pn = push param n on stack 150 | // %~ = pop int, bit complement, push int 151 | // %d = pop int, print hex upper 152 | n = ti_parm(buf, "%p1%~%d", 1, 5); 153 | printf("buf: %s\n", buf); 154 | assert(strcmp(buf, "-6") == 0); 155 | assert(n == strlen(buf)); 156 | 157 | // LOGICAL OPERATORS ============================================= 158 | 159 | // %pn = push param n on stack 160 | // %O = pop int, pop int, logical or 161 | // %x = pop int, print hex 162 | n = ti_parm(buf, "%p1%p2%O%x", 2, 10, 0); 163 | printf("buf: %s\n", buf); 164 | assert(strcmp(buf, "1") == 0); 165 | assert(n == strlen(buf)); 166 | 167 | n = ti_parm(buf, "%p1%p2%O%x", 2, 0, 0); 168 | printf("buf: %s\n", buf); 169 | assert(strcmp(buf, "0") == 0); 170 | assert(n == strlen(buf)); 171 | 172 | // %pn = push param n on stack 173 | // %O = pop int, pop int, logical and 174 | // %x = pop int, print hex 175 | n = ti_parm(buf, "%p1%p2%A%x", 2, 10, 0); 176 | printf("buf: %s\n", buf); 177 | assert(strcmp(buf, "0") == 0); 178 | assert(n == strlen(buf)); 179 | 180 | n = ti_parm(buf, "%p1%p2%O%x", 2, 10, 10); 181 | printf("buf: %s\n", buf); 182 | assert(strcmp(buf, "1") == 0); 183 | assert(n == strlen(buf)); 184 | 185 | // %pn = push param n on stack 186 | // %! = pop int, logical not, push bool 187 | // %x = pop int, print hex 188 | n = ti_parm(buf, "%p1%!%x", 1, 5); 189 | printf("buf: %s\n", buf); 190 | assert(strcmp(buf, "0") == 0); 191 | assert(n == strlen(buf)); 192 | 193 | n = ti_parm(buf, "%p1%!%x", 1, 0); 194 | printf("buf: %s\n", buf); 195 | assert(strcmp(buf, "1") == 0); 196 | assert(n == strlen(buf)); 197 | 198 | // %pn = push param n on stack 199 | // %= = pop int, compare, push bool 200 | // %d = pop int, print decimal 201 | n = ti_parm(buf, "%p1%p2%=%d", 2, 5, 5); 202 | printf("buf: %s\n", buf); 203 | assert(strcmp(buf, "1") == 0); 204 | assert(n == strlen(buf)); 205 | 206 | n = ti_parm(buf, "%p1%p2%=%d", 2, 5, 4); 207 | printf("buf: %s\n", buf); 208 | assert(strcmp(buf, "0") == 0); 209 | assert(n == strlen(buf)); 210 | 211 | // %pn = push param n on stack 212 | // %> = pop int, greater than, push bool 213 | // %d = pop int, print decimal 214 | n = ti_parm(buf, "%p1%p2%>%d", 2, 10, 5); 215 | printf("buf: %s\n", buf); 216 | assert(strcmp(buf, "1") == 0); 217 | assert(n == strlen(buf)); 218 | 219 | n = ti_parm(buf, "%p1%p2%>%d", 2, 5, 10); 220 | printf("buf: %s\n", buf); 221 | assert(strcmp(buf, "0") == 0); 222 | assert(n == strlen(buf)); 223 | 224 | // %pn = push param n on stack 225 | // %> = pop int, less than, push bool 226 | // %d = pop int, print decimal 227 | n = ti_parm(buf, "%p1%p2%<%d", 2, 10, 5); 228 | printf("buf: %s\n", buf); 229 | assert(strcmp(buf, "0") == 0); 230 | assert(n == strlen(buf)); 231 | 232 | n = ti_parm(buf, "%p1%p2%<%d", 2, 5, 10); 233 | printf("buf: %s\n", buf); 234 | assert(strcmp(buf, "1") == 0); 235 | assert(n == strlen(buf)); 236 | 237 | // FORMATTED OUTPUT OPERATORS ==================================== 238 | 239 | // %i = increment first two params by one 240 | // %pn = push param n on stack 241 | // %Fx = pop int off stack and print formatted 242 | n = ti_parm(buf, "%p1%:+03x", 1, 76); 243 | printf("buf: %s\n", buf); 244 | assert(strcmp(buf, "04c") == 0); 245 | assert(n == strlen(buf)); 246 | 247 | n = ti_parm(buf, "%p1%:-02X", 1, 76); 248 | printf("buf: %s\n", buf); 249 | assert(strcmp(buf, "4C") == 0); 250 | assert(n == strlen(buf)); 251 | 252 | n = ti_parm(buf, "%p1%04o", 1, 32); 253 | printf("buf: %s\n", buf); 254 | assert(strcmp(buf, "0040") == 0); 255 | assert(n == strlen(buf)); 256 | 257 | n = ti_parm(buf, "%'z'% 4s", 0); 258 | printf("buf: %s\n", buf); 259 | assert(strcmp(buf, " z") == 0); 260 | assert(n == strlen(buf)); 261 | 262 | n = ti_parm(buf, "%'z'%:+ 4s", 0); 263 | printf("buf: %s\n", buf); 264 | assert(strcmp(buf, " z") == 0); 265 | assert(n == strlen(buf)); 266 | 267 | n = ti_parm(buf, "%'z'%:- 4s", 0); 268 | printf("buf: %s\n", buf); 269 | assert(strcmp(buf, "z ") == 0); 270 | assert(n == strlen(buf)); 271 | 272 | // not sure why . is allowed since there's no float fmt code 273 | n = ti_parm(buf, "%p1%1.1d", 1, 32); 274 | printf("buf: %s\n", buf); 275 | assert(strcmp(buf, "32") == 0); 276 | assert(n == strlen(buf)); 277 | 278 | 279 | // IF/THEN/ELSE ================================================== 280 | 281 | // %? = if 282 | // %p1 = push param 1 on stack 283 | // %t = then 284 | // %e = else 285 | // %; = endif 286 | n = ti_parm(buf, "%?%p1%tif%eelse%;", 1, 1); 287 | printf("buf: %s\n", buf); 288 | assert(strcmp(buf, "if") == 0); 289 | assert(n == strlen(buf)); 290 | 291 | // if p1 then "if" else "else" endif 292 | n = ti_parm(buf, "%?%p1%tif%eelse%;", 1, 0); 293 | printf("buf: %s\n", buf); 294 | assert(strcmp(buf, "else") == 0); 295 | assert(n == strlen(buf)); 296 | 297 | // if p1 then if p2 then "if if" else "else" endif endif 298 | n = ti_parm(buf, "%?%p1%t%?%p2%tif if%eelse%;%;", 2, 1, 1); 299 | printf("buf: %s\n", buf); 300 | assert(strcmp(buf, "if if") == 0); 301 | assert(n == strlen(buf)); 302 | 303 | // if p1 then if p2 then "if if" else "else" endif endif 304 | n = ti_parm(buf, "%?%p1%t%?%p2%tif if%eelse%;%;", 2, 0, 1); 305 | printf("buf: %s\n", buf); 306 | assert(strcmp(buf, "") == 0); 307 | assert(n == strlen(buf)); 308 | 309 | // if p1 then "if" else if p2 then "else if" endif endif 310 | n = ti_parm(buf, "%?%p1%tif%e%?%p2%telse if%;%;", 2, 1, 0); 311 | printf("buf: %s\n", buf); 312 | assert(strcmp(buf, "if") == 0); 313 | assert(n == strlen(buf)); 314 | 315 | // if p1 then "if" else if p2 then "else if" endif endif 316 | n = ti_parm(buf, "%?%p1%tif%e%?%p2%telse if%;%;", 2, 0, 1); 317 | printf("buf: %s\n", buf); 318 | assert(strcmp(buf, "else if") == 0); 319 | assert(n == strlen(buf)); 320 | 321 | // if p1 then "if" else if p2 then "else if" endif endif 322 | n = ti_parm(buf, "%?%p1%tif%e%?%p2%telse if%;%;", 2, 0, 0); 323 | printf("buf: %s\n", buf); 324 | assert(strcmp(buf, "") == 0); 325 | assert(n == strlen(buf)); 326 | 327 | return 0; 328 | } 329 | 330 | // vim: noexpandtab 331 | -------------------------------------------------------------------------------- /test/tkbd_desc_test.c: -------------------------------------------------------------------------------- 1 | #include "../tkbd.h" 2 | #include "../tkbd.c" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(void) 10 | { 11 | // make stdout line buffered 12 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 13 | 14 | char buf[64]; 15 | struct tkbd_seq seq = { .type = TKBD_KEY, .key = TKBD_KEY_A }; 16 | 17 | int n = tkbd_desc(buf, sizeof(buf), &seq); 18 | printf("n=%d, buf=%s\n", n, buf); 19 | assert(strcmp("A", buf) == 0); 20 | assert(n == (int)strlen(buf)); 21 | 22 | seq.mod = TKBD_MOD_CTRL; 23 | n = tkbd_desc(buf, sizeof(buf), &seq); 24 | printf("n=%d, buf=%s\n", n, buf); 25 | assert(strcmp("Ctrl+A", buf) == 0); 26 | assert(n == (int)strlen(buf)); 27 | 28 | seq.mod |= TKBD_MOD_SHIFT|TKBD_MOD_ALT; 29 | n = tkbd_desc(buf, sizeof(buf), &seq); 30 | printf("n=%d, buf=%s\n", n, buf); 31 | assert(strcmp("Ctrl+Shift+Alt+A", buf) == 0); 32 | assert(n == (int)strlen(buf)); 33 | 34 | seq.key = TKBD_KEY_F12; 35 | n = tkbd_desc(buf, sizeof(buf), &seq); 36 | printf("n=%d, buf=%s\n", n, buf); 37 | assert(strcmp("Ctrl+Shift+Alt+F12", buf) == 0); 38 | assert(n == (int)strlen(buf)); 39 | 40 | seq.key = TKBD_KEY_F20; 41 | n = tkbd_desc(buf, sizeof(buf), &seq); 42 | printf("n=%d, buf=%s\n", n, buf); 43 | assert(strcmp("Ctrl+Shift+Alt+F20", buf) == 0); 44 | assert(n == (int)strlen(buf)); 45 | 46 | seq.key = TKBD_KEY_PGUP; 47 | n = tkbd_desc(buf, sizeof(buf), &seq); 48 | printf("n=%d, buf=%s\n", n, buf); 49 | assert(strcmp("Ctrl+Shift+Alt+PgUp", buf) == 0); 50 | assert(n == (int)strlen(buf)); 51 | 52 | seq.key = TKBD_KEY_HOME; 53 | n = tkbd_desc(buf, sizeof(buf), &seq); 54 | printf("n=%d, buf=%s\n", n, buf); 55 | assert(strcmp("Ctrl+Shift+Alt+HOME", buf) == 0); 56 | assert(n == (int)strlen(buf)); 57 | 58 | seq.key = TKBD_KEY_ENTER; 59 | n = tkbd_desc(buf, sizeof(buf), &seq); 60 | printf("n=%d, buf=%s\n", n, buf); 61 | assert(strcmp("Ctrl+Shift+Alt+Enter", buf) == 0); 62 | assert(n == (int)strlen(buf)); 63 | 64 | seq.key = TKBD_KEY_ESC; 65 | n = tkbd_desc(buf, sizeof(buf), &seq); 66 | printf("n=%d, buf=%s\n", n, buf); 67 | assert(strcmp("Ctrl+Shift+Alt+ESC", buf) == 0); 68 | assert(n == (int)strlen(buf)); 69 | 70 | // returns 0 when not a key sequence 71 | seq.type = TKBD_MOUSE; 72 | n = tkbd_desc(buf, sizeof(buf), &seq); 73 | printf("n=%d, buf=%s\n", n, buf); 74 | assert(n == 0); 75 | 76 | return 0; 77 | } 78 | 79 | // vim: noexpandtab 80 | -------------------------------------------------------------------------------- /test/tkbd_parse_test.c: -------------------------------------------------------------------------------- 1 | #include "../tkbd.h" 2 | #include "../tkbd.c" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void test_parse_seq_params(void) 10 | { 11 | int parms[2] = {0}; 12 | int n; 13 | 14 | n = parse_seq_params(parms, 2, "123"); 15 | printf("n = %d, parms[0] = %d, parms[1] = %d\n", n, parms[0], parms[1]); 16 | assert(n == 1); 17 | assert(parms[0] == 123); 18 | assert(parms[1] == 0); 19 | 20 | memset(&parms, 0, sizeof(parms)); 21 | n = parse_seq_params(parms, 2, "123;456;789;"); 22 | printf("n = %d, parms[0] = %d, parms[1] = %d\n", n, parms[0], parms[1]); 23 | assert(n == 2); 24 | assert(parms[0] == 123); 25 | assert(parms[1] == 456); 26 | } 27 | 28 | static void test_parse_char_seq(void) 29 | { 30 | int n; 31 | char buf[2] = {0}; 32 | 33 | for (char c = 'a'; c <= 'z'; c++) { 34 | struct tkbd_seq seq = {0}; 35 | buf[0] = c; 36 | n = parse_char_seq(&seq, buf, 1); 37 | printf("n=%d expect key=0x%02x, got key=0x%02x\n", 38 | n, (int)c, seq.key); 39 | assert(n == 1); 40 | assert(seq.type == TKBD_KEY); 41 | assert(seq.key == TKBD_KEY_A + (c - 'a')); 42 | assert(seq.mod == 0); 43 | assert(seq.ch == (uint32_t)c); 44 | assert(seq.data[0] == c && seq.data[1] == '\0'); 45 | } 46 | 47 | for (char c = 'A'; c <= 'Z'; c++) { 48 | struct tkbd_seq seq = {0}; 49 | buf[0] = c; 50 | n = parse_char_seq(&seq, buf, 1); 51 | printf("n=%d expect key=0x%02x, got key=0x%02x\n", 52 | n, (int)c, seq.key); 53 | assert(n == 1); 54 | assert(seq.type == TKBD_KEY); 55 | assert(seq.key == c); 56 | assert(seq.mod == TKBD_MOD_SHIFT); 57 | assert(seq.ch == (uint32_t)c); 58 | assert(seq.data[0] == c && seq.data[1] == '\0'); 59 | } 60 | 61 | for (char c = '0'; c <= '9'; c++) { 62 | struct tkbd_seq seq = {0}; 63 | buf[0] = c; 64 | n = parse_char_seq(&seq, buf, 1); 65 | printf("n=%d expect key=0x%02x, got key=0x%02x\n", 66 | n, (int)c, seq.key); 67 | assert(n == 1); 68 | assert(seq.type == TKBD_KEY); 69 | assert(seq.key == c); 70 | assert(seq.mod == 0); 71 | assert(seq.ch == (uint32_t)c); 72 | assert(seq.data[0] == c && seq.data[1] == '\0'); 73 | } 74 | 75 | char const * const punc1 = " `-=[]\\;',./"; 76 | for (int i = 0; punc1[i]; i++) { 77 | char c = punc1[i]; 78 | buf[0] = c; 79 | struct tkbd_seq seq = {0}; 80 | n = parse_char_seq(&seq, buf, 1); 81 | printf("n=%d expect key=0x%02x, got key=0x%02x\n", 82 | n, (int)c, seq.key); 83 | assert(n == 1); 84 | assert(seq.type == TKBD_KEY); 85 | assert(seq.key == c); 86 | assert(seq.mod == 0); 87 | assert(seq.ch == (uint32_t)c); 88 | assert(seq.data[0] == c && seq.data[1] == '\0'); 89 | } 90 | 91 | char const * const punc2 = "~!@#$%^&*()_+{}|:\"<>?"; 92 | for (int i = 0; punc2[i]; i++) { 93 | char c = punc2[i]; 94 | buf[0] = c; 95 | struct tkbd_seq seq = {0}; 96 | n = parse_char_seq(&seq, buf, 1); 97 | printf("n=%d expect key=0x%02x, got key=0x%02x\n", 98 | n, (int)c, seq.key); 99 | assert(n == 1); 100 | assert(seq.type == TKBD_KEY); 101 | assert(seq.key == c); 102 | assert(seq.mod == TKBD_MOD_SHIFT); 103 | assert(seq.ch == (uint32_t)c); 104 | assert(seq.data[0] == c && seq.data[1] == '\0'); 105 | } 106 | 107 | // parsing non control sequences returns zero 108 | struct tkbd_seq seq0 = {0}; 109 | char *bufno = "ABCD"; 110 | n = parse_ctrl_seq(&seq0, bufno, strlen(bufno)); 111 | printf("n = %d\n", n); 112 | assert(n == 0); 113 | assert(seq0.key == 0); 114 | assert(seq0.mod == 0); 115 | assert(seq0.ch == 0); 116 | assert(seq0.data[0] == 0); 117 | } 118 | 119 | static void test_parse_ctrl_seq(void) 120 | { 121 | int n; 122 | 123 | struct key { 124 | char *seq; 125 | int key; 126 | int mod; 127 | }; 128 | 129 | struct key keys[] = { 130 | { "\x1C", TKBD_KEY_BACKSLASH, TKBD_MOD_CTRL }, 131 | { "\x1D", TKBD_KEY_BRACKET_RIGHT, TKBD_MOD_CTRL }, 132 | { "\x08", TKBD_KEY_BACKSPACE, TKBD_MOD_NONE }, 133 | { "\x09", TKBD_KEY_TAB, TKBD_MOD_NONE }, 134 | { "\x0A", TKBD_KEY_ENTER, TKBD_MOD_NONE }, 135 | { "\x00", TKBD_KEY_2, TKBD_MOD_CTRL }, 136 | { "\x7F", TKBD_KEY_BACKSPACE2, TKBD_MOD_NONE }, 137 | { "\x01", TKBD_KEY_A, TKBD_MOD_CTRL }, 138 | { "\x06", TKBD_KEY_F, TKBD_MOD_CTRL }, 139 | { "\x1b", TKBD_KEY_ESC, TKBD_MOD_NONE }, 140 | }; 141 | 142 | for (int i = 0; i < (int)ARRAYLEN(keys); i++) { 143 | struct tkbd_seq seq = {0}; 144 | struct key *k = &keys[i]; 145 | n = parse_ctrl_seq(&seq, k->seq, 1); 146 | printf("n=%d expect key=0x%x, got key=0x%x\n", 147 | n, k->key, seq.key); 148 | assert(n == 1); 149 | assert(seq.type == TKBD_KEY); 150 | assert(seq.key == k->key); 151 | assert(seq.mod == k->mod); 152 | assert(seq.ch == (uint32_t)k->seq[0]); 153 | assert(k->seq[0] == seq.data[0] && seq.data[1] == '\0'); 154 | } 155 | 156 | // parsing non control sequences returns zero 157 | struct tkbd_seq seq0 = {0}; 158 | char *buf = "ABCD"; 159 | n = parse_ctrl_seq(&seq0, buf, strlen(buf)); 160 | printf("n = %d\n", n); 161 | assert(n == 0); 162 | assert(seq0.key == 0); 163 | assert(seq0.mod == 0); 164 | assert(seq0.ch == 0); 165 | assert(seq0.data[0] == 0); 166 | } 167 | 168 | static void test_parse_alt_seq(void) 169 | { 170 | int n; 171 | 172 | struct key { 173 | char *seq; 174 | int key; 175 | int mod; 176 | }; 177 | 178 | struct key keys[] = { 179 | { "\033A", TKBD_KEY_A, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 180 | { "\033M", TKBD_KEY_M, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 181 | { "\033Z", TKBD_KEY_Z, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 182 | { "\033a", TKBD_KEY_A, TKBD_MOD_ALT }, 183 | { "\033m", TKBD_KEY_M, TKBD_MOD_ALT }, 184 | { "\033z", TKBD_KEY_Z, TKBD_MOD_ALT }, 185 | { "\0330", TKBD_KEY_0, TKBD_MOD_ALT }, 186 | { "\0339", TKBD_KEY_9, TKBD_MOD_ALT }, 187 | { "\033;", TKBD_KEY_SEMICOLON, TKBD_MOD_ALT }, 188 | { "\033>", TKBD_KEY_GT, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 189 | 190 | { "\033\x1C", TKBD_KEY_BACKSLASH, TKBD_MOD_CTRL|TKBD_MOD_ALT }, 191 | { "\033\x08", TKBD_KEY_BACKSPACE, TKBD_MOD_NONE|TKBD_MOD_ALT }, 192 | { "\033\x09", TKBD_KEY_TAB, TKBD_MOD_NONE|TKBD_MOD_ALT }, 193 | { "\033\x0A", TKBD_KEY_ENTER, TKBD_MOD_NONE|TKBD_MOD_ALT }, 194 | { "\033\x00", TKBD_KEY_2, TKBD_MOD_CTRL|TKBD_MOD_ALT }, 195 | { "\033\x7F", TKBD_KEY_BACKSPACE2, TKBD_MOD_NONE|TKBD_MOD_ALT }, 196 | { "\033\x01", TKBD_KEY_A, TKBD_MOD_CTRL|TKBD_MOD_ALT }, 197 | { "\033\x06", TKBD_KEY_F, TKBD_MOD_CTRL|TKBD_MOD_ALT }, 198 | { "\033\033", TKBD_KEY_ESC, TKBD_MOD_ALT }, 199 | 200 | { "\033\033[A", TKBD_KEY_UP, TKBD_MOD_ALT }, 201 | { "\033\x08[24~", TKBD_KEY_F12, TKBD_MOD_ALT }, 202 | }; 203 | 204 | for (int i = 0; i < (int)ARRAYLEN(keys); i++) { 205 | struct tkbd_seq seq = {0}; 206 | struct key *k = &keys[i]; 207 | n = parse_alt_seq(&seq, k->seq, 2); 208 | printf("n = %d, key = 0x%x\n", n, k->key); 209 | assert(n == 2); 210 | assert(seq.type == TKBD_KEY); 211 | assert(seq.mod == k->mod); 212 | assert(seq.ch == (uint32_t)seq.data[1]); 213 | assert(seq.data[0] == '\033'); 214 | assert(seq.data[1] == k->seq[1]); 215 | assert(seq.data[2] == '\0'); 216 | } 217 | 218 | // parsing non alt sequences 219 | struct tkbd_seq seq0 = {0}; 220 | char *buf = "ABCD"; 221 | n = parse_alt_seq(&seq0, buf, strlen(buf)); 222 | printf("n = %d\n", n); 223 | assert(n == 0); 224 | assert(seq0.key == 0); 225 | assert(seq0.mod == 0); 226 | assert(seq0.ch == 0); 227 | assert(seq0.data[0] == 0); 228 | } 229 | 230 | static void test_parse_special_seq(void) 231 | { 232 | struct tkbd_seq seq; 233 | char buf[256] = {0}; 234 | int n; 235 | 236 | // stop at null character 237 | n = parse_special_seq(&seq, buf, 10); 238 | assert(n == 0); 239 | 240 | // don't read past buf len 241 | strcpy(buf, "\033[A"); 242 | n = parse_special_seq(&seq, buf, 0); 243 | assert(n == 0); 244 | 245 | // read one sequence and stop 246 | strcpy(buf, "\033[A\033[B"); 247 | memset(&seq, 0, sizeof(seq)); 248 | n = parse_special_seq(&seq, buf, strlen(buf)); 249 | assert(n == strlen("\033[A")); 250 | assert(seq.type == TKBD_KEY); 251 | assert(seq.key == TKBD_KEY_UP); 252 | assert(strcmp(seq.data, "\033[A") == 0); 253 | 254 | // parses CSI sequence 255 | strcpy(buf, "\033[A"); 256 | memset(&seq, 0, sizeof(seq)); 257 | n = parse_special_seq(&seq, buf, strlen(buf)); 258 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 259 | assert((size_t)n == strlen(buf)); 260 | assert(seq.type == TKBD_KEY); 261 | assert(seq.key == TKBD_KEY_UP); 262 | assert(seq.mod == TKBD_MOD_NONE); 263 | assert(strcmp(seq.data, buf) == 0); 264 | 265 | // parses SS3 sequence 266 | strcpy(buf, "\033OQ"); 267 | memset(&seq, 0, sizeof(seq)); 268 | n = parse_special_seq(&seq, buf, strlen(buf)); 269 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 270 | assert((size_t)n == strlen(buf)); 271 | assert(seq.type == TKBD_KEY); 272 | assert(seq.key == TKBD_KEY_F2); 273 | assert(seq.mod == TKBD_MOD_NONE); 274 | assert(strcmp(seq.data, buf) == 0); 275 | 276 | // parses mod parameters in ansi style sequence (form 1) 277 | strcpy(buf, "\033[7A"); 278 | memset(&seq, 0, sizeof(seq)); 279 | n = parse_special_seq(&seq, buf, strlen(buf)); 280 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 281 | assert((size_t)n == strlen(buf)); 282 | assert(seq.type == TKBD_KEY); 283 | assert(seq.key == TKBD_KEY_UP); 284 | assert(seq.mod == (TKBD_MOD_CTRL|TKBD_MOD_ALT)); 285 | assert(strcmp(seq.data, buf) == 0); 286 | 287 | // parses mod parameters in ansi style sequence (form 2) 288 | strcpy(buf, "\033[1;7A"); 289 | memset(&seq, 0, sizeof(seq)); 290 | n = parse_special_seq(&seq, buf, strlen(buf)); 291 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 292 | assert((size_t)n == strlen(buf)); 293 | assert(seq.type == TKBD_KEY); 294 | assert(seq.key == TKBD_KEY_UP); 295 | assert(seq.mod == (TKBD_MOD_CTRL|TKBD_MOD_ALT)); 296 | assert(strcmp(seq.data, buf) == 0); 297 | 298 | // parses mod parameters in DECFNK style sequence 299 | strcpy(buf, "\033[24;2~"); 300 | memset(&seq, 0, sizeof(seq)); 301 | n = parse_special_seq(&seq, buf, strlen(buf)); 302 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 303 | assert((size_t)n == strlen(buf)); 304 | assert(seq.type == TKBD_KEY); 305 | assert(seq.key == TKBD_KEY_F12); 306 | assert(seq.mod == TKBD_MOD_SHIFT); 307 | assert(strcmp(seq.data, buf) == 0); 308 | 309 | // handles out of range DECFNK sequences 310 | strcpy(buf, "\033[100;2~"); 311 | memset(&seq, 0, sizeof(seq)); 312 | n = parse_special_seq(&seq, buf, strlen(buf)); 313 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 314 | assert((size_t)n == strlen(buf)); 315 | assert(seq.type == TKBD_KEY); 316 | assert(seq.key == TKBD_KEY_UNKNOWN); 317 | assert(seq.mod == TKBD_MOD_SHIFT); 318 | assert(strcmp(seq.data, buf) == 0); 319 | 320 | // handles unmapped ANSI style sequences 321 | strcpy(buf, "\033[2Y"); 322 | memset(&seq, 0, sizeof(seq)); 323 | n = parse_special_seq(&seq, buf, strlen(buf)); 324 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 325 | assert((size_t)n == strlen(buf)); 326 | assert(seq.type == TKBD_KEY); 327 | assert(seq.key == TKBD_KEY_UNKNOWN); 328 | assert(seq.mod == TKBD_MOD_SHIFT); 329 | assert(strcmp(seq.data, buf) == 0); 330 | 331 | // handles out of table ANSI style sequences 332 | strcpy(buf, "\033[31;45;33@"); 333 | memset(&seq, 0, sizeof(seq)); 334 | n = parse_special_seq(&seq, buf, strlen(buf)); 335 | printf("n = %d, key = %d, mod = %d\n", n, seq.key, seq.mod); 336 | assert((size_t)n == strlen(buf)); 337 | assert(seq.type == TKBD_KEY); 338 | assert(seq.key == TKBD_KEY_UNKNOWN); 339 | assert(seq.mod == TKBD_MOD_NONE); 340 | assert(strcmp(seq.data, buf) == 0); 341 | 342 | // try to overflow the seq->data buffer 343 | assert(TKBD_SEQ_MAX == 32 && "update overflow test below"); 344 | strcpy(buf, "\033[2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Y"); 345 | memset(&seq, 0, sizeof(seq)); 346 | n = parse_special_seq(&seq, buf, strlen(buf)); 347 | printf("n = %d, key = %d, mod = %d, seq = %s\n", 348 | n, seq.key, seq.mod, seq.data); 349 | assert((size_t)n == strlen(buf)); 350 | assert(seq.type == TKBD_KEY); 351 | assert(seq.key == TKBD_KEY_UNKNOWN); 352 | assert(seq.mod == TKBD_MOD_SHIFT); 353 | assert(memcmp(seq.data, buf, seq.len) == 0); 354 | } 355 | 356 | static void test_parse() 357 | { 358 | int n; 359 | 360 | struct key { 361 | char *seq; 362 | int key; 363 | int mod; 364 | }; 365 | 366 | struct key keys[] = { 367 | // parse_char_seq 368 | { "a", TKBD_KEY_A, TKBD_MOD_NONE }, 369 | { "z", TKBD_KEY_Z, TKBD_MOD_NONE }, 370 | { "A", TKBD_KEY_A, TKBD_MOD_SHIFT }, 371 | { "Z", TKBD_KEY_Z, TKBD_MOD_SHIFT }, 372 | { "`", TKBD_KEY_BACKTICK, TKBD_MOD_NONE }, 373 | { "/", TKBD_KEY_SLASH, TKBD_MOD_NONE }, 374 | 375 | // parse_ctrl_seq 376 | { "\033", TKBD_KEY_ESC, TKBD_MOD_NONE }, 377 | { "\x01", TKBD_KEY_A, TKBD_MOD_CTRL }, 378 | { "\x1A", TKBD_KEY_Z, TKBD_MOD_CTRL }, 379 | { "\x09", TKBD_KEY_TAB, TKBD_MOD_NONE }, 380 | { "\x0A", TKBD_KEY_ENTER, TKBD_MOD_NONE }, 381 | 382 | // parse_alt_seq 383 | { "\033A", TKBD_KEY_A, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 384 | { "\033Z", TKBD_KEY_Z, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 385 | { "\033a", TKBD_KEY_A, TKBD_MOD_ALT }, 386 | { "\033z", TKBD_KEY_Z, TKBD_MOD_ALT }, 387 | { "\0330", TKBD_KEY_0, TKBD_MOD_ALT }, 388 | { "\0339", TKBD_KEY_9, TKBD_MOD_ALT }, 389 | { "\033;", TKBD_KEY_SEMICOLON, TKBD_MOD_ALT }, 390 | { "\033>", TKBD_KEY_GT, TKBD_MOD_SHIFT|TKBD_MOD_ALT }, 391 | { "\033\x09", TKBD_KEY_TAB, TKBD_MOD_ALT }, 392 | { "\033\x0A", TKBD_KEY_ENTER, TKBD_MOD_ALT }, 393 | { "\033\x01", TKBD_KEY_A, TKBD_MOD_CTRL|TKBD_MOD_ALT }, 394 | { "\033\033", TKBD_KEY_ESC, TKBD_MOD_ALT }, 395 | 396 | // parse_special_seq 397 | { "\033[A", TKBD_KEY_UP, TKBD_MOD_NONE }, 398 | { "\033[1A", TKBD_KEY_UP, TKBD_MOD_NONE }, 399 | { "\033[1;2A", TKBD_KEY_UP, TKBD_MOD_SHIFT }, 400 | { "\033[1;8A", TKBD_KEY_UP, TKBD_MOD_SHIFT| 401 | TKBD_MOD_ALT| 402 | TKBD_MOD_CTRL }, 403 | { "\033[24;2~", TKBD_KEY_F12, TKBD_MOD_SHIFT }, 404 | 405 | // Shift+Tab special case 406 | { "\033[Z", TKBD_KEY_TAB, TKBD_MOD_SHIFT }, 407 | 408 | // linux term special cases 409 | { "\033[[A", TKBD_KEY_F1, TKBD_MOD_NONE }, 410 | { "\033[[E", TKBD_KEY_F5, TKBD_MOD_NONE }, 411 | }; 412 | 413 | for (int i = 0; i < (int)ARRAYLEN(keys); i++) { 414 | struct tkbd_seq seq = {0}; 415 | struct key k = keys[i]; 416 | n = tkbd_parse(&seq, k.seq, strlen(k.seq)); 417 | printf("n = %d, seq=%s, expect key=%x, mod=%x; got key=%x, mod=%x\n", 418 | n, k.seq, k.key, k.mod, seq.key, seq.mod); 419 | assert(n == (int)strlen(k.seq)); 420 | assert(seq.type == TKBD_KEY); 421 | assert(seq.mod == k.mod); 422 | assert(seq.len == (size_t)n); 423 | assert(memcmp(seq.data, k.seq, seq.len) == 0); 424 | } 425 | } 426 | 427 | int main(void) 428 | { 429 | // make stdout line buffered 430 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 431 | 432 | test_parse_seq_params(); 433 | test_parse_char_seq(); 434 | test_parse_ctrl_seq(); 435 | test_parse_alt_seq(); 436 | test_parse_special_seq(); 437 | test_parse(); 438 | 439 | return 0; 440 | } 441 | 442 | // vim: noexpandtab 443 | -------------------------------------------------------------------------------- /test/tkbd_stresc_test.c: -------------------------------------------------------------------------------- 1 | #include "../tkbd.h" 2 | #include "../tkbd.c" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(void) 10 | { 11 | // make stdout line buffered 12 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 13 | 14 | // example of keyboard escape sequence 15 | char buf[128] = {0}; 16 | char *s = "\033[21;1~"; 17 | char *expect = "\\e[21;1~"; 18 | int n = tkbd_stresc(buf, s, strlen(s)); 19 | printf("n=%d, buf=%s\n", n, buf); 20 | assert(n == (int)strlen(s)+1); 21 | assert(strcmp(buf, expect) == 0); 22 | 23 | 24 | // these special characters are printed with lettered escape codes 25 | s = "\0\t\r\n\\"; 26 | expect = "\\0\\t\\r\\n\\\\"; 27 | n = tkbd_stresc(buf, s, 5); 28 | printf("n=%d, buf=%s\n", n, buf); 29 | assert(n == 10); 30 | assert(strcmp(buf, expect) == 0); 31 | 32 | 33 | // all other non-printable characters are printed in octal 34 | s = "\001\002\010\016\x7f"; 35 | expect = "\\001\\002\\010\\016\\177"; 36 | n = tkbd_stresc(buf, s, strlen(s)); 37 | printf("n=%d, buf=%s\n", n, buf); 38 | assert(n == (int)strlen(s)*4); 39 | assert(strcmp(buf, expect) == 0); 40 | 41 | return 0; 42 | } 43 | 44 | // vim: noexpandtab 45 | -------------------------------------------------------------------------------- /test/utf8_test.c: -------------------------------------------------------------------------------- 1 | #include "../utf8.c" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static void test_utf8_len(void) 8 | { 9 | // U+00 - U+7F ascii range 10 | assert(utf8_seq_len('A') == 1); 11 | assert(utf8_seq_len('\0') == 1); 12 | assert(utf8_seq_len('\x7f') == 1); 13 | 14 | // U+80 - U+07FF range 15 | assert(utf8_seq_len('\xc2') == 2); 16 | assert(utf8_seq_len('\xdf') == 2); 17 | 18 | // U+0800 - U+FFFF range 19 | assert(utf8_seq_len('\xe0') == 3); 20 | assert(utf8_seq_len('\xef') == 3); 21 | 22 | // U+10000 - U+1FFFF range 23 | assert(utf8_seq_len('\xf0') == 4); 24 | assert(utf8_seq_len('\xf7') == 4); 25 | 26 | // illegal leading bytes 27 | assert(utf8_seq_len('\x80') == 0); 28 | assert(utf8_seq_len('\xbf') == 0); 29 | assert(utf8_seq_len('\xc1') == 0); 30 | assert(utf8_seq_len('\xf8') == 0); 31 | assert(utf8_seq_len('\xfb') == 0); 32 | assert(utf8_seq_len('\xfc') == 0); 33 | assert(utf8_seq_len('\xfd') == 0); 34 | assert(utf8_seq_len('\xfe') == 0); 35 | assert(utf8_seq_len('\xff') == 0); 36 | } 37 | 38 | struct t { 39 | char *seq; 40 | uint32_t code; 41 | }; 42 | 43 | static const struct t tests[] = { 44 | { "A", 'A' }, 45 | { "\x01", 0x0001 }, 46 | { "\x7f", 0x007f }, 47 | 48 | { "\xc2\x80", 0x0080 }, // € 49 | { "\xc2\xa9", 0x00A9 }, // © 50 | { "\xca\xb0", 0x02B0 }, // ʰ 51 | { "\xcd\xb0", 0x0370 }, // Ͱ 52 | { "\xd0\x84", 0x0404 }, // Є 53 | { "\xd4\xb1", 0x0531 }, // Ա 54 | 55 | { "\xe0\xa4\x84", 0x0904 }, // ऄ 56 | { "\xe1\x82\xa0", 0x10A0 }, // Ⴀ 57 | { "\xe2\x86\x88", 0x2188 }, // ↈ 58 | { "\xe3\x80\x84", 0x3004 }, // 〄 59 | { "\xe4\x80\x87", 0x4007 }, // 䀇 60 | { "\xe5\x82\x96", 0x5096 }, // 傖 61 | { "\xef\xbf\xbd", 0xFFFD }, // � 62 | 63 | { "\xf0\x90\x8c\x8f", 0x1030F }, // 𐌏 64 | { "\xf0\x9f\x82\xbb", 0x1F0BB }, // 🂻 65 | { "\xf0\x9f\x86\x92", 0x1F192 }, // 🆒 66 | { "\xf0\x9f\x8c\xae", 0x1F32E }, // 🌮 67 | { "\xf3\xa0\x80\xa4", 0xE0024 }, 68 | 69 | { "\xf4\x80\x80\x80", 0x100000 }, 70 | }; 71 | 72 | static void test_utf8_seq_to_codepoint(void) 73 | { 74 | // test valid sequence to codepoint table 75 | for (int i = 0; i < (int)(sizeof(tests)/sizeof(tests[0])); i++) { 76 | const struct t *t = &tests[i]; 77 | uint32_t code; 78 | int n = utf8_seq_to_codepoint(&code, t->seq, strlen(t->seq)); 79 | printf("n=%d, codepoint=U+%04X\n", n, code); 80 | assert(n == (int)strlen(t->seq)); 81 | assert(code == t->code); 82 | } 83 | 84 | // test \0 separately because of strlen issues 85 | uint32_t code; 86 | int n = utf8_seq_to_codepoint(&code, "\0", 1); 87 | printf("n=%d, code=U+%04X\n", n, code); 88 | assert(n == 1); 89 | assert(code == 0x00); 90 | 91 | // illegal leading bytes return 0 and don't write codepoint 92 | char * chars[] = { "\x80;;;", "\xc1;;;", "\xf8;;;", "\xff;;;" }; 93 | for (int i = 0; i < (int)(sizeof(chars)/sizeof(chars[0])); i++) { 94 | uint32_t code = 1; 95 | int n = utf8_seq_to_codepoint(&code, chars[i], 4); 96 | printf("n=%d, code=U+%04X, seq=%s\n", n, code, chars[i]); 97 | assert(n == 0); 98 | assert(code == 1); 99 | } 100 | 101 | // not enough bytes returns 0 and doesn't write to codepoint 102 | code = 1; 103 | n = utf8_seq_to_codepoint(&code, "\xe0\xa4", 2); 104 | printf("n=%d, code=U+%04X, seq=%s\n", n, code, "\xe0\xa4"); 105 | assert(n == 0); 106 | assert(code == 1); 107 | } 108 | 109 | static void test_utf8_codepoint_to_seq(void) 110 | { 111 | for (int i = 0; i < (int)(sizeof(tests)/sizeof(tests[0])); i++) { 112 | const struct t *t = &tests[i]; 113 | char buf[7] = {0}; 114 | int n = utf8_codepoint_to_seq(buf, t->code); 115 | printf("n=%d, code=U+%04X, seq=%s\n", n, t->code, buf); 116 | assert(n == (int)strlen(buf)); 117 | assert(strcmp(buf, t->seq) == 0); 118 | } 119 | 120 | // test \0 separately because of strlen issues 121 | char buf[4] = { 'A' }; 122 | int n = utf8_codepoint_to_seq(buf, 0x00); 123 | printf("n=%d, code=U+%04X\n", n, 0x00); 124 | assert(n == 1); 125 | assert(buf[0] == '\0'); 126 | 127 | // invalid codepoints return 0 and don't write buf 128 | buf[0] = 'A'; 129 | n = utf8_codepoint_to_seq(buf, 0x10FFFF+1); 130 | printf("n=%d, code=U+%04X\n", n, 0x11FFFF+1); 131 | assert(n == 0); 132 | assert(buf[0] == 'A'); 133 | } 134 | 135 | 136 | int main(void) 137 | { 138 | // make stdout line buffered 139 | setvbuf(stdout, NULL, _IOLBF, -BUFSIZ); 140 | 141 | test_utf8_len(); 142 | test_utf8_seq_to_codepoint(); 143 | test_utf8_codepoint_to_seq(); 144 | 145 | return 0; 146 | } 147 | 148 | // vim: noexpandtab 149 | -------------------------------------------------------------------------------- /tkbd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * tkbd.c - Terminal keyboard, mouse, and character input library 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * See tkbd.h for interface documentation. 7 | * 8 | */ 9 | 10 | #include "tkbd.h" 11 | #include "ti.h" 12 | 13 | #include // strtoul 14 | #include // size_t 15 | #include // snprintf 16 | #include // memset 17 | #include // read 18 | #include // tcgetattr, tcsetattr, struct termios 19 | #include // isprint 20 | #include 21 | 22 | #define ARRAYLEN(a) (sizeof(a) / sizeof(a[0])) 23 | #define MIN(a,b) (((a)<(b))?(a):(b)) 24 | #define MAX(a,b) (((a)>(b))?(a):(b)) 25 | 26 | 27 | // Attach the keyboard input stream stuct to the given file descriptor. 28 | int tkbd_attach(struct tkbd_stream *s, int fd) 29 | { 30 | int rc; 31 | 32 | s->fd = fd; 33 | memset(s->buf, 0, sizeof(s->buf)); 34 | s->bufpos = 0; 35 | s->buflen = 0; 36 | 37 | // don't attempt to enter raw mode if fd isn't a tty 38 | if(!isatty(fd)) { 39 | s->tc = (struct termios){0}; 40 | return 0; 41 | } 42 | 43 | // save current termios settings for detach() 44 | if ((rc = tcgetattr(fd, &s->tc))) 45 | return rc; 46 | 47 | // set raw mode input flags 48 | struct termios raw = s->tc; 49 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 50 | raw.c_cflag |= (CS8); 51 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 52 | 53 | // config read(2) to return a 0 read after 100ms of keyboard inactivity 54 | // and to otherwise return as soon as data arrives 55 | raw.c_cc[VMIN] = 0; 56 | raw.c_cc[VTIME] = 1; 57 | 58 | if ((rc = tcsetattr(fd, TCSAFLUSH, &raw))) 59 | return rc; 60 | 61 | return 0; 62 | } 63 | 64 | // Reset file descriptor termios attributes if set in tkbd_attach(). 65 | int tkbd_detach(struct tkbd_stream *s) 66 | { 67 | struct termios empty = {0}; 68 | if (memcmp(&s->tc, &empty, sizeof(empty)) == 0) 69 | return 0; 70 | 71 | int rc = tcsetattr(s->fd, TCSAFLUSH, &s->tc); 72 | return rc; 73 | } 74 | 75 | // Read a key, mouse, or character from the keyboard input stream. 76 | int tkbd_read(struct tkbd_stream *s, struct tkbd_seq *seq) 77 | { 78 | // fill buffer with data from fd, possibly restructuring the buffer to 79 | // free already processed input. 80 | if (s->buflen == 0) { 81 | int bufspc = sizeof(s->buf) - s->bufpos - s->buflen; 82 | 83 | if (bufspc < TKBD_SEQ_MAX) { 84 | memmove(s->buf, s->buf + s->bufpos, s->buflen); 85 | s->bufpos = 0; 86 | bufspc = sizeof(s->buf) - s->buflen; 87 | } 88 | 89 | if (bufspc > 0) { 90 | char *p = s->buf + s->bufpos + s->buflen; 91 | ssize_t sz = read(s->fd, p, bufspc); 92 | if (sz < 0) 93 | return sz; 94 | s->buflen += sz; 95 | } 96 | } 97 | 98 | char *buf = s->buf + s->bufpos; 99 | int len = s->buflen; 100 | assert(buf+len <= s->buf+sizeof(s->buf)); 101 | 102 | int n = tkbd_parse(seq, buf, len); 103 | s->bufpos += n; 104 | s->buflen -= n; 105 | 106 | return n; 107 | } 108 | 109 | 110 | /* 111 | * tkbd_parse() internal routines and constants 112 | * 113 | */ 114 | 115 | // Parse multiple numeric parameters from a CSI sequence and store in the array 116 | // pointed to by ar. A maximum of n parameters will be parsed and filled into 117 | // the array. If a parameter is blank, 0 will be set in the array. 118 | // 119 | // Returns the number of params parsed and filled into the array. 120 | static int parse_seq_params(int* ar, int n, char *pdata) 121 | { 122 | // if first char is 0x3C..0x3F then param data is privately defined and 123 | // may not conform to ECMA-48 numeric/selection param format. 124 | if (*pdata >= '>' && *pdata <= '?') 125 | return 0; 126 | 127 | int i = 0; 128 | while (i < n) { 129 | ar[i++] = strtol(pdata, &pdata, 10); 130 | if (*pdata++ == '\0') 131 | break; 132 | } 133 | 134 | return i; 135 | } 136 | 137 | static const uint8_t utf8_length[256] = { 138 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x00 139 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x20 140 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x40 141 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x60 142 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x80 143 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xa0 144 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xc0 145 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 // 0xe0 146 | }; 147 | static const uint8_t utf8_mask[6] = {0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01}; 148 | 149 | // Convert one or more bytes into unicode codepoint. No more than sz bytes is 150 | // read from the seq buffer. 151 | // Returns the number of bytes consumed. 152 | static int parse_utf8(uint32_t *codepoint, const char *seq, size_t sz) 153 | { 154 | if (sz < 1) 155 | return 0; 156 | 157 | int len = utf8_length[(uint8_t)seq[0]]; 158 | if (len > (int)sz) 159 | return 0; 160 | 161 | uint8_t mask = utf8_mask[len-1]; 162 | uint32_t res = seq[0] & mask; 163 | for (int i = 1; i < len; ++i) { 164 | res <<= 6; 165 | res |= seq[i] & 0x3f; 166 | } 167 | 168 | *codepoint = res; 169 | return len; 170 | } 171 | 172 | // Parse a printable US-ASCII, ISO-8859-1, or utf8 character and store in seq. 173 | static int parse_char_seq(struct tkbd_seq *seq, const char *p, int len) 174 | { 175 | const char *pe = p + len; 176 | 177 | if (p >= pe || *p < 0x20 || *p == 0x7F) 178 | return 0; 179 | 180 | seq->type = TKBD_KEY; 181 | seq->ch = *p; 182 | seq->data[0] = *p; 183 | seq->len = 1; 184 | 185 | if (*p >= 'a' && *p <= 'z') { 186 | seq->key = TKBD_KEY_A + (*p - 'a'); 187 | return 1; 188 | } 189 | 190 | if (*p >= '0' && *p <= '9') { 191 | seq->key = *p; 192 | return 1; 193 | } 194 | 195 | if (*p >= 'A' && *p <= 'Z') { 196 | seq->mod |= TKBD_MOD_SHIFT; 197 | seq->key = *p; 198 | return 1; 199 | } 200 | 201 | // punctuation character or space 202 | if (*p <= 0x7E) { 203 | seq->key = *p; 204 | if (!strchr(" `-=[]\\;',./", *p)) 205 | seq->mod |= TKBD_MOD_SHIFT; 206 | return 1; 207 | } 208 | 209 | // TODO: consider allowing extended ascii 0x80-0xc1, 0xF5-0xFF to 210 | // represent codepoints U+80-U+C1, U+F5-U+FF. 211 | 212 | // non-key utf8 character 213 | uint32_t codepoint = 0; 214 | int seqlen = parse_utf8(&codepoint, p, len); 215 | if (seqlen > 0) { 216 | seq->key = TKBD_KEY_NONE; 217 | seq->ch = codepoint; 218 | memcpy(seq->data, p, seqlen); 219 | seq->len = seqlen; 220 | return seqlen; 221 | } 222 | 223 | return 0; 224 | } 225 | 226 | // Parse a Ctrl+CH, BACKSPACE, TAB, ENTER, and ESC char sequences. 227 | // These generate single-byte C0 sequences. 228 | // 229 | // Control sequences handled 230 | // Ctrl+\ or Ctrl+4, Ctrl+] or Ctrl+5, Ctrl+^ or Ctrl+6, Ctrl+_ or Ctrl+7, 231 | // Ctrl+@ or Ctrl+2, Ctrl+A...Ctrl+Z (0x01...0x1A). 232 | static int parse_ctrl_seq(struct tkbd_seq *seq, const char *p, int len) 233 | { 234 | const char *pe = p + len; 235 | 236 | if (p >= pe) 237 | return 0; 238 | 239 | if (*p == '\033') { 240 | // ESC key 241 | seq->key = TKBD_KEY_ESC; 242 | } else if (*p >= 0x1C && *p <= 0x1F) { 243 | // Ctrl+\ or Ctrl+4, Ctrl+] or Ctrl+5, 244 | // Ctrl+^ or Ctrl+6, Ctrl+_ or Ctrl+7 245 | seq->mod |= TKBD_MOD_CTRL; 246 | seq->key = TKBD_KEY_BACKSLASH + (*p - 0x1C); 247 | } else if (*p >= 0x08 && *p <= 0x0A) { 248 | // BACKSPACE (CTRL+H), TAB (CTRL+I), ENTER (CTRL+J) 249 | seq->key = *p; 250 | } else if (*p == 0x00) { 251 | // Ctrl+@ or Ctrl+2 252 | seq->mod |= TKBD_MOD_CTRL; 253 | seq->key = TKBD_KEY_2; 254 | } else if (*p == 0x7F) { 255 | // BACKSPACE2 or Ctrl+8 256 | seq->key = TKBD_KEY_BACKSPACE2; 257 | } else if (*p <= 0x1A) { 258 | // Ctrl+A (0x01) through Ctrl+Z (0x1A) 259 | seq->mod |= TKBD_MOD_CTRL; 260 | seq->key = TKBD_KEY_A + (*p - 0x01); 261 | } else { 262 | return 0; 263 | } 264 | 265 | seq->ch = *p; 266 | seq->data[0] = *p; 267 | seq->len = 1; 268 | seq->type = TKBD_KEY; 269 | return 1; 270 | } 271 | 272 | /* 273 | * Table of DECFNK style sequences 274 | * 275 | * ESC [ KEY ; MOD ~ 276 | * Ex: \E[1~ = HOME, \E[1;2~ = Shift+HOME 277 | * 278 | * This encoding scheme was originally introduced on the DEC VT100 for the 279 | * SF1-SF4 special function keys and was later extended by xterm and other 280 | * emulators to cover a much wider range of keys. 281 | * 282 | * Array elements correspond to first parameter in escape sequence. 283 | * Second parameter specifies mod key flags. 284 | * 285 | * \E[0~ - \E[10~ F0 \E[20~ F9 \E[30~ - 286 | * \E[1~ HOME \E[11~ F1 \E[21~ F10 \E[31~ F17 287 | * \E[2~ INS \E[12~ F2 \E[22~ - \E[32~ F18 288 | * \E[3~ DEL \E[13~ F3 \E[23~ F11 \E[33~ F19 289 | * \E[4~ END \E[14~ F4 \E[24~ F12 \E[34~ F20 290 | * \E[5~ PGUP \E[15~ F5 \E[25~ F13 \E[35~ 291 | * \E[6~ PGDN \E[16~ - \E[26~ F14 292 | * \E[7~ HOME \E[17~ F6 \E[27~ - 293 | * \E[8~ END \E[18~ F7 \E[28~ F15 294 | * \E[9~ - \E[19~ F8 \E[29~ F16 295 | * 296 | */ 297 | static const uint16_t fn_key_table[] = { 298 | TKBD_KEY_UNKNOWN, 299 | TKBD_KEY_HOME, 300 | TKBD_KEY_INS, 301 | TKBD_KEY_DEL, 302 | TKBD_KEY_END, 303 | TKBD_KEY_PGUP, 304 | TKBD_KEY_PGDN, 305 | TKBD_KEY_HOME, 306 | TKBD_KEY_END, 307 | TKBD_KEY_UNKNOWN, 308 | TKBD_KEY_UNKNOWN, 309 | TKBD_KEY_F1, 310 | TKBD_KEY_F2, 311 | TKBD_KEY_F3, 312 | TKBD_KEY_F4, 313 | TKBD_KEY_F5, 314 | TKBD_KEY_UNKNOWN, 315 | TKBD_KEY_F6, 316 | TKBD_KEY_F7, 317 | TKBD_KEY_F8, 318 | TKBD_KEY_F9, 319 | TKBD_KEY_F10, 320 | TKBD_KEY_UNKNOWN, 321 | TKBD_KEY_F11, 322 | TKBD_KEY_F12, 323 | TKBD_KEY_F13, 324 | TKBD_KEY_F14, 325 | TKBD_KEY_UNKNOWN, 326 | TKBD_KEY_F15, 327 | TKBD_KEY_F16, 328 | TKBD_KEY_UNKNOWN, 329 | TKBD_KEY_F17, 330 | TKBD_KEY_F18, 331 | TKBD_KEY_F19, 332 | TKBD_KEY_F20, 333 | }; 334 | 335 | /* 336 | * Table of ANSI style key input sequences. 337 | * 338 | * This encoding scheme was part of ANSI X3.64-1979 and adopted by the DEC VT100 339 | * terminal as its new standard mode of operation. 340 | * 341 | * ESC [ MOD KEY 342 | * Ex: \E[H = HOME, \E[2H = Shift+HOME 343 | * 344 | * First parameter, when given, specifies the key modifier except when 1 and 345 | * the second parameter is set, in which case the second parameter specifies 346 | * key modifier flags. 347 | * 348 | * Array elements correspond to CHR - A in escape sequence table. 349 | * 350 | * \E[A UP \E[K DEL \E[U - 351 | * \E[B DOWN \E[L ^C+INS \E[V - 352 | * \E[C RIGHT \E[M ^C+DEL \E[W - 353 | * \E[D LEFT \E[N - \E[X - 354 | * \E[E KP 5 \E[O - \E[Y - 355 | * \E[F END \E[P DEL \E[Z Shift+Tab 356 | * \E[G KP 5 \E[Q - 357 | * \E[H HOME \E[R - 358 | * \E[I - \E[S - 359 | * \E[J HOME \E[T - 360 | * 361 | * Lowercase range: 362 | * 363 | * \E[h INS 364 | * 365 | * The VT100 had the concept of "cursor mode" where SS3 is used to introduce the 366 | * sequence instead of CSI (e.g., "\EOP" instead of "\E[P"). This has been 367 | * carried forward and keys arrive in both styles but not necessarily due to any 368 | * mode. 369 | * 370 | * \EOA - \EOK - \EOU - 371 | * \EOB - \EOL - \EOV - 372 | * \EOC - \EOM - \EOW - 373 | * \EOD - \EON - \EOX - 374 | * \EOE - \EOO - \EOY - 375 | * \EOF - \EOP F1 \EOZ - 376 | * \EOG - \EOQ F2 377 | * \EOH - \EOR F3 378 | * \EOI - \EOS F4 379 | * \EOJ - \EOT - 380 | */ 381 | static const uint16_t ansi_key_table[] = { 382 | TKBD_KEY_UP, // A 383 | TKBD_KEY_DOWN, // B 384 | TKBD_KEY_RIGHT, // C 385 | TKBD_KEY_LEFT, // D 386 | TKBD_KEY_UNKNOWN, // E 387 | TKBD_KEY_END, // F 388 | TKBD_KEY_UNKNOWN, // G 389 | TKBD_KEY_HOME, // H 390 | TKBD_KEY_UNKNOWN, // I 391 | TKBD_KEY_HOME, // J 392 | TKBD_KEY_DEL, // K 393 | TKBD_KEY_INS, // L 394 | TKBD_KEY_DEL, // M 395 | TKBD_KEY_UNKNOWN, // N 396 | TKBD_KEY_UNKNOWN, // O 397 | TKBD_KEY_DEL, // P 398 | TKBD_KEY_UNKNOWN, // Q 399 | TKBD_KEY_UNKNOWN, // R 400 | TKBD_KEY_UNKNOWN, // S 401 | TKBD_KEY_UNKNOWN, // T 402 | TKBD_KEY_UNKNOWN, // U 403 | TKBD_KEY_UNKNOWN, // V 404 | TKBD_KEY_UNKNOWN, // W 405 | TKBD_KEY_UNKNOWN, // X 406 | TKBD_KEY_UNKNOWN, // Y 407 | TKBD_KEY_TAB, // Z 408 | }; 409 | 410 | // Linux terminal special case: F1 - F5 keys are \E[[A - \E[[E. 411 | static int parse_linux_seq(struct tkbd_seq *seq, const char *p, int len) 412 | { 413 | static const int seqlen = 4; 414 | if (len < seqlen) 415 | return 0; 416 | 417 | if (p[0] != '\033' || p[1] != '[' || p[2] != '[') 418 | return 0; 419 | 420 | if (p[3] < 'A' || p[3] > 'E') 421 | return 0; 422 | 423 | seq->type = TKBD_KEY; 424 | seq->key = TKBD_KEY_F1 + (p[3] - 'A'); 425 | seq->len = seqlen; 426 | memcpy(seq->data, p, seqlen); 427 | return 4; 428 | } 429 | 430 | // Parse a special keyboard sequence and fill the zeroed seq structure. 431 | // No more than len bytes will be read from buf. 432 | // 433 | // Special keyboard sequences are typically only generated for function keys 434 | // F1-F12, INS, DEL, HOME, END, PGUP, PGDOWN, and the cursor arrow keys. 435 | // 436 | // IMPORTANT: This function assumes the seq struct is zeroed. 437 | // 438 | // Returns the number of bytes read from buf to fill the seq structure. 439 | // Returns zero when no escape sequence is present at front of buf or when the 440 | // sequence is not recognized. 441 | // 442 | // Conforms to ECMA-48 Section 5.4 "Control sequences". 443 | static int parse_special_seq(struct tkbd_seq *seq, const char *buf, int len) 444 | { 445 | const char *p = buf; 446 | const char *pe = p + len; 447 | 448 | // bail if not an escape sequence 449 | if (p >= pe || *p++ != '\033') 450 | return 0; 451 | if (p >= pe) 452 | return 0; 453 | 454 | // figure out CSI vs. SS3 sequence type; bail if neither 455 | const char seqtype = *p++; 456 | if (seqtype != '[' && seqtype != 'O') 457 | return 0; 458 | 459 | // consume all parameter bytes (0x30-0x3F) 460 | int i = 0; 461 | char parmdata[32] = {0}; 462 | while (p < pe && *p >= '0' && *p <= '?') { 463 | // continue seeking if params overflow buffer 464 | if (i < (int)sizeof(parmdata)-1) 465 | parmdata[i++] = *p++; 466 | else 467 | p++; 468 | } 469 | if (p >= pe) 470 | return 0; 471 | 472 | // special case Linux term F1-F5 keys: \E[[A - \E[[E 473 | // this isn't even a valid ECMA-48 sequence 474 | if (*p == '[') 475 | return parse_linux_seq(seq, buf, len); 476 | 477 | // optional intermediate byte (0x20 - 0x2F) 478 | // 479 | // NOTE: ECMA-48 mentions that more than one intermediate byte is 480 | // technically allowed but also that it shouldn't be needed and this 481 | // appears to not be used in practice. 482 | char interbyte = 0; 483 | if (*p >= ' ' && *p <= '/') 484 | interbyte = *p++; 485 | (void)interbyte; 486 | 487 | if (p >= pe) 488 | return 0; 489 | 490 | // final byte (0x40 - 0x7E) 491 | char finalbyte = 0; 492 | if (*p >= '@' && *p <= '~') 493 | finalbyte = *p++; 494 | 495 | // at this point we have a valid ECMA-48 CSI or SS3 sequence and will 496 | // not return 0 to signal we don't understand the sequence 497 | seq->type = TKBD_KEY; 498 | seq->len = MIN(p-buf, TKBD_SEQ_MAX); 499 | memcpy(seq->data, buf, seq->len); 500 | 501 | // determine key sequence style and map key and mods 502 | if (seqtype == '[' && finalbyte == '~') { 503 | // ESC [ KEY ; MOD ~ 504 | // Ex: \E[5;3~ = ALT+PGUP 505 | int parms[2] = {0}; 506 | parse_seq_params(parms, ARRAYLEN(parms), parmdata); 507 | 508 | if (parms[0] < (int)ARRAYLEN(fn_key_table)) 509 | seq->key = fn_key_table[parms[0]]; 510 | else 511 | seq->key = TKBD_KEY_UNKNOWN; 512 | 513 | if (parms[1]) 514 | seq->mod = parms[1] - 1; 515 | 516 | } else if (seqtype == '[' && finalbyte >= 'A' && finalbyte <= 'Z') { 517 | // ESC [ MOD KEY 518 | // Ex: \E[3A = ALT+UP, \EOP = F1 519 | int parms[2] = {0}; 520 | parse_seq_params(parms, ARRAYLEN(parms), parmdata); 521 | seq->key = ansi_key_table[finalbyte - 'A']; 522 | 523 | // special case \E[Z = Shift+Tab 524 | if (finalbyte == 'Z') 525 | seq->mod |= TKBD_MOD_SHIFT; 526 | 527 | // handle both forms: "\E[3A" and "\E[1;3A" both = ALT+UP 528 | if (parms[0] == 1 && parms[1]) 529 | seq->mod = parms[1] - 1; 530 | else if (parms[0]) 531 | seq->mod = parms[0] - 1; 532 | 533 | } else if (seqtype == 'O' && finalbyte >= 'P' && finalbyte <= 'S') { 534 | // \E[OP-\E[OS = F1-F4 535 | seq->key = TKBD_KEY_F1 + finalbyte - 'P'; 536 | 537 | } else { 538 | // we dont have a mapping for this key but we know we received a 539 | // fully formed ECMA-48 CSI/SS3 sequence so let's count it. 540 | seq->key = TKBD_KEY_UNKNOWN; 541 | } 542 | 543 | return p - buf; 544 | 545 | } 546 | 547 | // Parse an ALT key sequence and fill seq struct. 548 | // Any character or C0 control sequence may be preceded by ESC, indicating 549 | // that ALT was pressed at the same time. 550 | // 551 | // ALT+CH: \Eg (parse_char_seq) 552 | // SHIFT+ALT+CH: \EG (parse_char_seq) 553 | // CTRL+ALT+CH: \E^G (parse_ctrl_seq) 554 | // 555 | // Returns the number of bytes consumed to fill the seq struct. 556 | static int parse_alt_seq(struct tkbd_seq *seq, const char *buf, int len) 557 | { 558 | const char *p = buf; 559 | const char *pe = buf + len; 560 | 561 | if (p >= pe || *p++ != '\033') 562 | return 0; 563 | 564 | int n = parse_char_seq(seq, p, pe - p); 565 | if (n == 0) 566 | n = parse_special_seq(seq, p, pe - p); 567 | if (n == 0) 568 | n = parse_ctrl_seq(seq, p, pe - p); 569 | 570 | if (n == 0) 571 | return 0; 572 | 573 | seq->mod |= TKBD_MOD_ALT; 574 | p += n; 575 | seq->len = p - buf; 576 | memcpy(seq->data, buf, seq->len); 577 | return p - buf; 578 | } 579 | 580 | // Decode various mouse event escape sequences into the given seq struct. 581 | // Returns the number of bytes read from buf when the sequence is recognized and 582 | // decoded; or, a negative integer count of bytes when the sequence is recognized 583 | // as a mouse sequence but invalid. 584 | static int parse_mouse_seq(struct tkbd_seq *seq, const char *buf, int len) 585 | { 586 | if (len < 3 || !(buf[0] == '\033' && buf[1] == '[')) 587 | return 0; 588 | 589 | // TODO: split two main cases into separate functions 590 | if (len >= 6 && buf[2] == 'M') { 591 | // X10 mouse encoding, the simplest one 592 | // \033 [ M Cb Cx Cy 593 | int b = buf[3] - 32; 594 | switch (b & 3) { 595 | case 0: 596 | if ((b & 64) != 0) 597 | seq->key = TKBD_MOUSE_WHEEL_UP; 598 | else 599 | seq->key = TKBD_MOUSE_LEFT; 600 | break; 601 | case 1: 602 | if ((b & 64) != 0) 603 | seq->key = TKBD_MOUSE_WHEEL_DOWN; 604 | else 605 | seq->key = TKBD_MOUSE_MIDDLE; 606 | break; 607 | case 2: 608 | seq->key = TKBD_MOUSE_RIGHT; 609 | break; 610 | case 3: 611 | seq->key = TKBD_MOUSE_RELEASE; 612 | break; 613 | default: 614 | // TODO: unknown sequence type instead of neg return 615 | return -6; 616 | } 617 | seq->type = TKBD_MOUSE; // TBKB_KEY by default 618 | if ((b & 32) != 0) 619 | seq->mod |= TKBD_MOD_MOTION; 620 | 621 | // the coord is 1,1 for upper left 622 | seq->x = (uint8_t)buf[4] - 1 - 32; 623 | seq->y = (uint8_t)buf[5] - 1 - 32; 624 | 625 | return 6; 626 | } else { 627 | // xterm 1006 extended mode or urxvt 1015 extended mode 628 | // xterm: \033 [ < Cb ; Cx ; Cy (M or m) 629 | // urxvt: \033 [ Cb ; Cx ; Cy M 630 | int i, mi = -1, starti = -1; 631 | int isM, isU, s1 = -1, s2 = -1; 632 | int n1 = 0, n2 = 0, n3 = 0; 633 | 634 | for (i = 0; i < len; i++) { 635 | // We search the first (s1) and the last (s2) ';' 636 | if (buf[i] == ';') { 637 | if (s1 == -1) 638 | s1 = i; 639 | s2 = i; 640 | } 641 | 642 | // We search for the first 'm' or 'M' 643 | if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) { 644 | mi = i; 645 | break; 646 | } 647 | } 648 | if (mi == -1) 649 | return 0; 650 | 651 | // whether it's a capital M or not 652 | isM = (buf[mi] == 'M'); 653 | 654 | if (buf[2] == '<') { 655 | isU = 0; 656 | starti = 3; 657 | } else { 658 | isU = 1; 659 | starti = 2; 660 | } 661 | 662 | if (s1 == -1 || s2 == -1 || s1 == s2) 663 | return 0; 664 | 665 | n1 = strtoul(&buf[starti], NULL, 10); 666 | n2 = strtoul(&buf[s1 + 1], NULL, 10); 667 | n3 = strtoul(&buf[s2 + 1], NULL, 10); 668 | 669 | if (isU) 670 | n1 -= 32; 671 | 672 | switch (n1 & 3) { 673 | case 0: 674 | if ((n1&64) != 0) 675 | seq->key = TKBD_MOUSE_WHEEL_UP; 676 | else 677 | seq->key = TKBD_MOUSE_LEFT; 678 | break; 679 | case 1: 680 | if ((n1&64) != 0) 681 | seq->key = TKBD_MOUSE_WHEEL_DOWN; 682 | else 683 | seq->key = TKBD_MOUSE_MIDDLE; 684 | break; 685 | case 2: 686 | seq->key = TKBD_MOUSE_RIGHT; 687 | break; 688 | case 3: 689 | seq->key = TKBD_MOUSE_RELEASE; 690 | break; 691 | default: 692 | return mi + 1; 693 | } 694 | 695 | if (!isM) // on xterm mouse release is signaled by lowercase m 696 | seq->key = TKBD_MOUSE_RELEASE; 697 | 698 | seq->type = TKBD_MOUSE; // TKBD_KEY by default 699 | if ((n1&32) != 0) 700 | seq->mod |= TKBD_MOD_MOTION; 701 | 702 | seq->x = (uint8_t)n2 - 1; 703 | seq->y = (uint8_t)n3 - 1; 704 | 705 | return mi + 1; 706 | } 707 | 708 | return 0; 709 | } 710 | 711 | // Parse mouse, special key, alt key, or ctrl key sequence and fill seq struct. 712 | // Order is important here since funcs like parse_alt_seq eat \033 chars. 713 | int tkbd_parse(struct tkbd_seq *seq, const char *buf, size_t sz) 714 | { 715 | int n; 716 | 717 | if ((n = parse_mouse_seq(seq, buf, sz))) 718 | return n; 719 | if ((n = parse_special_seq(seq, buf, sz))) 720 | return n; 721 | if ((n = parse_alt_seq(seq, buf, sz))) 722 | return n; 723 | if ((n = parse_ctrl_seq(seq, buf, sz))) 724 | return n; 725 | if ((n = parse_char_seq(seq, buf, sz))) 726 | return n; 727 | 728 | return 0; 729 | } 730 | 731 | 732 | /* 733 | * tkbd_desc() internal constants 734 | * 735 | */ 736 | 737 | // Modifier keys map to MOD - 1 738 | static const char * const modifier_key_names[] = { 739 | "Shift", 740 | "Alt", 741 | "Shift+Alt", 742 | "Ctrl", 743 | "Ctrl+Shift", 744 | "Ctrl+Alt", 745 | "Ctrl+Shift+Alt", 746 | "Meta", 747 | "Meta+Shift", 748 | "Meta+Alt", 749 | "Meta+Shift+Alt", 750 | "Meta+Ctrl", 751 | "Meta+Ctrl+Shift", 752 | "Meta+Ctrl+Alt", 753 | "Meta+Ctrl+Shift+Alt", 754 | }; 755 | 756 | // Special key name indexes map to KEY - TKBD_KEY_UP 757 | static const char * const special_key_names[] = { 758 | "Up", // TKBD_KEY_UP 0x10 759 | "Down", // TKBD_KEY_DOWN 0x11 760 | "Right", // TKBD_KEY_RIGHT 0x12 761 | "Left", // TKBD_KEY_LEFT 0x13 762 | "INS", // TKBD_KEY_INS 0x14 763 | "DEL", // TKBD_KEY_DEL 0x15 764 | "PgUp", // TKBD_KEY_PGUP 0x16 765 | "PgDn", // TKBD_KEY_PGDN 0x17 766 | "HOME", // TKBD_KEY_HOME 0x18 767 | "END", // TKBD_KEY_END 0x19 768 | }; 769 | 770 | // Function key names map to KEY - TKBD_KEY_F1 771 | static const char * const function_key_names[] = { 772 | "F1", // TKBD_KEY_F1 0x61 773 | "F2", // TKBD_KEY_F2 0x62 774 | "F3", // TKBD_KEY_F3 0x63 775 | "F4", // TKBD_KEY_F4 0x64 776 | "F5", // TKBD_KEY_F5 0x65 777 | NULL, 778 | "F6", // TKBD_KEY_F6 0x67 779 | "F7", // TKBD_KEY_F7 0x68 780 | "F8", // TKBD_KEY_F8 0x69 781 | "F9", // TKBD_KEY_F9 0x6A 782 | "F10", // TKBD_KEY_F10 0x6B 783 | "F11", // TKBD_KEY_F11 0x6C 784 | "F12", // TKBD_KEY_F12 0x6D 785 | "F13", // TKBD_KEY_F13 0x6E 786 | "F14", // TKBD_KEY_F14 0x6F 787 | NULL, 788 | "F15", // TKBD_KEY_F15 0x71 789 | "F16", // TKBD_KEY_F16 0x72 790 | NULL, 791 | "F17", // TKBD_KEY_F17 0x74 792 | "F18", // TKBD_KEY_F18 0x75 793 | "F19", // TKBD_KEY_F19 0x76 794 | "F20", // TKBD_KEY_F20 0x77 795 | }; 796 | 797 | int tkbd_desc(char *dest, size_t sz, const struct tkbd_seq *seq) 798 | { 799 | if (seq->type != TKBD_KEY) 800 | return 0; 801 | 802 | // figure out modifier string part 803 | const char *modstr = ""; 804 | uint8_t mod = seq->mod & (TKBD_MOD_SHIFT|TKBD_MOD_ALT| 805 | TKBD_MOD_CTRL|TKBD_MOD_META); 806 | if (mod) 807 | modstr = modifier_key_names[mod-1]; 808 | 809 | // figure out key name string 810 | const char *keystr = ""; 811 | char ch[2] = {0}; // for single char keys 812 | 813 | if (seq->key >= TKBD_KEY_UP && seq->key <= TKBD_KEY_END) { 814 | keystr = special_key_names[seq->key - TKBD_KEY_UP]; 815 | } else if (seq->key >= TKBD_KEY_F1 && seq->key <= TKBD_KEY_F20) { 816 | keystr = function_key_names[seq->key - TKBD_KEY_F1]; 817 | } else { 818 | switch (seq->key) { 819 | case TKBD_KEY_ESC: 820 | keystr = "ESC"; 821 | break; 822 | case TKBD_KEY_TAB: 823 | keystr = "Tab"; 824 | break; 825 | case TKBD_KEY_ENTER: 826 | keystr = "Enter"; 827 | break; 828 | case TKBD_KEY_SPACE: 829 | keystr = "Space"; 830 | break; 831 | case TKBD_KEY_BACKSPACE: 832 | case TKBD_KEY_BACKSPACE2: 833 | keystr = "Backspace"; 834 | break; 835 | case TKBD_KEY_UNKNOWN: 836 | keystr = "Unknown"; 837 | break; 838 | default: 839 | // XXX need to handle better 840 | assert(isprint(seq->key)); 841 | ch[0] = (char)seq->key; 842 | keystr = ch; 843 | break; 844 | } 845 | } 846 | 847 | if (keystr == NULL) 848 | keystr = "Unknown"; 849 | 850 | if (modstr[0] && keystr[0]) 851 | return snprintf(dest, sz, "%s+%s", modstr, keystr); 852 | 853 | if (keystr[0]) 854 | return snprintf(dest, sz, "%s", keystr); 855 | 856 | if (modstr[0]) 857 | return snprintf(dest, sz, "%s", modstr); 858 | 859 | return 0; 860 | } 861 | 862 | 863 | /* 864 | * tkbd_stresc() 865 | * 866 | */ 867 | 868 | // TODO: non-ascii (just let 8bit chars through) 869 | int tkbd_stresc(char *buf, const char *str, size_t strsz) 870 | { 871 | assert(buf); 872 | assert(str); 873 | 874 | // characters to escape and corresponding codes 875 | static const char chars[] = {'\\','\t','\n','\r','\033','\0'}; 876 | static const char codes[] = {'\\', 't', 'n', 'r', 'e', '0'}; 877 | 878 | char *pb = buf; 879 | const char *ps = str; 880 | const char * const pse = str + strsz; 881 | 882 | for (; ps < pse; ps++) { 883 | if (*ps >= ' ' && *ps <= '~' && *ps != '\\') { 884 | *pb++ = *ps; 885 | continue; 886 | } 887 | 888 | *pb++ = '\\'; 889 | size_t i = 0; 890 | for (; i < sizeof(chars); i++) { 891 | if (*ps == chars[i]) { 892 | *pb++ = codes[i]; 893 | break; 894 | } 895 | } 896 | if (i < sizeof(chars)) 897 | continue; 898 | 899 | pb += sprintf(pb, "%03hho", *ps); 900 | } 901 | *pb = 0; 902 | 903 | return pb - buf; 904 | } 905 | -------------------------------------------------------------------------------- /tkbd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * tkbd.h - Terminal keyboard, mouse, and character input library 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * Read input from a terminal with support for decoding special key, mouse, and 7 | * utf8 character sequences. 8 | * 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include // size_t 16 | #include // struct termios 17 | 18 | /* 19 | * Limits 20 | */ 21 | #define TKBD_SEQ_MAX 32 // max length in bytes of an escape sequence 22 | 23 | /* 24 | * Keyboard, mouse, or unicode character sequence structure. 25 | * 26 | * The tkbd_parse() and tkbd_read() functions fill this structure with 27 | * information consumed from a char buffer or file descriptor. 28 | */ 29 | struct tkbd_seq { 30 | uint8_t type; // TKBD_KEY or TKBD_MOUSE 31 | uint8_t mod; // one of the TKBD_MOD_* constants 32 | uint16_t key; // one of the TKBD_KEY_* constants 33 | uint32_t ch; // unicode character codepoint 34 | int32_t x, y; // mouse coordinates 35 | 36 | // raw char sequence source data 37 | size_t len; 38 | char data[TKBD_SEQ_MAX]; 39 | }; 40 | 41 | /* 42 | * Parse a single keyboard/mouse/resize sequence or UTF8 encoded character from 43 | * the buffer pointed to by buf and fill the structure pointed to by seq 44 | * with information. No more than sz bytes will be read from buf. 45 | * 46 | * Returns the number of bytes read from buf when the structure is filled. 47 | * Returns 0 when not enough data is available to decode a sequence. 48 | */ 49 | int tkbd_parse(struct tkbd_seq *seq, const char *buf, size_t sz); 50 | 51 | /* 52 | * Write a key description ("Ctrl+C", "Shift+Alt+PgUp", "Z", etc.) to the buffer 53 | * pointed to by buf. No more than sz bytes are written. 54 | * 55 | * Returns the number of bytes (excluding terminating null byte) needed to write 56 | * the description. No more than sz bytes will be written by the function but if 57 | * the return value is greater than sz, the description was truncated. 58 | */ 59 | int tkbd_desc(char *buf, size_t sz, const struct tkbd_seq *seq); 60 | 61 | /* 62 | * Keyboard input stream structure. 63 | * 64 | * Used with tkbd_read() to manage buffering and termios state. 65 | */ 66 | struct tkbd_stream { 67 | int fd; // file descriptor to read from 68 | char buf[1024]; // input buffer 69 | int bufpos; // current byte position buf 70 | int buflen; // number of bytes available after bufpos 71 | 72 | struct termios tc; // original termios 73 | }; 74 | 75 | /* 76 | * Attach a keyboard input stream structure to a file descriptor. 77 | * 78 | * The file descriptor is typically STDIN_FILENO or may be a file descriptor 79 | * associated with /dev/tty via a call to open(2) like: 80 | * 81 | * int fd = open("/dev/tty", O_RDWR); 82 | * 83 | * When the file descriptor is a tty, it's put into raw mode; non-tty file 84 | * descriptors may also be given but reads will block until EOF. 85 | * 86 | * Returns 0 on success. 87 | * Returns -1 on failure and sets errno appropriately. 88 | */ 89 | int tkbd_attach(struct tkbd_stream *s, int fd); 90 | 91 | /* 92 | * Detach the keyboard input stream from the attached file descriptor. 93 | * 94 | * This must be called on the stream before the program exits or the terminal 95 | * will remain in raw input mode. It's recommended this be set up to happen in 96 | * an atexit or signal hook. 97 | * 98 | * Returns 0 on success, -1 on failure and sets errno to indicate error. 99 | */ 100 | int tkbd_detach(struct tkbd_stream *s); 101 | 102 | /* 103 | * Read a single keyboard, mouse, or UTF8 encoded character sequence from the 104 | * stream and fill the structure pointed to by seq. 105 | * 106 | * Callers should be prepared to handle a filled key seq struct or a zero return 107 | * due to 100ms inactivity timeout. The latter is often used to check for 108 | * SIGWINCH or perform other housekeeping tasks. 109 | * 110 | * Returns the number of bytes consumed to fill the seq struct on success. 111 | * Returns 0 when 100ms has elapsed with no data arriving. 112 | * Returns -1 when a read error occurs and sets errno appropriately. 113 | */ 114 | int tkbd_read(struct tkbd_stream *s, struct tkbd_seq *seq); 115 | 116 | 117 | /* 118 | * Write an escaped version of a keyboard sequence to a character buffer. 119 | * 120 | * This is most often useful when printing the tkbd_seq.seq member for display 121 | * since writing the raw characters to the terminal may be interpreted as 122 | * commands instead of text. 123 | * 124 | * The strsz argument specifies the length of the sequence string in bytes since 125 | * it may contain null characters. 126 | * The character buffer is assumed to be 4x strsz in bytes. 127 | * 128 | * Returns the number of bytes written to buf, excluding the null terminator. 129 | */ 130 | int tkbd_stresc(char *buf, const char *str, size_t strsz); 131 | 132 | 133 | /* 134 | * Sequence types 135 | */ 136 | #define TKBD_KEY 1 // Key was pressed 137 | #define TKBD_MOUSE 2 // Move, scroll, or button sequence 138 | 139 | /* 140 | * Key modifier flags 141 | */ 142 | #define TKBD_MOD_NONE 0x00 143 | #define TKBD_MOD_SHIFT 0x01 144 | #define TKBD_MOD_ALT 0x02 145 | #define TKBD_MOD_CTRL 0x04 146 | #define TKBD_MOD_META 0x08 147 | #define TKBD_MOD_MOTION 0x80 148 | 149 | /* 150 | * Key constants 151 | * 152 | * Keys map to their ascii character equivalents where possible. 153 | * Special keys are mapped to unused parts of C0 control range. 154 | * Function keys are mapped to lower alpha range. 155 | * 156 | */ 157 | 158 | #define TKBD_KEY_NONE 0x00 159 | 160 | #define TKBD_KEY_UNKNOWN 0xFFFF 161 | 162 | #define TKBD_KEY_BACKSPACE 0x08 163 | #define TKBD_KEY_TAB 0x09 164 | #define TKBD_KEY_ENTER 0x0A 165 | #define TKBD_KEY_ESC 0x1B 166 | #define TKBD_KEY_SPACE 0x20 167 | #define TKBD_KEY_BACKSPACE2 0x7F 168 | 169 | #define TKBD_KEY_UP 0x10 170 | #define TKBD_KEY_DOWN 0x11 171 | #define TKBD_KEY_RIGHT 0x12 172 | #define TKBD_KEY_LEFT 0x13 173 | 174 | #define TKBD_KEY_INS 0x14 175 | #define TKBD_KEY_DEL 0x15 176 | #define TKBD_KEY_PGUP 0x16 177 | #define TKBD_KEY_PGDN 0x17 178 | #define TKBD_KEY_HOME 0x18 179 | #define TKBD_KEY_END 0x19 180 | 181 | #define TKBD_KEY_DOUBLE_QUOTE 0x22 182 | #define TKBD_KEY_QUOTE 0x27 183 | #define TKBD_KEY_PLUS 0x2B 184 | #define TKBD_KEY_COMMA 0x2C 185 | #define TKBD_KEY_DASH 0x2D 186 | #define TKBD_KEY_MINUS 0x2D 187 | #define TKBD_KEY_PERIOD 0x2E 188 | #define TKBD_KEY_SLASH 0x2F 189 | #define TKBD_KEY_BANG 0x21 190 | #define TKBD_KEY_POUND 0x23 191 | #define TKBD_KEY_DOLLAR 0x24 192 | #define TKBD_KEY_PERCENT 0x25 193 | #define TKBD_KEY_AMP 0x26 194 | #define TKBD_KEY_PAREN_LEFT 0x28 195 | #define TKBD_KEY_PAREN_RIGHT 0x29 196 | #define TKBD_KEY_STAR 0x2A 197 | 198 | #define TKBD_KEY_0 0x30 199 | #define TKBD_KEY_1 0x31 200 | #define TKBD_KEY_2 0x32 201 | #define TKBD_KEY_3 0x33 202 | #define TKBD_KEY_4 0x34 203 | #define TKBD_KEY_5 0x35 204 | #define TKBD_KEY_6 0x36 205 | #define TKBD_KEY_7 0x37 206 | #define TKBD_KEY_8 0x38 207 | #define TKBD_KEY_9 0x39 208 | 209 | #define TKBD_KEY_COLON 0x3A 210 | #define TKBD_KEY_SEMICOLON 0x3B 211 | #define TKBD_KEY_LT 0x3C 212 | #define TKBD_KEY_EQUAL 0x3D 213 | #define TKBD_KEY_GT 0x3E 214 | #define TKBD_KEY_QUESTION 0x3F 215 | #define TKBD_KEY_AT 0x40 216 | 217 | #define TKBD_KEY_A 0x41 218 | #define TKBD_KEY_B 0x42 219 | #define TKBD_KEY_C 0x43 220 | #define TKBD_KEY_D 0x44 221 | #define TKBD_KEY_E 0x45 222 | #define TKBD_KEY_F 0x46 223 | #define TKBD_KEY_G 0x47 224 | #define TKBD_KEY_H 0x48 225 | #define TKBD_KEY_I 0x49 226 | #define TKBD_KEY_J 0x4A 227 | #define TKBD_KEY_K 0x4B 228 | #define TKBD_KEY_L 0x4C 229 | #define TKBD_KEY_M 0x4D 230 | #define TKBD_KEY_N 0x4E 231 | #define TKBD_KEY_O 0x4F 232 | #define TKBD_KEY_P 0x50 233 | #define TKBD_KEY_Q 0x51 234 | #define TKBD_KEY_R 0x52 235 | #define TKBD_KEY_S 0x53 236 | #define TKBD_KEY_T 0x54 237 | #define TKBD_KEY_U 0x55 238 | #define TKBD_KEY_V 0x56 239 | #define TKBD_KEY_W 0x57 240 | #define TKBD_KEY_X 0x58 241 | #define TKBD_KEY_Y 0x59 242 | #define TKBD_KEY_Z 0x5A 243 | 244 | #define TKBD_KEY_BRACKET_LEFT 0x5B 245 | #define TKBD_KEY_BACKSLASH 0x5C 246 | #define TKBD_KEY_BRACKET_RIGHT 0x5D 247 | #define TKBD_KEY_CARROT 0x5E 248 | #define TKBD_KEY_UNDERSCORE 0x5F 249 | #define TKBD_KEY_BACKTICK 0x60 250 | #define TKBD_KEY_BACKQUOTE 0x60 251 | 252 | #define TKBD_KEY_F1 0x61 253 | #define TKBD_KEY_F2 0x62 254 | #define TKBD_KEY_F3 0x63 255 | #define TKBD_KEY_F4 0x64 256 | #define TKBD_KEY_F5 0x65 257 | #define TKBD_KEY_F6 0x67 258 | #define TKBD_KEY_F7 0x68 259 | #define TKBD_KEY_F8 0x69 260 | #define TKBD_KEY_F9 0x6A 261 | #define TKBD_KEY_F10 0x6B 262 | #define TKBD_KEY_F11 0x6C 263 | #define TKBD_KEY_F12 0x6D 264 | #define TKBD_KEY_F13 0x6E 265 | #define TKBD_KEY_F14 0x6F 266 | #define TKBD_KEY_F15 0x71 267 | #define TKBD_KEY_F16 0x72 268 | #define TKBD_KEY_F17 0x74 269 | #define TKBD_KEY_F18 0x75 270 | #define TKBD_KEY_F19 0x76 271 | #define TKBD_KEY_F20 0x77 272 | 273 | #define TKBD_KEY_BRACE_LEFT 0x7B 274 | #define TKBD_KEY_PIPE 0x7C 275 | #define TKBD_KEY_BRACE_RIGHT 0x7D 276 | #define TKBD_KEY_TILDE 0x7E 277 | 278 | #define TKBD_MOUSE_LEFT (0xFFFF-1) 279 | #define TKBD_MOUSE_RIGHT (0xFFFF-2) 280 | #define TKBD_MOUSE_MIDDLE (0xFFFF-3) 281 | #define TKBD_MOUSE_RELEASE (0xFFFF-4) 282 | #define TKBD_MOUSE_WHEEL_UP (0xFFFF-5) 283 | #define TKBD_MOUSE_WHEEL_DOWN (0xFFFF-6) 284 | 285 | -------------------------------------------------------------------------------- /tools/gencap-defs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #/ Usage: gencap-defs.sh [] 3 | #/ Generates C preprocessor defines for all terminfo capabilities using the 4 | #/ latest ncurses Caps database published to the ThomasDickey/ncurses-snapshots 5 | #/ repository, or a custom URL if provided. 6 | set -eu 7 | 8 | # Show usage 9 | if [ $# -gt 0 ] && [ "$1" = "--help" ]; then 10 | grep <"$0" '^#/' | cut -c4- 11 | exit 12 | fi 13 | 14 | # URL of raw ncurses Caps file 15 | url="${1:-https://raw.githubusercontent.com/ThomasDickey/ncurses-snapshots/master/include/Caps}" 16 | 17 | # Use local Caps file if it exists 18 | if [ $# -eq 0 -a -f "Caps" ]; then 19 | fetch="cat Caps" 20 | else 21 | fetch="curl -sS '$url'" 22 | fi 23 | 24 | # Fetch file and do some light preformatting 25 | db=$( 26 | curl -sS "$url" | 27 | grep -v -e "^#" | 28 | awk '{o=$3" "$2" "$1; for(i=8;i<=NF;i++){o=o" "$i}; print o}') 29 | 30 | 31 | # Reformat lines into CPP defines 32 | def='{o="#define ti_"$3"\t"$1"\t//";for(i=5;i<=NF;i++){o=o" "$i};print o}' 33 | format_defines() { 34 | nl -s ' ' -w 1 -v 0 | 35 | awk "$def" | 36 | column -s' ' -t 37 | } 38 | 39 | echo "/*" 40 | echo " * Capability indexes generated with $0 at $(date '+%Y-%m-%dT%H:%M:%S %Z')" 41 | echo " *" 42 | echo " */" 43 | echo 44 | echo "// Boolean capability indexes" 45 | echo "$db" | grep '^bool ' | format_defines 46 | 47 | echo 48 | echo "// Numeric capability indexes" 49 | echo "$db" | grep '^num ' | format_defines 50 | 51 | echo 52 | echo "// String capability indexes" 53 | echo "$db" | grep '^str ' | format_defines 54 | -------------------------------------------------------------------------------- /tools/gencap-names.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #/ Usage: gencaps-names.sh [] 3 | #/ Generates terminfo capability name array constants for all terminfo 4 | #/ capabilities using the latest ncurses Caps database published to the 5 | #/ ThomasDickey/ncurses-snapshots repository on GitHub, or a custom URL 6 | #/ if provided. 7 | set -eu 8 | 9 | # Show usage 10 | if [ $# -gt 0 ] && [ "$1" = "--help" ]; then 11 | grep <"$0" '^#/' | cut -c4- 12 | exit 13 | fi 14 | 15 | # URL of raw ncurses Caps file 16 | url="${1:-https://raw.githubusercontent.com/ThomasDickey/ncurses-snapshots/master/include/Caps}" 17 | 18 | # Use local Caps file if it exists 19 | if [ $# -eq 0 -a -f "Caps" ]; then 20 | fetch="cat Caps" 21 | else 22 | fetch="curl -sS '$url'" 23 | fi 24 | 25 | # Fetch file and do some light preformatting 26 | db=$( 27 | $fetch | 28 | grep -v -e "^#" | 29 | awk '{o=$3" "$2" "$1; for(i=8;i<=NF;i++){o=o" "$i}; print o}') 30 | 31 | 32 | # Reformat lines into literal string array elements 33 | def='{o="\""$3"\",\t//";for(i=5;i<=NF;i++){o=o" "$i};print o}' 34 | format_names() { 35 | nl -s ' ' -w 1 -v 0 | 36 | awk '{ print "\t\"" $3 "\"," }' 37 | } 38 | 39 | echo "/*" 40 | echo " * Capability names generated with $0 at $(date '+%Y-%m-%dT%H:%M:%S %Z')" 41 | echo " *" 42 | echo " */" 43 | echo 44 | echo "// Boolean capability names" 45 | echo "static const char * const ti_boolnames[] = {" 46 | echo "$db" | grep '^bool ' | format_names | fmt 47 | echo "};" 48 | 49 | echo 50 | echo "// Numeric capability names" 51 | echo "static const char * const ti_numnames[] = {" 52 | echo "$db" | grep '^num ' | format_names | fmt 53 | echo "};" 54 | 55 | echo 56 | echo "// String capability names" 57 | echo "static const char * const ti_strnames[] = {" 58 | echo "$db" | grep '^str ' | format_names | fmt 59 | echo "};" 60 | -------------------------------------------------------------------------------- /utf8.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * utf8.c - Standalone utf8 char identification and codepoint conversion lib 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * Based originally on Termbox library utf8.c. 7 | * Copyright (C) 2010-2013 nsf 8 | * 9 | * See utf8.h for documentation and usage. 10 | * 11 | * 12 | */ 13 | 14 | #include // size_t 15 | #include // uint32_t 16 | 17 | #include "utf8.h" 18 | 19 | // Table of utf8 byte sequence lengths indexed by first byte value. 20 | // Invalid leading bytes are marked with a zero value. 21 | static const uint8_t utf8_length[256] = { 22 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x00 23 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x20 24 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x40 25 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x60 26 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x80 27 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xa0 28 | 0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xc0 29 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0, // 0xe0 30 | }; 31 | 32 | static const uint8_t utf8_mask[6] = { 0x7F, 0x1F, 0x0F, 0x07 }; 33 | 34 | int utf8_seq_len(char c) 35 | { 36 | return utf8_length[(uint8_t)c]; 37 | } 38 | 39 | int utf8_seq_to_codepoint(uint32_t *codepoint, const char *seq, size_t sz) 40 | { 41 | if (sz < 1) 42 | return 0; 43 | 44 | int len = utf8_seq_len(seq[0]); 45 | 46 | // invalid leading byte or not enough bytes 47 | if (len == 0 || len > (int)sz) 48 | return 0; 49 | 50 | uint8_t mask = utf8_mask[len-1]; 51 | uint32_t res = seq[0] & mask; 52 | for (int i = 1; i < len; ++i) { 53 | res <<= 6; 54 | res |= seq[i] & 0x3f; 55 | } 56 | 57 | *codepoint = res; 58 | return len; 59 | } 60 | 61 | int utf8_codepoint_to_seq(char *seq, uint32_t c) 62 | { 63 | int len = 0; 64 | int first; 65 | 66 | if (c < 0x80) { 67 | first = 0; 68 | len = 1; 69 | } else if (c < 0x800) { 70 | first = 0xc0; 71 | len = 2; 72 | } else if (c < 0x10000) { 73 | first = 0xe0; 74 | len = 3; 75 | } else if (c <= 0x10FFFF) { 76 | first = 0xf0; 77 | len = 4; 78 | } else { 79 | return 0; 80 | } 81 | 82 | for (int i = len - 1; i > 0; --i) { 83 | seq[i] = (c & 0x3f) | 0x80; 84 | c >>= 6; 85 | } 86 | seq[0] = c | first; 87 | 88 | return len; 89 | } 90 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * utf8.h - Standalone utf8 char identification and codepoint conversion lib 4 | * Copyright (c) 2020, Auxrelius I 5 | * 6 | * Based originally on Termbox library utf8.c. 7 | * Copyright (C) 2010-2013 nsf 8 | * 9 | * The utf8 library includes just enough utf8 features for basic terminal 10 | * input and output processing. 11 | * 12 | * The following headers must be included before utf8.h: 13 | * 14 | * #include 15 | * #include 16 | * 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include // size_t 23 | #include 24 | 25 | /* Illegal character replacement char */ 26 | #define UTF8_REPLACEMENT_CHAR "\xEF\xBF\xBD" // � U+FFFD 27 | 28 | /* 29 | * Returns the length in bytes of a utf8 character sequence with ch as the 30 | * first byte. 31 | */ 32 | int utf8_seq_len(char ch); 33 | 34 | /* Convert the utf8 byte sequence pointed to by seq to a 32bit integer unicode 35 | * codepoint. No more than len bytes will be consumed from the seq array. 36 | * 37 | * Returns the number of bytes read from the seq buffer on success. 38 | * Returns 0 when not enough bytes are available in the seq buffer or when the 39 | * first byte is not a valid leading byte value. 40 | */ 41 | int utf8_seq_to_codepoint(uint32_t *codepoint, const char *seq, size_t len); 42 | 43 | /* Convert a 32bit integer unicode codepoint to a utf8 byte sequence and write 44 | * to the char buffer pointed to by seq. No null terminator character is 45 | * written to the buffer. 46 | * 47 | * The buffer should be allocated to be at least 4 bytes unless the utf8 byte 48 | * length of the codepoint is determined to be less beforehand. 49 | * 50 | * Returns the number of bytes written to the seq buffer (1..4) on success. 51 | * Returns 0 when the codepoint value exceeds the 0x10FFFF maximum value. 52 | */ 53 | int utf8_codepoint_to_seq(char *seq, uint32_t codepoint); 54 | --------------------------------------------------------------------------------