├── .gitignore ├── LICENSE ├── README.md ├── lavat.c ├── makefile └── termbox.h /.gitignore: -------------------------------------------------------------------------------- 1 | lavat 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 AngelJumbo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lavat 2 | 3 | Little program that simulates a lava lamp in the terminal. 4 | ![demo](https://github.com/AngelJumbo/demos/blob/main/lavat/3.gif?raw=true) 5 | ## Installation 6 | 7 | Requirements: A Unix-like system, a C compiler and make. 8 | 9 | ``` 10 | git clone https://github.com/AngelJumbo/lavat 11 | cd lavat 12 | sudo make install 13 | ``` 14 | 15 | ### Arch Linux 16 | Lavat is also available on the AUR [here](https://aur.archlinux.org/packages/lavat-git). Install it with your favourite AUR-helper or manually. 17 | ``` 18 | $ paru -S lavat-git 19 | ``` 20 | ## Usage 21 | 22 | ``` 23 | Usage: lavat [OPTIONS] 24 | OPTIONS: 25 | -g Enable gradient mode with truecolor support. 26 | Changes how -c and -k options work. 27 | -c Set color. In normal mode, available colors are: red, blue, yellow, green, cyan, magenta, white, and black. 28 | In gradient mode (-g), use hex format: RRGGBB (e.g., FF0000 for red). 29 | -k Set the rim color. Same format options as -c. 30 | In gradient mode, this sets the second color for the gradient. 31 | -s Set the speed, from 1 to 10. (default 5) 32 | -r Set the radius of the metaballs, from 1 to 10. (default: 5) 33 | -R Set a rim for each metaball, sizes from 1 to 5.(default: none) 34 | This option does not work with the default color 35 | If you use Kitty or Alacritty you must use it with the -k option to see the rim. 36 | -b Set the number of metaballs in the simulation, from 5 to 20. (default: 10) 37 | -F Allows for a custom set of chars to be used 38 | Only ascii symbols are supported for now, wide/unicode chars may appear broken. 39 | -C Retain the entire lava inside the terminal. 40 | It may not work well with a lot of balls or with a bigger radius than the default one. 41 | -p PARTY!! THREE MODES AVAILABLE (p1, p2 and p3). 42 | -h Print help. 43 | RUNTIME CONTROLS: 44 | i Increase radius of the metaballs. 45 | d Decrease radius of the metaballs. 46 | shift i Increase rim of the metaballs. 47 | shift d Decrease rim of the metaballs. 48 | m Increase the number of metaballs. 49 | l Decrease the number metaballs. 50 | c Change the color of the metaballs. 51 | k Change the rim color of the metaballs. 52 | + Increase speed. 53 | - Decrease speed. 54 | p TURN ON THE PARTY AND CYCLE THROUGH THE PARTY MODES (it can also turns off the party). 55 | (Tip: Zoom out in your terminal before running the program to get a better resolution of the lava). 56 | EXAMPLES: 57 | lavat -c green -k red Use named colors in normal mode 58 | lavat -g -c 00FF00 -k FF0000 Use hex colors in gradient mode 59 | ``` 60 | 61 | ## Demo 62 | 63 | `lavat -p3` 64 | 65 | ![demo 1](https://github.com/AngelJumbo/demos/blob/main/lavat/6.gif?raw=true) 66 | 67 | PARTY MODE!!! 68 | 69 | `lavat -c red -R 1` 70 | 71 | ![demo 1](https://github.com/AngelJumbo/demos/blob/main/lavat/1.gif?raw=true) 72 | 73 | 74 | `lavat -c cyan -R 4 -b 20 -r 2` 75 | 76 | ![demo 2](https://github.com/AngelJumbo/demos/blob/main/lavat/2.gif?raw=true) 77 | 78 | If you send more than one character to the -F option you can have 3d-ish effect. 79 | 80 | `lavat -c blue -R2 -F @@:::::: -r10` 81 | 82 | ![demo 2](https://github.com/AngelJumbo/demos/blob/main/lavat/4.gif?raw=true) 83 | 84 | For the Alacritty and Kitty users I know that the -R option haven't been working for you, but now you can set the color of the rim independently. Try: 85 | 86 | `lavat -c yellow -R1 -k red` 87 | 88 | ![demo 2](https://github.com/AngelJumbo/demos/blob/main/lavat/5.gif?raw=true) 89 | 90 | (The colors depend on your color scheme.) 91 | 92 | ## Credits 93 | 94 | - This program is made with [Termbox2](https://github.com/termbox/termbox2). 95 | - [Lava lamp in JavaScript](https://codeguppy.com/site/tutorials/lava-lamp.html) 96 | -------------------------------------------------------------------------------- /lavat.c: -------------------------------------------------------------------------------- 1 | #define TB_IMPL 2 | #define TB_LIB_OPTS 3 | #define TB_OPT_TRUECOLOR 4 | 5 | #include "termbox.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define MIN_NBALLS 5 14 | #define MAX_NBALLS 20 15 | 16 | typedef struct { 17 | int x; 18 | int y; 19 | int dx; 20 | int dy; 21 | } Ball; 22 | 23 | uintattr_t pallete[11]; 24 | int baseColor[3]; 25 | int baseColor2[3]; 26 | short gradient1=0; 27 | short gradient2=0; 28 | 29 | static char *custom = NULL; 30 | static char *custom2 = NULL; 31 | static short color = TB_WHITE; 32 | static short color2 = TB_WHITE; 33 | static short party = 0; 34 | static int nballs = 10; 35 | static short speedMult = 5; 36 | static short rim = 1; 37 | static short contained = 0; 38 | static float radiusIn = 110; 39 | static float radius; 40 | static int margin; 41 | static float sumConst; 42 | static float sumConst2; 43 | static int maxX, maxY; 44 | static int speed; 45 | static Ball balls[MAX_NBALLS] = {0}; 46 | static struct tb_event event = {0}; 47 | static short colors[]={TB_WHITE, TB_RED, TB_YELLOW, TB_BLUE, TB_GREEN, TB_MAGENTA, TB_CYAN, TB_BLACK }; 48 | 49 | void init_params(); 50 | void event_handler(); 51 | int parse_options(int argc, char *argv[]); 52 | void print_help(); 53 | short next_color(short current); 54 | void fix_rim_color(); 55 | void set_random_colors(short level); 56 | 57 | void set_pallete(); 58 | uintattr_t get_color(float val); 59 | void set_pallete2(); 60 | 61 | int set_color(short *var, int *baseColor, char *optarg, short useGradient) { 62 | if (useGradient) { 63 | // Parse hex color 64 | if (sscanf(optarg, "%02x%02x%02x", &baseColor[0], &baseColor[1], &baseColor[2]) == 3) { 65 | return 1; 66 | } else { 67 | printf("Invalid hex color format. Use format: RRGGBB\n"); 68 | return 0; 69 | } 70 | } else { 71 | // Parse named color 72 | if (strcmp(optarg, "red") == 0) { 73 | *var = TB_RED; 74 | } else if (strcmp(optarg, "yellow") == 0) { 75 | *var = TB_YELLOW; 76 | } else if (strcmp(optarg, "blue") == 0) { 77 | *var = TB_BLUE; 78 | } else if (strcmp(optarg, "green") == 0) { 79 | *var = TB_GREEN; 80 | } else if (strcmp(optarg, "magenta") == 0) { 81 | *var = TB_MAGENTA; 82 | } else if (strcmp(optarg, "cyan") == 0) { 83 | *var = TB_CYAN; 84 | } else if (strcmp(optarg, "black") == 0) { 85 | *var = TB_BLACK; 86 | } else if (strcmp(optarg, "white") == 0) { 87 | *var = TB_WHITE; 88 | } else { 89 | printf("Unknown color: %s\n", optarg); 90 | return 0; 91 | } 92 | return 1; 93 | } 94 | } 95 | 96 | int main(int argc, char *argv[]) { 97 | 98 | if (!parse_options(argc, argv)) 99 | return 0; 100 | 101 | time_t t; 102 | // Ball *balls = malloc(sizeof(Ball) * nballs); 103 | 104 | srand((unsigned)time(&t)); 105 | 106 | tb_init(); 107 | 108 | tb_hide_cursor(); 109 | 110 | init_params(); 111 | 112 | 113 | while (1) { 114 | 115 | // move balls 116 | for (int i = 0; i < nballs; i++) { 117 | 118 | if (balls[i].x + balls[i].dx >= maxX - margin || 119 | balls[i].x + balls[i].dx < margin) 120 | balls[i].dx *= -1; 121 | 122 | if (balls[i].y + balls[i].dy >= maxY - margin || 123 | balls[i].y + balls[i].dy < margin) 124 | balls[i].dy *= -1; 125 | 126 | balls[i].x += balls[i].dx; 127 | balls[i].y += balls[i].dy; 128 | } 129 | 130 | // render 131 | for (int i = 0; i < maxX; i++) { 132 | for (int j = 0; j < maxY / 2; j++) { 133 | // calculate the two halfs of the block at the same time 134 | float sum[2] = {0}; 135 | 136 | for (int j2 = 0; j2 < (!custom ? 2 : 1); j2++) { 137 | 138 | for (int k = 0; k < nballs; k++) { 139 | int y = j * 2 + j2; 140 | float dist_squared = (i - balls[k].x) * (i - balls[k].x) + (y - balls[k].y) * (y - balls[k].y); 141 | if (dist_squared == 0) { 142 | sum[j2] += FLT_MAX; 143 | } else { 144 | sum[j2] += (radius * radius) / dist_squared; 145 | } 146 | } 147 | } 148 | 149 | if (!custom) { 150 | if(gradient1){ 151 | if (sum[0] > sumConst) { 152 | if (sum[1] > sumConst) { 153 | tb_printf(i, j, get_color( sum[0]), get_color(sum[1]), "▀"); 154 | } else { 155 | tb_printf(i, j, get_color( sum[0]), TB_TRUECOLOR_DEFAULT, "▀"); 156 | } 157 | } else if (sum[1] > sumConst) { 158 | tb_printf(i, j,get_color( sum[1]),TB_TRUECOLOR_DEFAULT, "▄"); 159 | } 160 | 161 | }else{ 162 | if (sum[0] > sumConst) { 163 | if (sum[1] > sumConst) { 164 | tb_printf(i, j, color2, 0, "█"); 165 | } else { 166 | tb_printf(i, j, color2, 0, "▀"); 167 | } 168 | } else if (sum[1] > sumConst) { 169 | tb_printf(i, j, color2, 0, "▄"); 170 | } 171 | 172 | if (rim) { 173 | if (sum[0] > sumConst2) { 174 | if (sum[1] > sumConst2) { 175 | tb_printf(i, j, color, 0, "█"); 176 | } else { 177 | tb_printf(i, j, color2, color, "▄"); 178 | } 179 | } else if (sum[1] > sumConst2) { 180 | tb_printf(i, j, color2, color, "▀"); 181 | } 182 | } 183 | } 184 | } else { 185 | if (sum[0] > sumConst) { 186 | tb_printf(i, j, color2, 0, custom2); 187 | } 188 | 189 | if (rim) { 190 | if (sum[0] > sumConst2) { 191 | tb_printf(i, j, color, 0, custom); 192 | } 193 | } 194 | } 195 | } 196 | } 197 | if (party>0){ 198 | set_random_colors(party); 199 | } 200 | tb_present(); 201 | usleep(speed); 202 | tb_clear(); 203 | 204 | tb_peek_event(&event, 10); 205 | 206 | event_handler(); 207 | } 208 | 209 | tb_shutdown(); 210 | 211 | // free(balls); 212 | } 213 | 214 | void event_handler() { 215 | if (event.type == TB_EVENT_RESIZE) { 216 | do 217 | tb_peek_event(&event, 10); 218 | while (event.type == TB_EVENT_RESIZE); 219 | 220 | init_params(); 221 | } else if (event.type == TB_EVENT_KEY) { 222 | 223 | if (event.key == TB_KEY_CTRL_C || event.key == TB_KEY_ESC) { 224 | tb_shutdown(); 225 | exit(0); 226 | } 227 | 228 | switch (event.ch) { 229 | case '-': 230 | case '_': 231 | if (speedMult < 10) { 232 | speedMult++; 233 | speed = (((1 / (float)(maxX + maxY)) * 1000000) + 10000) * speedMult; 234 | } 235 | break; 236 | case '+': 237 | case '=': 238 | if (speedMult > 1) { 239 | speedMult--; 240 | speed = (((1 / (float)(maxX + maxY)) * 1000000) + 10000) * speedMult; 241 | } 242 | break; 243 | case 'm': 244 | case 'M': 245 | if (nballs + 1 <= MAX_NBALLS) { 246 | nballs++; 247 | } 248 | break; 249 | case 'l': 250 | case 'L': 251 | if (nballs - 1 >= MIN_NBALLS) { 252 | nballs--; 253 | } 254 | break; 255 | case 'i': 256 | if (radiusIn + 5 <= 150) { 257 | radiusIn += 5; 258 | radius = (radiusIn * radiusIn + (float)(maxX * maxY)) / 15000; 259 | margin = contained ? radius * 10 : 0; 260 | } 261 | break; 262 | case 'd': 263 | if (radiusIn - 5 >= 100) { 264 | radiusIn -= 5; 265 | radius = (radiusIn * radiusIn + (float)(maxX * maxY)) / 15000; 266 | margin = contained ? radius * 10 : 0; 267 | } 268 | break; 269 | case 'I': 270 | 271 | if (color != TB_WHITE || custom || gradient1 ) 272 | if (rim + 1 <= 5) { 273 | rim++; 274 | sumConst2 = sumConst * (1 + (float)(0.25 * rim)); 275 | } 276 | break; 277 | case 'D': 278 | 279 | if (color != TB_WHITE || custom || gradient1) 280 | if (rim - 1 >= 0) { 281 | rim--; 282 | sumConst2 = sumConst * (1 + (float)(0.25 * rim)); 283 | } 284 | break; 285 | case 'c': 286 | color = next_color(color); 287 | fix_rim_color(); 288 | break; 289 | case 'k': 290 | color2 = next_color(color2); 291 | fix_rim_color(); 292 | break; 293 | case 'p': 294 | party = (party+1)%4; 295 | break; 296 | case 'q': 297 | case 'Q': 298 | tb_shutdown(); 299 | exit(0); 300 | break; 301 | } 302 | } 303 | } 304 | 305 | void init_params() { 306 | 307 | maxX = tb_width(); 308 | maxY = tb_height() * 2; 309 | speedMult = 11 - speedMult; 310 | speed = (((1 / (float)(maxX + maxY)) * 1000000) + 10000) * speedMult; 311 | radius = (radiusIn * radiusIn + (float)(maxX * maxY)) / 15000; 312 | 313 | margin = contained ? radius * 10 : 0; 314 | 315 | sumConst = 0.0225; 316 | sumConst2 = sumConst * (1 + (float)(0.25 * rim)); 317 | 318 | custom2 = custom; 319 | 320 | if (color2 == TB_WHITE || !rim) 321 | color2 = color | TB_BOLD; 322 | 323 | if (custom && strlen(custom) > 1 && rim) { 324 | custom2 = custom + 1; 325 | } 326 | 327 | for (int i = 0; i < MAX_NBALLS; i++) { 328 | balls[i].x = rand() % (maxX - 2 * margin) + margin; 329 | balls[i].y = rand() % (maxY - 2 * margin) + margin; 330 | balls[i].dx = (rand() % 2 == 0) ? -1 : 1; 331 | balls[i].dy = (rand() % 2 == 0) ? -1 : 1; 332 | } 333 | if(gradient1){ 334 | tb_set_output_mode(TB_OUTPUT_TRUECOLOR); 335 | tb_set_clear_attrs(TB_TRUECOLOR_DEFAULT, TB_TRUECOLOR_DEFAULT); 336 | if(gradient2) set_pallete2(); 337 | else set_pallete(); 338 | } 339 | } 340 | 341 | short next_color(short current){ 342 | for(int i = 0; i<8; i++){ 343 | if((current == colors[i])||(current == (colors[i] | TB_BOLD ))){ 344 | return colors[(i+1)%8]; 345 | } 346 | } 347 | return colors[0]; 348 | } 349 | 350 | void fix_rim_color(){ 351 | if(color2 == color){ 352 | color2 = color2 | TB_BOLD; 353 | } 354 | } 355 | 356 | void set_random_colors( short level){ 357 | if(level==1 || level==3) color = colors[ rand() % 7]; 358 | if(level==2 || level==3) color2 = colors[ rand() % 7]; 359 | fix_rim_color(); 360 | } 361 | 362 | int parse_options(int argc, char *argv[]) { 363 | if (argc == 1) 364 | return 1; 365 | 366 | int c; 367 | // First pass to check for gradient mode 368 | optind = 1; // Reset getopt 369 | while ((c = getopt(argc, argv, ":c:k:s:r:R:b:F:Cp:hg")) != -1) { 370 | if (c == 'g') { 371 | gradient1 = 1; 372 | if (!tb_has_truecolor()) { 373 | fprintf(stderr, "The terminal does not support truecolor\n"); 374 | return 0; 375 | } 376 | break; 377 | } 378 | } 379 | 380 | // Reset getopt for second pass 381 | optind = 1; 382 | while ((c = getopt(argc, argv, ":c:k:s:r:R:b:F:Cp:hg")) != -1) { 383 | switch (c) { 384 | case 'c': 385 | if (!set_color(&color, baseColor, optarg, gradient1)) 386 | return 0; 387 | break; 388 | case 'k': 389 | if (!set_color(&color2, baseColor2, optarg, gradient1)) 390 | return 0; 391 | gradient2 = gradient1; // If we're using gradient, enable the second gradient too 392 | break; 393 | case 's': 394 | speedMult = atoi(optarg); 395 | if (speedMult > 10 || speedMult <= 0) { 396 | printf("Invalid speed, only values between 1 and 10 are allowed\n"); 397 | return 0; 398 | } 399 | break; 400 | case 'R': 401 | rim = atoi(optarg); 402 | if (rim > 5 || rim < 1) { 403 | printf("Invalid rim, only values between 1 and 5 are allowed\n"); 404 | return 0; 405 | } 406 | break; 407 | case 'r': 408 | radiusIn = 100 + atoi(optarg) * 5; 409 | if (radiusIn > 150 || radiusIn < 100) { 410 | printf("Invalid radius, only values between 1 and 10 are allowed\n"); 411 | return 0; 412 | } 413 | break; 414 | case 'b': 415 | nballs = atoi(optarg); 416 | if (nballs > MAX_NBALLS || nballs < MIN_NBALLS) { 417 | printf("Invalid number of metaballs, only values between %i and %i are " 418 | "allowed\n", 419 | MIN_NBALLS, MAX_NBALLS); 420 | return 0; 421 | } 422 | break; 423 | case 'F': 424 | custom = optarg; 425 | break; 426 | case 'C': 427 | contained = 1; 428 | break; 429 | case 'p': 430 | party = atoi(optarg); 431 | if (party < 0 || party > 3) { 432 | printf("Invalid party mode, only values between 1 and 3 are allowed\n"); 433 | return 0; 434 | } 435 | break; 436 | case 'h': 437 | print_help(); 438 | return 0; 439 | break; 440 | case 'g': 441 | // Already handled in first pass 442 | break; 443 | case ':': 444 | fprintf(stderr, "Option -%c requires an operand\n", optopt); 445 | return 0; 446 | break; 447 | case '?': 448 | fprintf(stderr, "Unrecognized option: -%c\n", optopt); 449 | return 0; 450 | } 451 | } 452 | return 1; 453 | } 454 | 455 | void print_help() { 456 | printf( 457 | "Usage: lavat [OPTIONS]\n" 458 | "OPTIONS:\n" 459 | " -g Enable gradient mode with truecolor support.\n" 460 | " Changes how -c and -k options work.\n" 461 | " -c Set color. In normal mode, available colors are: red, blue, yellow, " 462 | "green, cyan, magenta, white, and black.\n" 463 | " In gradient mode (-g), use hex format: RRGGBB (e.g., FF0000 for red).\n" 464 | " -k Set the rim color. Same format options as -c.\n" 465 | " In gradient mode, this sets the second color for the gradient.\n" 466 | " -s Set the speed, from 1 to 10. (default 5)\n" 467 | " -r Set the radius of the metaballs, from 1 to 10. " 468 | "(default: 5)\n" 469 | " -R Set a rim for each metaball, sizes from 1 to 5." 470 | "(default: none)\n" 471 | " This option does not work with the default " 472 | "color\n" 473 | " If you use Kitty or Alacritty you must use it " 474 | "with the -k option to see the rim.\n" 475 | " -b Set the number of metaballs in the simulation, " 476 | "from %i to %i. (default: 10)\n" 477 | " -F Allows for a custom set of chars to be used\n" 478 | " Only ascii symbols are supported for now, " 479 | "wide/unicode chars may appear broken.\n" 480 | " -C Retain the entire lava inside the terminal.\n" 481 | " It may not work well with a lot of balls or with" 482 | " a bigger radius than the default one.\n" 483 | " -p PARTY!! THREE MODES AVAILABLE (p1, p2 and p3).\n" 484 | " -h Print help.\n" 485 | "RUNTIME CONTROLS:\n" 486 | " i Increase radius of the metaballs.\n" 487 | " d Decrease radius of the metaballs.\n" 488 | " shift i Increase rim of the metaballs.\n" 489 | " shift d Decrease rim of the metaballs.\n" 490 | " m Increase the number of metaballs.\n" 491 | " l Decrease the number metaballs.\n" 492 | " c Change the color of the metaballs.\n" 493 | " k Change the rim color of the metaballs.\n" 494 | " + Increase speed.\n" 495 | " - Decrease speed.\n" 496 | " p TURN ON THE PARTY AND CYCLE THROUGH THE PARTY MODES " 497 | "(it can also turns off the party).\n" 498 | "(Tip: Zoom out in your terminal before running the program to get a " 499 | "better resolution of the lava).\n" 500 | "EXAMPLES:\n" 501 | " lavat -c green -k red Use named colors in normal mode\n" 502 | " lavat -g -c 00FF00 -k FF0000 Use hex colors in gradient mode\n", 503 | MIN_NBALLS, MAX_NBALLS); 504 | } 505 | 506 | void set_pallete(){ 507 | int avgColor= (baseColor[0] + baseColor[1] + baseColor[2])/3; 508 | int blackfactor[5]; 509 | int whitefactor[5]; 510 | for(int i=1 ;i<6;i++){ 511 | blackfactor[i-1]=(6-i)*avgColor/5; 512 | whitefactor[i-1]=i*(255-avgColor)/5; 513 | } 514 | 515 | for(int i=0 ;i<5;i++){ 516 | int r, g, b; 517 | float factor = (1 - ((float)blackfactor[i]/(avgColor))); 518 | r = baseColor[0]*factor; 519 | g = baseColor[1]*factor; 520 | b = baseColor[2]*factor; 521 | pallete[i]= (r << 16) | (g << 8) | b; 522 | r = baseColor[0] + (255-baseColor[0])*whitefactor[i]/(255-avgColor); 523 | g = baseColor[1] + (255-baseColor[1])*whitefactor[i]/(255-avgColor); 524 | b = baseColor[2] + (255-baseColor[2])*whitefactor[i]/(255-avgColor); 525 | pallete[i+6] = (r << 16) | (g << 8) | b; 526 | 527 | } 528 | pallete[5]= (baseColor[0] << 16) | (baseColor[1] << 8) | baseColor[2]; 529 | } 530 | 531 | void set_pallete2() { 532 | for (int i = 0; i < 11; i++) { 533 | float t = (float)i / (11 - 1); 534 | 535 | int r = (1 - t) * baseColor[0] + t * baseColor2[0]; 536 | int g = (1 - t) * baseColor[1] + t * baseColor2[1]; 537 | int b = (1 - t) * baseColor[2] + t * baseColor2[2]; 538 | 539 | pallete[i] = (r << 16) | (g << 8) | b; 540 | } 541 | } 542 | 543 | uintattr_t get_color(float val) { 544 | val = (val - sumConst)/(0.001*(rim+1)); 545 | 546 | if (val > 10) { 547 | return pallete[10]; 548 | } else if (val < 0) { 549 | return pallete[0]; 550 | } 551 | 552 | return pallete[(int)val]; 553 | } 554 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PREFIX = /usr/local 2 | 3 | lavat: lavat.c 4 | $(CC) lavat.c -o lavat 5 | 6 | .PHONY: clean 7 | clean: 8 | $(RM) lavat 9 | 10 | .PHONY: install 11 | install: lavat 12 | mkdir -p $(PREFIX)/bin 13 | install lavat $(PREFIX)/bin/lavat 14 | 15 | .PHONY: uninstall 16 | uninstall: 17 | $(RM) $(PREFIX)/bin/lavat 18 | 19 | -------------------------------------------------------------------------------- /termbox.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2010-2020 nsf 5 | 2015-2022 Adam Saponara 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #ifndef __TERMBOX_H 27 | #define __TERMBOX_H 28 | 29 | #ifndef _XOPEN_SOURCE 30 | #define _XOPEN_SOURCE 31 | #endif 32 | 33 | #ifndef _DEFAULT_SOURCE 34 | #define _DEFAULT_SOURCE 35 | #endif 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #ifdef __cplusplus 56 | extern "C" { 57 | #endif 58 | 59 | // __ffi_start 60 | 61 | #define TB_VERSION_STR "2.1.0-dev" 62 | 63 | #if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts 64 | // Ensure consistent compile-time options when using as a library 65 | #undef TB_OPT_TRUECOLOR 66 | #undef TB_OPT_EGC 67 | #undef TB_OPT_PRINTF_BUF 68 | #undef TB_OPT_READ_BUF 69 | #define TB_OPT_TRUECOLOR 70 | #define TB_OPT_EGC 71 | #endif 72 | 73 | /* ASCII key constants (tb_event.key) */ 74 | #define TB_KEY_CTRL_TILDE 0x00 75 | #define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ 76 | #define TB_KEY_CTRL_A 0x01 77 | #define TB_KEY_CTRL_B 0x02 78 | #define TB_KEY_CTRL_C 0x03 79 | #define TB_KEY_CTRL_D 0x04 80 | #define TB_KEY_CTRL_E 0x05 81 | #define TB_KEY_CTRL_F 0x06 82 | #define TB_KEY_CTRL_G 0x07 83 | #define TB_KEY_BACKSPACE 0x08 84 | #define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ 85 | #define TB_KEY_TAB 0x09 86 | #define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */ 87 | #define TB_KEY_CTRL_J 0x0a 88 | #define TB_KEY_CTRL_K 0x0b 89 | #define TB_KEY_CTRL_L 0x0c 90 | #define TB_KEY_ENTER 0x0d 91 | #define TB_KEY_CTRL_M 0x0d /* clash with 'ENTER' */ 92 | #define TB_KEY_CTRL_N 0x0e 93 | #define TB_KEY_CTRL_O 0x0f 94 | #define TB_KEY_CTRL_P 0x10 95 | #define TB_KEY_CTRL_Q 0x11 96 | #define TB_KEY_CTRL_R 0x12 97 | #define TB_KEY_CTRL_S 0x13 98 | #define TB_KEY_CTRL_T 0x14 99 | #define TB_KEY_CTRL_U 0x15 100 | #define TB_KEY_CTRL_V 0x16 101 | #define TB_KEY_CTRL_W 0x17 102 | #define TB_KEY_CTRL_X 0x18 103 | #define TB_KEY_CTRL_Y 0x19 104 | #define TB_KEY_CTRL_Z 0x1a 105 | #define TB_KEY_ESC 0x1b 106 | #define TB_KEY_CTRL_LSQ_BRACKET 0x1b /* clash with 'ESC' */ 107 | #define TB_KEY_CTRL_3 0x1b /* clash with 'ESC' */ 108 | #define TB_KEY_CTRL_4 0x1c 109 | #define TB_KEY_CTRL_BACKSLASH 0x1c /* clash with 'CTRL_4' */ 110 | #define TB_KEY_CTRL_5 0x1d 111 | #define TB_KEY_CTRL_RSQ_BRACKET 0x1d /* clash with 'CTRL_5' */ 112 | #define TB_KEY_CTRL_6 0x1e 113 | #define TB_KEY_CTRL_7 0x1f 114 | #define TB_KEY_CTRL_SLASH 0x1f /* clash with 'CTRL_7' */ 115 | #define TB_KEY_CTRL_UNDERSCORE 0x1f /* clash with 'CTRL_7' */ 116 | #define TB_KEY_SPACE 0x20 117 | #define TB_KEY_BACKSPACE2 0x7f 118 | #define TB_KEY_CTRL_8 0x7f /* clash with 'BACKSPACE2' */ 119 | 120 | #define tb_key_i(i) 0xffff - (i) 121 | /* Terminal-dependent key constants (tb_event.key) and terminfo capabilities */ 122 | /* BEGIN codegen h */ 123 | /* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:02 +0000 */ 124 | #define TB_KEY_F1 (0xffff - 0) 125 | #define TB_KEY_F2 (0xffff - 1) 126 | #define TB_KEY_F3 (0xffff - 2) 127 | #define TB_KEY_F4 (0xffff - 3) 128 | #define TB_KEY_F5 (0xffff - 4) 129 | #define TB_KEY_F6 (0xffff - 5) 130 | #define TB_KEY_F7 (0xffff - 6) 131 | #define TB_KEY_F8 (0xffff - 7) 132 | #define TB_KEY_F9 (0xffff - 8) 133 | #define TB_KEY_F10 (0xffff - 9) 134 | #define TB_KEY_F11 (0xffff - 10) 135 | #define TB_KEY_F12 (0xffff - 11) 136 | #define TB_KEY_INSERT (0xffff - 12) 137 | #define TB_KEY_DELETE (0xffff - 13) 138 | #define TB_KEY_HOME (0xffff - 14) 139 | #define TB_KEY_END (0xffff - 15) 140 | #define TB_KEY_PGUP (0xffff - 16) 141 | #define TB_KEY_PGDN (0xffff - 17) 142 | #define TB_KEY_ARROW_UP (0xffff - 18) 143 | #define TB_KEY_ARROW_DOWN (0xffff - 19) 144 | #define TB_KEY_ARROW_LEFT (0xffff - 20) 145 | #define TB_KEY_ARROW_RIGHT (0xffff - 21) 146 | #define TB_KEY_BACK_TAB (0xffff - 22) 147 | #define TB_KEY_MOUSE_LEFT (0xffff - 23) 148 | #define TB_KEY_MOUSE_RIGHT (0xffff - 24) 149 | #define TB_KEY_MOUSE_MIDDLE (0xffff - 25) 150 | #define TB_KEY_MOUSE_RELEASE (0xffff - 26) 151 | #define TB_KEY_MOUSE_WHEEL_UP (0xffff - 27) 152 | #define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28) 153 | 154 | #define TB_CAP_F1 0 155 | #define TB_CAP_F2 1 156 | #define TB_CAP_F3 2 157 | #define TB_CAP_F4 3 158 | #define TB_CAP_F5 4 159 | #define TB_CAP_F6 5 160 | #define TB_CAP_F7 6 161 | #define TB_CAP_F8 7 162 | #define TB_CAP_F9 8 163 | #define TB_CAP_F10 9 164 | #define TB_CAP_F11 10 165 | #define TB_CAP_F12 11 166 | #define TB_CAP_INSERT 12 167 | #define TB_CAP_DELETE 13 168 | #define TB_CAP_HOME 14 169 | #define TB_CAP_END 15 170 | #define TB_CAP_PGUP 16 171 | #define TB_CAP_PGDN 17 172 | #define TB_CAP_ARROW_UP 18 173 | #define TB_CAP_ARROW_DOWN 19 174 | #define TB_CAP_ARROW_LEFT 20 175 | #define TB_CAP_ARROW_RIGHT 21 176 | #define TB_CAP_BACK_TAB 22 177 | #define TB_CAP__COUNT_KEYS 23 178 | #define TB_CAP_ENTER_CA 23 179 | #define TB_CAP_EXIT_CA 24 180 | #define TB_CAP_SHOW_CURSOR 25 181 | #define TB_CAP_HIDE_CURSOR 26 182 | #define TB_CAP_CLEAR_SCREEN 27 183 | #define TB_CAP_SGR0 28 184 | #define TB_CAP_UNDERLINE 29 185 | #define TB_CAP_BOLD 30 186 | #define TB_CAP_BLINK 31 187 | #define TB_CAP_ITALIC 32 188 | #define TB_CAP_REVERSE 33 189 | #define TB_CAP_ENTER_KEYPAD 34 190 | #define TB_CAP_EXIT_KEYPAD 35 191 | #define TB_CAP__COUNT 36 192 | /* END codegen h */ 193 | 194 | /* Some hard-coded caps */ 195 | #define TB_HARDCAP_ENTER_MOUSE "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" 196 | #define TB_HARDCAP_EXIT_MOUSE "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" 197 | 198 | /* Colors (numeric) and attributes (bitwise) (tb_cell.fg, tb_cell.bg) */ 199 | #define TB_BLACK 0x0001 200 | #define TB_RED 0x0002 201 | #define TB_GREEN 0x0003 202 | #define TB_YELLOW 0x0004 203 | #define TB_BLUE 0x0005 204 | #define TB_MAGENTA 0x0006 205 | #define TB_CYAN 0x0007 206 | #define TB_WHITE 0x0008 207 | #define TB_BOLD 0x0100 208 | #define TB_UNDERLINE 0x0200 209 | #define TB_REVERSE 0x0400 210 | #define TB_ITALIC 0x0800 211 | #define TB_BLINK 0x1000 212 | #define TB_DEFAULT 0x2000 213 | #ifdef TB_OPT_TRUECOLOR 214 | #define TB_TRUECOLOR_BOLD 0x01000000 215 | #define TB_TRUECOLOR_UNDERLINE 0x02000000 216 | #define TB_TRUECOLOR_REVERSE 0x04000000 217 | #define TB_TRUECOLOR_ITALIC 0x08000000 218 | #define TB_TRUECOLOR_BLINK 0x10000000 219 | #define TB_TRUECOLOR_DEFAULT 0x20000000 220 | #endif 221 | 222 | /* Event types (tb_event.type) */ 223 | #define TB_EVENT_KEY 1 224 | #define TB_EVENT_RESIZE 2 225 | #define TB_EVENT_MOUSE 3 226 | 227 | /* Key modifiers (bitwise) (tb_event.mod) */ 228 | #define TB_MOD_ALT 1 229 | #define TB_MOD_CTRL 2 230 | #define TB_MOD_SHIFT 4 231 | #define TB_MOD_MOTION 8 232 | 233 | /* Input modes (bitwise) (tb_set_input_mode) */ 234 | #define TB_INPUT_CURRENT 0 235 | #define TB_INPUT_ESC 1 236 | #define TB_INPUT_ALT 2 237 | #define TB_INPUT_MOUSE 4 238 | 239 | /* Output modes (tb_set_output_mode) */ 240 | #define TB_OUTPUT_CURRENT 0 241 | #define TB_OUTPUT_NORMAL 1 242 | #define TB_OUTPUT_256 2 243 | #define TB_OUTPUT_216 3 244 | #define TB_OUTPUT_GRAYSCALE 4 245 | #ifdef TB_OPT_TRUECOLOR 246 | #define TB_OUTPUT_TRUECOLOR 5 247 | #endif 248 | 249 | /* Common function return values unless otherwise noted. 250 | * 251 | * Library behavior is undefined after receiving TB_ERR_MEM. Callers may 252 | * attempt reinitializing by freeing memory, invoking tb_shutdown, then 253 | * tb_init. 254 | */ 255 | #define TB_OK 0 256 | #define TB_ERR -1 257 | #define TB_ERR_NEED_MORE -2 258 | #define TB_ERR_INIT_ALREADY -3 259 | #define TB_ERR_INIT_OPEN -4 260 | #define TB_ERR_MEM -5 261 | #define TB_ERR_NO_EVENT -6 262 | #define TB_ERR_NO_TERM -7 263 | #define TB_ERR_NOT_INIT -8 264 | #define TB_ERR_OUT_OF_BOUNDS -9 265 | #define TB_ERR_READ -10 266 | #define TB_ERR_RESIZE_IOCTL -11 267 | #define TB_ERR_RESIZE_PIPE -12 268 | #define TB_ERR_RESIZE_SIGACTION -13 269 | #define TB_ERR_POLL -14 270 | #define TB_ERR_TCGETATTR -15 271 | #define TB_ERR_TCSETATTR -16 272 | #define TB_ERR_UNSUPPORTED_TERM -17 273 | #define TB_ERR_RESIZE_WRITE -18 274 | #define TB_ERR_RESIZE_POLL -19 275 | #define TB_ERR_RESIZE_READ -20 276 | #define TB_ERR_RESIZE_SSCANF -21 277 | #define TB_ERR_CAP_COLLISION -22 278 | 279 | #define TB_ERR_SELECT TB_ERR_POLL 280 | #define TB_ERR_RESIZE_SELECT TB_ERR_RESIZE_POLL 281 | 282 | /* Function types to be used with tb_set_func() */ 283 | #define TB_FUNC_EXTRACT_PRE 0 284 | #define TB_FUNC_EXTRACT_POST 1 285 | 286 | /* Define this to set the size of the buffer used in tb_printf() 287 | * and tb_sendf() 288 | */ 289 | #ifndef TB_OPT_PRINTF_BUF 290 | #define TB_OPT_PRINTF_BUF 4096 291 | #endif 292 | 293 | /* Define this to set the size of the read buffer used when reading 294 | * from the tty 295 | */ 296 | #ifndef TB_OPT_READ_BUF 297 | #define TB_OPT_READ_BUF 64 298 | #endif 299 | 300 | /* Define this for limited back compat with termbox v1 */ 301 | #ifdef TB_OPT_V1_COMPAT 302 | #define tb_change_cell tb_set_cell 303 | #define tb_put_cell(x, y, c) tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg) 304 | #define tb_set_clear_attributes tb_set_clear_attrs 305 | #define tb_select_input_mode tb_set_input_mode 306 | #define tb_select_output_mode tb_set_output_mode 307 | #endif 308 | 309 | /* Define these to swap in a different allocator */ 310 | #ifndef tb_malloc 311 | #define tb_malloc malloc 312 | #define tb_realloc realloc 313 | #define tb_free free 314 | #endif 315 | 316 | #ifdef TB_OPT_TRUECOLOR 317 | typedef uint32_t uintattr_t; 318 | #else 319 | typedef uint16_t uintattr_t; 320 | #endif 321 | 322 | /* The terminal screen is represented as 2d array of cells. The structure is 323 | * optimized for dealing with single-width (wcwidth()==1) Unicode code points, 324 | * however some support for grapheme clusters (e.g., combining diacritical 325 | * marks) and wide code points (e.g., Hiragana) is provided through ech, nech, 326 | * cech via tb_set_cell_ex(). ech is only valid when nech>0, otherwise ch is 327 | * used. 328 | * 329 | * For non-single-width code points, given N=wcwidth(ch)/wcswidth(ech): 330 | * 331 | * when N==0: termbox forces a single-width cell. Callers should avoid this 332 | * if aiming to render text accurately. 333 | * 334 | * when N>1: termbox zeroes out the following N-1 cells and skips sending 335 | * them to the tty. So, e.g., if the caller sets x=0,y=0 to an N==2 336 | * code point, the caller's next set should be at x=2,y=0. Anything 337 | * set at x=1,y=0 will be ignored. If there are not enough columns 338 | * remaining on the line to render N width, spaces are sent 339 | * instead. 340 | * 341 | * See tb_present() for implementation. 342 | */ 343 | struct tb_cell { 344 | uint32_t ch; /* a Unicode character */ 345 | uintattr_t fg; /* bitwise foreground attributes */ 346 | uintattr_t bg; /* bitwise background attributes */ 347 | #ifdef TB_OPT_EGC 348 | uint32_t *ech; /* a grapheme cluster of Unicode code points */ 349 | size_t nech; /* length in bytes of ech, 0 means use ch instead of ech */ 350 | size_t cech; /* capacity in bytes of ech */ 351 | #endif 352 | }; 353 | 354 | /* An incoming event from the tty. 355 | * 356 | * Given the event type, the following fields are relevant: 357 | * 358 | * when TB_EVENT_KEY: (key XOR ch, one will be zero), mod. Note there is 359 | * overlap between TB_MOD_CTRL and TB_KEY_CTRL_*. 360 | * TB_MOD_CTRL and TB_MOD_SHIFT are only set as 361 | * modifiers to TB_KEY_ARROW_*. 362 | * 363 | * when TB_EVENT_RESIZE: w, h 364 | * 365 | * when TB_EVENT_MOUSE: key (TB_KEY_MOUSE_*), x, y 366 | */ 367 | struct tb_event { 368 | uint8_t type; /* one of TB_EVENT_* constants */ 369 | uint8_t mod; /* bitwise TB_MOD_* constants */ 370 | uint16_t key; /* one of TB_KEY_* constants */ 371 | uint32_t ch; /* a Unicode code point */ 372 | int32_t w; /* resize width */ 373 | int32_t h; /* resize height */ 374 | int32_t x; /* mouse x */ 375 | int32_t y; /* mouse y */ 376 | }; 377 | 378 | /* Initializes the termbox library. This function should be called before any 379 | * other functions. tb_init() is equivalent to tb_init_file("/dev/tty"). After 380 | * successful initialization, the library must be finalized using the 381 | * tb_shutdown() function. 382 | */ 383 | int tb_init(void); 384 | int tb_init_file(const char *path); 385 | int tb_init_fd(int ttyfd); 386 | int tb_init_rwfd(int rfd, int wfd); 387 | int tb_shutdown(void); 388 | 389 | /* Returns the size of the internal back buffer (which is the same as terminal's 390 | * window size in rows and columns). The internal buffer can be resized after 391 | * tb_clear() or tb_present() function calls. Both dimensions have an 392 | * unspecified negative value when called before tb_init() or after 393 | * tb_shutdown(). 394 | */ 395 | int tb_width(void); 396 | int tb_height(void); 397 | 398 | /* Clears the internal back buffer using TB_DEFAULT color or the 399 | * color/attributes set by tb_set_clear_attrs() function. 400 | */ 401 | int tb_clear(void); 402 | int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg); 403 | 404 | /* Synchronizes the internal back buffer with the terminal by writing to tty. */ 405 | int tb_present(void); 406 | 407 | /* Sets the position of the cursor. Upper-left character is (0, 0). */ 408 | int tb_set_cursor(int cx, int cy); 409 | int tb_hide_cursor(void); 410 | 411 | /* Set cell contents in the internal back buffer at the specified position. 412 | * 413 | * Use tb_set_cell_ex() for rendering grapheme clusters (e.g., combining 414 | * diacritical marks). 415 | * 416 | * Function tb_set_cell(x, y, ch, fg, bg) is equivalent to 417 | * tb_set_cell_ex(x, y, &ch, 1, fg, bg). 418 | * 419 | * Function tb_extend_cell() is a shortcut for appending 1 code point to 420 | * cell->ech. 421 | */ 422 | int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg); 423 | int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, 424 | uintattr_t bg); 425 | int tb_extend_cell(int x, int y, uint32_t ch); 426 | 427 | /* Sets the input mode. Termbox has two input modes: 428 | * 429 | * 1. TB_INPUT_ESC 430 | * When escape (\x1b) is in the buffer and there's no match for an escape 431 | * sequence, a key event for TB_KEY_ESC is returned. 432 | * 433 | * 2. TB_INPUT_ALT 434 | * When escape (\x1b) is in the buffer and there's no match for an escape 435 | * sequence, the next keyboard event is returned with a TB_MOD_ALT modifier. 436 | * 437 | * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the 438 | * modes (e.g., TB_INPUT_ESC | TB_INPUT_MOUSE) to receive TB_EVENT_MOUSE events. 439 | * If none of the main two modes were set, but the mouse mode was, TB_INPUT_ESC 440 | * mode is used. If for some reason you've decided to use 441 | * (TB_INPUT_ESC | TB_INPUT_ALT) combination, it will behave as if only 442 | * TB_INPUT_ESC was selected. 443 | * 444 | * If mode is TB_INPUT_CURRENT, the function returns the current input mode. 445 | * 446 | * The default input mode is TB_INPUT_ESC. 447 | */ 448 | int tb_set_input_mode(int mode); 449 | 450 | /* Sets the termbox output mode. Termbox has multiple output modes: 451 | * 452 | * 1. TB_OUTPUT_NORMAL => [0..8] 453 | * 454 | * This mode provides 8 different colors: 455 | * TB_BLACK, TB_RED, TB_GREEN, TB_YELLOW, 456 | * TB_BLUE, TB_MAGENTA, TB_CYAN, TB_WHITE 457 | * 458 | * Plus TB_DEFAULT which skips sending a color code (i.e., uses the 459 | * terminal's default color). 460 | * 461 | * Colors (including TB_DEFAULT) may be bitwise OR'd with attributes: 462 | * TB_BOLD, TB_UNDERLINE, TB_REVERSE, TB_ITALIC, TB_BLINK 463 | * 464 | * The value 0 is interpreted as TB_DEFAULT. 465 | * 466 | * Some notes: TB_REVERSE can be applied as either fg or bg attributes for 467 | * the same effect. TB_BOLD, TB_UNDERLINE, TB_ITALIC, TB_BLINK apply as fg 468 | * attributes only, and are ignored as bg attributes. 469 | * 470 | * Example usage: 471 | * tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); 472 | * 473 | * 2. TB_OUTPUT_256 => [0..255] 474 | * 475 | * In this mode you get 256 distinct colors: 476 | * 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL 477 | * 0x08 - 0x0f: bright versions of the above 478 | * 0x10 - 0xe7: 216 different colors 479 | * 0xe8 - 0xff: 24 different shades of grey 480 | * 481 | * Attributes may be bitwise OR'd as in TB_OUTPUT_NORMAL. 482 | * 483 | * In this mode 0x00 represents TB_BLACK, so TB_DEFAULT must be used for 484 | * default colors. 485 | * 486 | * 3. TB_OUTPUT_216 => [0..216] 487 | * 488 | * This mode supports the 3rd range of TB_OUTPUT_256 only, but you don't 489 | * need to provide an offset. 490 | * 491 | * The value 0 is interpreted as TB_DEFAULT. 492 | * 493 | * 4. TB_OUTPUT_GRAYSCALE => [0..24] 494 | * 495 | * This mode supports the 4th range of TB_OUTPUT_256 only, but you don't 496 | * need to provide an offset. 497 | * 498 | * The value 0 is interpreted as TB_DEFAULT. 499 | * 500 | * 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xffffff] 501 | * 502 | * This mode provides 24-bit color on supported terminals. The format is 503 | * 0xRRGGBB. Colors may be bitwise OR'd with `TB_TRUECOLOR_*` attributes. 504 | * 505 | * In this mode 0x000000 represents black, so TB_TRUECOLOR_DEFAULT must be 506 | * used for default colors. 507 | * 508 | * If mode is TB_OUTPUT_CURRENT, the function returns the current output mode. 509 | * 510 | * The default output mode is TB_OUTPUT_NORMAL. 511 | * 512 | * To use the terminal default color (i.e., to not send an escape code), pass 513 | * TB_DEFAULT (or TB_TRUECOLOR_DEFAULT in TB_OUTPUT_TRUECOLOR mode). For 514 | * convenience, the value 0 is interpreted as TB_DEFAULT in TB_OUTPUT_NORMAL, 515 | * TB_OUTPUT_216, and TB_OUTPUT_GRAYSCALE. 516 | * 517 | * Note, not all terminals support all output modes, especially beyond 518 | * TB_OUTPUT_NORMAL. There is also no very reliable way to determine color 519 | * support dynamically. If portability is desired, callers are recommended to 520 | * use TB_OUTPUT_NORMAL or make output mode end-user configurable. 521 | */ 522 | int tb_set_output_mode(int mode); 523 | 524 | /* Wait for an event up to timeout_ms milliseconds and fill the event structure 525 | * with it. If no event is available within the timeout period, TB_ERR_NO_EVENT 526 | * is returned. On a resize event, the underlying select(2) call may be 527 | * interrupted, yielding a return code of TB_ERR_POLL. In this case, you may 528 | * check errno via tb_last_errno(). If it's EINTR, you can safely ignore that 529 | * and call tb_peek_event() again. 530 | */ 531 | int tb_peek_event(struct tb_event *event, int timeout_ms); 532 | 533 | /* Same as tb_peek_event except no timeout. */ 534 | int tb_poll_event(struct tb_event *event); 535 | 536 | /* Internal termbox FDs that can be used with poll() / select(). Must call 537 | * tb_poll_event() / tb_peek_event() if activity is detected. */ 538 | int tb_get_fds(int *ttyfd, int *resizefd); 539 | 540 | /* Print and printf functions. Specify param out_w to determine width of printed 541 | * string. 542 | */ 543 | int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str); 544 | int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...); 545 | int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, 546 | const char *str); 547 | int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, 548 | const char *fmt, ...); 549 | 550 | /* Send raw bytes to terminal. */ 551 | int tb_send(const char *buf, size_t nbuf); 552 | int tb_sendf(const char *fmt, ...); 553 | 554 | /* Set custom functions. fn_type is one of TB_FUNC_* constants, fn is a 555 | * compatible function pointer, or NULL to clear. 556 | * 557 | * TB_FUNC_EXTRACT_PRE: 558 | * If specified, invoke this function BEFORE termbox tries to extract any 559 | * escape sequences from the input buffer. 560 | * 561 | * TB_FUNC_EXTRACT_POST: 562 | * If specified, invoke this function AFTER termbox tries (and fails) to 563 | * extract any escape sequences from the input buffer. 564 | */ 565 | int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)); 566 | 567 | /* Utility functions. */ 568 | int tb_utf8_char_length(char c); 569 | int tb_utf8_char_to_unicode(uint32_t *out, const char *c); 570 | int tb_utf8_unicode_to_char(char *out, uint32_t c); 571 | int tb_last_errno(void); 572 | const char *tb_strerror(int err); 573 | struct tb_cell *tb_cell_buffer(void); 574 | int tb_has_truecolor(void); 575 | int tb_has_egc(void); 576 | const char *tb_version(void); 577 | 578 | #ifdef __cplusplus 579 | } 580 | #endif 581 | 582 | #endif /* __TERMBOX_H */ 583 | 584 | #ifdef TB_IMPL 585 | 586 | #define if_err_return(rv, expr) \ 587 | if (((rv) = (expr)) != TB_OK) \ 588 | return (rv) 589 | #define if_err_break(rv, expr) \ 590 | if (((rv) = (expr)) != TB_OK) \ 591 | break 592 | #define if_ok_return(rv, expr) \ 593 | if (((rv) = (expr)) == TB_OK) \ 594 | return (rv) 595 | #define if_ok_or_need_more_return(rv, expr) \ 596 | if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) \ 597 | return (rv) 598 | 599 | #define send_literal(rv, a) \ 600 | if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1)) 601 | 602 | #define send_num(rv, nbuf, n) \ 603 | if_err_return((rv), \ 604 | bytebuf_nputs(&global.out, (nbuf), convert_num((n), (nbuf)))) 605 | 606 | #define snprintf_or_return(rv, str, sz, fmt, ...) \ 607 | do { \ 608 | (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__); \ 609 | if ((rv) < 0 || (rv) >= (int)(sz)) \ 610 | return TB_ERR; \ 611 | } while (0) 612 | 613 | #define if_not_init_return() \ 614 | if (!global.initialized) \ 615 | return TB_ERR_NOT_INIT 616 | 617 | struct bytebuf_t { 618 | char *buf; 619 | size_t len; 620 | size_t cap; 621 | }; 622 | 623 | struct cellbuf_t { 624 | int width; 625 | int height; 626 | struct tb_cell *cells; 627 | }; 628 | 629 | struct cap_trie_t { 630 | char c; 631 | struct cap_trie_t *children; 632 | size_t nchildren; 633 | int is_leaf; 634 | uint16_t key; 635 | uint8_t mod; 636 | }; 637 | 638 | struct tb_global_t { 639 | int ttyfd; 640 | int rfd; 641 | int wfd; 642 | int ttyfd_open; 643 | int resize_pipefd[2]; 644 | int width; 645 | int height; 646 | int cursor_x; 647 | int cursor_y; 648 | int last_x; 649 | int last_y; 650 | uintattr_t fg; 651 | uintattr_t bg; 652 | uintattr_t last_fg; 653 | uintattr_t last_bg; 654 | int input_mode; 655 | int output_mode; 656 | char *terminfo; 657 | size_t nterminfo; 658 | const char *caps[TB_CAP__COUNT]; 659 | struct cap_trie_t cap_trie; 660 | struct bytebuf_t in; 661 | struct bytebuf_t out; 662 | struct cellbuf_t back; 663 | struct cellbuf_t front; 664 | struct termios orig_tios; 665 | int has_orig_tios; 666 | int last_errno; 667 | int initialized; 668 | int (*fn_extract_esc_pre)(struct tb_event *, size_t *); 669 | int (*fn_extract_esc_post)(struct tb_event *, size_t *); 670 | char errbuf[1024]; 671 | }; 672 | 673 | static struct tb_global_t global = {0}; 674 | 675 | /* BEGIN codegen c */ 676 | /* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:03 +0000 */ 677 | 678 | static const int16_t terminfo_cap_indexes[] = { 679 | 66, // kf1 (TB_CAP_F1) 680 | 68, // kf2 (TB_CAP_F2) 681 | 69, // kf3 (TB_CAP_F3) 682 | 70, // kf4 (TB_CAP_F4) 683 | 71, // kf5 (TB_CAP_F5) 684 | 72, // kf6 (TB_CAP_F6) 685 | 73, // kf7 (TB_CAP_F7) 686 | 74, // kf8 (TB_CAP_F8) 687 | 75, // kf9 (TB_CAP_F9) 688 | 67, // kf10 (TB_CAP_F10) 689 | 216, // kf11 (TB_CAP_F11) 690 | 217, // kf12 (TB_CAP_F12) 691 | 77, // kich1 (TB_CAP_INSERT) 692 | 59, // kdch1 (TB_CAP_DELETE) 693 | 76, // khome (TB_CAP_HOME) 694 | 164, // kend (TB_CAP_END) 695 | 82, // kpp (TB_CAP_PGUP) 696 | 81, // knp (TB_CAP_PGDN) 697 | 87, // kcuu1 (TB_CAP_ARROW_UP) 698 | 61, // kcud1 (TB_CAP_ARROW_DOWN) 699 | 79, // kcub1 (TB_CAP_ARROW_LEFT) 700 | 83, // kcuf1 (TB_CAP_ARROW_RIGHT) 701 | 148, // kcbt (TB_CAP_BACK_TAB) 702 | 28, // smcup (TB_CAP_ENTER_CA) 703 | 40, // rmcup (TB_CAP_EXIT_CA) 704 | 16, // cnorm (TB_CAP_SHOW_CURSOR) 705 | 13, // civis (TB_CAP_HIDE_CURSOR) 706 | 5, // clear (TB_CAP_CLEAR_SCREEN) 707 | 39, // sgr0 (TB_CAP_SGR0) 708 | 36, // smul (TB_CAP_UNDERLINE) 709 | 27, // bold (TB_CAP_BOLD) 710 | 26, // blink (TB_CAP_BLINK) 711 | 311, // sitm (TB_CAP_ITALIC) 712 | 34, // rev (TB_CAP_REVERSE) 713 | 89, // smkx (TB_CAP_ENTER_KEYPAD) 714 | 88, // rmkx (TB_CAP_EXIT_KEYPAD) 715 | }; 716 | 717 | // xterm 718 | static const char *xterm_caps[] = { 719 | "\033OP", // kf1 (TB_CAP_F1) 720 | "\033OQ", // kf2 (TB_CAP_F2) 721 | "\033OR", // kf3 (TB_CAP_F3) 722 | "\033OS", // kf4 (TB_CAP_F4) 723 | "\033[15~", // kf5 (TB_CAP_F5) 724 | "\033[17~", // kf6 (TB_CAP_F6) 725 | "\033[18~", // kf7 (TB_CAP_F7) 726 | "\033[19~", // kf8 (TB_CAP_F8) 727 | "\033[20~", // kf9 (TB_CAP_F9) 728 | "\033[21~", // kf10 (TB_CAP_F10) 729 | "\033[23~", // kf11 (TB_CAP_F11) 730 | "\033[24~", // kf12 (TB_CAP_F12) 731 | "\033[2~", // kich1 (TB_CAP_INSERT) 732 | "\033[3~", // kdch1 (TB_CAP_DELETE) 733 | "\033OH", // khome (TB_CAP_HOME) 734 | "\033OF", // kend (TB_CAP_END) 735 | "\033[5~", // kpp (TB_CAP_PGUP) 736 | "\033[6~", // knp (TB_CAP_PGDN) 737 | "\033OA", // kcuu1 (TB_CAP_ARROW_UP) 738 | "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) 739 | "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) 740 | "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) 741 | "\033[Z", // kcbt (TB_CAP_BACK_TAB) 742 | "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA) 743 | "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA) 744 | "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) 745 | "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) 746 | "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) 747 | "\033(B\033[m", // sgr0 (TB_CAP_SGR0) 748 | "\033[4m", // smul (TB_CAP_UNDERLINE) 749 | "\033[1m", // bold (TB_CAP_BOLD) 750 | "\033[5m", // blink (TB_CAP_BLINK) 751 | "\033[3m", // sitm (TB_CAP_ITALIC) 752 | "\033[7m", // rev (TB_CAP_REVERSE) 753 | "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) 754 | "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) 755 | }; 756 | 757 | // linux 758 | static const char *linux_caps[] = { 759 | "\033[[A", // kf1 (TB_CAP_F1) 760 | "\033[[B", // kf2 (TB_CAP_F2) 761 | "\033[[C", // kf3 (TB_CAP_F3) 762 | "\033[[D", // kf4 (TB_CAP_F4) 763 | "\033[[E", // kf5 (TB_CAP_F5) 764 | "\033[17~", // kf6 (TB_CAP_F6) 765 | "\033[18~", // kf7 (TB_CAP_F7) 766 | "\033[19~", // kf8 (TB_CAP_F8) 767 | "\033[20~", // kf9 (TB_CAP_F9) 768 | "\033[21~", // kf10 (TB_CAP_F10) 769 | "\033[23~", // kf11 (TB_CAP_F11) 770 | "\033[24~", // kf12 (TB_CAP_F12) 771 | "\033[2~", // kich1 (TB_CAP_INSERT) 772 | "\033[3~", // kdch1 (TB_CAP_DELETE) 773 | "\033[1~", // khome (TB_CAP_HOME) 774 | "\033[4~", // kend (TB_CAP_END) 775 | "\033[5~", // kpp (TB_CAP_PGUP) 776 | "\033[6~", // knp (TB_CAP_PGDN) 777 | "\033[A", // kcuu1 (TB_CAP_ARROW_UP) 778 | "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) 779 | "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) 780 | "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) 781 | "\033[Z", // kcbt (TB_CAP_BACK_TAB) 782 | "", // smcup (TB_CAP_ENTER_CA) 783 | "", // rmcup (TB_CAP_EXIT_CA) 784 | "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR) 785 | "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR) 786 | "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) 787 | "\033[m\017", // sgr0 (TB_CAP_SGR0) 788 | "\033[4m", // smul (TB_CAP_UNDERLINE) 789 | "\033[1m", // bold (TB_CAP_BOLD) 790 | "\033[5m", // blink (TB_CAP_BLINK) 791 | "", // sitm (TB_CAP_ITALIC) 792 | "\033[7m", // rev (TB_CAP_REVERSE) 793 | "", // smkx (TB_CAP_ENTER_KEYPAD) 794 | "", // rmkx (TB_CAP_EXIT_KEYPAD) 795 | }; 796 | 797 | // screen 798 | static const char *screen_caps[] = { 799 | "\033OP", // kf1 (TB_CAP_F1) 800 | "\033OQ", // kf2 (TB_CAP_F2) 801 | "\033OR", // kf3 (TB_CAP_F3) 802 | "\033OS", // kf4 (TB_CAP_F4) 803 | "\033[15~", // kf5 (TB_CAP_F5) 804 | "\033[17~", // kf6 (TB_CAP_F6) 805 | "\033[18~", // kf7 (TB_CAP_F7) 806 | "\033[19~", // kf8 (TB_CAP_F8) 807 | "\033[20~", // kf9 (TB_CAP_F9) 808 | "\033[21~", // kf10 (TB_CAP_F10) 809 | "\033[23~", // kf11 (TB_CAP_F11) 810 | "\033[24~", // kf12 (TB_CAP_F12) 811 | "\033[2~", // kich1 (TB_CAP_INSERT) 812 | "\033[3~", // kdch1 (TB_CAP_DELETE) 813 | "\033[1~", // khome (TB_CAP_HOME) 814 | "\033[4~", // kend (TB_CAP_END) 815 | "\033[5~", // kpp (TB_CAP_PGUP) 816 | "\033[6~", // knp (TB_CAP_PGDN) 817 | "\033OA", // kcuu1 (TB_CAP_ARROW_UP) 818 | "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) 819 | "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) 820 | "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) 821 | "\033[Z", // kcbt (TB_CAP_BACK_TAB) 822 | "\033[?1049h", // smcup (TB_CAP_ENTER_CA) 823 | "\033[?1049l", // rmcup (TB_CAP_EXIT_CA) 824 | "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) 825 | "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) 826 | "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) 827 | "\033[m\017", // sgr0 (TB_CAP_SGR0) 828 | "\033[4m", // smul (TB_CAP_UNDERLINE) 829 | "\033[1m", // bold (TB_CAP_BOLD) 830 | "\033[5m", // blink (TB_CAP_BLINK) 831 | "", // sitm (TB_CAP_ITALIC) 832 | "\033[7m", // rev (TB_CAP_REVERSE) 833 | "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) 834 | "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) 835 | }; 836 | 837 | // rxvt-256color 838 | static const char *rxvt_256color_caps[] = { 839 | "\033[11~", // kf1 (TB_CAP_F1) 840 | "\033[12~", // kf2 (TB_CAP_F2) 841 | "\033[13~", // kf3 (TB_CAP_F3) 842 | "\033[14~", // kf4 (TB_CAP_F4) 843 | "\033[15~", // kf5 (TB_CAP_F5) 844 | "\033[17~", // kf6 (TB_CAP_F6) 845 | "\033[18~", // kf7 (TB_CAP_F7) 846 | "\033[19~", // kf8 (TB_CAP_F8) 847 | "\033[20~", // kf9 (TB_CAP_F9) 848 | "\033[21~", // kf10 (TB_CAP_F10) 849 | "\033[23~", // kf11 (TB_CAP_F11) 850 | "\033[24~", // kf12 (TB_CAP_F12) 851 | "\033[2~", // kich1 (TB_CAP_INSERT) 852 | "\033[3~", // kdch1 (TB_CAP_DELETE) 853 | "\033[7~", // khome (TB_CAP_HOME) 854 | "\033[8~", // kend (TB_CAP_END) 855 | "\033[5~", // kpp (TB_CAP_PGUP) 856 | "\033[6~", // knp (TB_CAP_PGDN) 857 | "\033[A", // kcuu1 (TB_CAP_ARROW_UP) 858 | "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) 859 | "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) 860 | "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) 861 | "\033[Z", // kcbt (TB_CAP_BACK_TAB) 862 | "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) 863 | "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) 864 | "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) 865 | "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) 866 | "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) 867 | "\033[m\017", // sgr0 (TB_CAP_SGR0) 868 | "\033[4m", // smul (TB_CAP_UNDERLINE) 869 | "\033[1m", // bold (TB_CAP_BOLD) 870 | "\033[5m", // blink (TB_CAP_BLINK) 871 | "", // sitm (TB_CAP_ITALIC) 872 | "\033[7m", // rev (TB_CAP_REVERSE) 873 | "\033=", // smkx (TB_CAP_ENTER_KEYPAD) 874 | "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) 875 | }; 876 | 877 | // rxvt-unicode 878 | static const char *rxvt_unicode_caps[] = { 879 | "\033[11~", // kf1 (TB_CAP_F1) 880 | "\033[12~", // kf2 (TB_CAP_F2) 881 | "\033[13~", // kf3 (TB_CAP_F3) 882 | "\033[14~", // kf4 (TB_CAP_F4) 883 | "\033[15~", // kf5 (TB_CAP_F5) 884 | "\033[17~", // kf6 (TB_CAP_F6) 885 | "\033[18~", // kf7 (TB_CAP_F7) 886 | "\033[19~", // kf8 (TB_CAP_F8) 887 | "\033[20~", // kf9 (TB_CAP_F9) 888 | "\033[21~", // kf10 (TB_CAP_F10) 889 | "\033[23~", // kf11 (TB_CAP_F11) 890 | "\033[24~", // kf12 (TB_CAP_F12) 891 | "\033[2~", // kich1 (TB_CAP_INSERT) 892 | "\033[3~", // kdch1 (TB_CAP_DELETE) 893 | "\033[7~", // khome (TB_CAP_HOME) 894 | "\033[8~", // kend (TB_CAP_END) 895 | "\033[5~", // kpp (TB_CAP_PGUP) 896 | "\033[6~", // knp (TB_CAP_PGDN) 897 | "\033[A", // kcuu1 (TB_CAP_ARROW_UP) 898 | "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) 899 | "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) 900 | "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) 901 | "\033[Z", // kcbt (TB_CAP_BACK_TAB) 902 | "\033[?1049h", // smcup (TB_CAP_ENTER_CA) 903 | "\033[r\033[?1049l", // rmcup (TB_CAP_EXIT_CA) 904 | "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) 905 | "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) 906 | "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) 907 | "\033[m\033(B", // sgr0 (TB_CAP_SGR0) 908 | "\033[4m", // smul (TB_CAP_UNDERLINE) 909 | "\033[1m", // bold (TB_CAP_BOLD) 910 | "\033[5m", // blink (TB_CAP_BLINK) 911 | "\033[3m", // sitm (TB_CAP_ITALIC) 912 | "\033[7m", // rev (TB_CAP_REVERSE) 913 | "\033=", // smkx (TB_CAP_ENTER_KEYPAD) 914 | "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) 915 | }; 916 | 917 | // Eterm 918 | static const char *eterm_caps[] = { 919 | "\033[11~", // kf1 (TB_CAP_F1) 920 | "\033[12~", // kf2 (TB_CAP_F2) 921 | "\033[13~", // kf3 (TB_CAP_F3) 922 | "\033[14~", // kf4 (TB_CAP_F4) 923 | "\033[15~", // kf5 (TB_CAP_F5) 924 | "\033[17~", // kf6 (TB_CAP_F6) 925 | "\033[18~", // kf7 (TB_CAP_F7) 926 | "\033[19~", // kf8 (TB_CAP_F8) 927 | "\033[20~", // kf9 (TB_CAP_F9) 928 | "\033[21~", // kf10 (TB_CAP_F10) 929 | "\033[23~", // kf11 (TB_CAP_F11) 930 | "\033[24~", // kf12 (TB_CAP_F12) 931 | "\033[2~", // kich1 (TB_CAP_INSERT) 932 | "\033[3~", // kdch1 (TB_CAP_DELETE) 933 | "\033[7~", // khome (TB_CAP_HOME) 934 | "\033[8~", // kend (TB_CAP_END) 935 | "\033[5~", // kpp (TB_CAP_PGUP) 936 | "\033[6~", // knp (TB_CAP_PGDN) 937 | "\033[A", // kcuu1 (TB_CAP_ARROW_UP) 938 | "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) 939 | "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) 940 | "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) 941 | "", // kcbt (TB_CAP_BACK_TAB) 942 | "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) 943 | "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) 944 | "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) 945 | "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) 946 | "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) 947 | "\033[m\017", // sgr0 (TB_CAP_SGR0) 948 | "\033[4m", // smul (TB_CAP_UNDERLINE) 949 | "\033[1m", // bold (TB_CAP_BOLD) 950 | "\033[5m", // blink (TB_CAP_BLINK) 951 | "", // sitm (TB_CAP_ITALIC) 952 | "\033[7m", // rev (TB_CAP_REVERSE) 953 | "", // smkx (TB_CAP_ENTER_KEYPAD) 954 | "", // rmkx (TB_CAP_EXIT_KEYPAD) 955 | }; 956 | 957 | static struct { 958 | const char *name; 959 | const char **caps; 960 | const char *alias; 961 | } builtin_terms[] = { 962 | {"xterm", xterm_caps, "" }, 963 | {"linux", linux_caps, "" }, 964 | {"screen", screen_caps, "tmux"}, 965 | {"rxvt-256color", rxvt_256color_caps, "" }, 966 | {"rxvt-unicode", rxvt_unicode_caps, "rxvt"}, 967 | {"Eterm", eterm_caps, "" }, 968 | {NULL, NULL, NULL }, 969 | }; 970 | 971 | /* END codegen c */ 972 | 973 | static struct { 974 | const char *cap; 975 | const uint16_t key; 976 | const uint8_t mod; 977 | } builtin_mod_caps[] = { 978 | // xterm arrows 979 | {"\x1b[1;2A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, 980 | {"\x1b[1;3A", TB_KEY_ARROW_UP, TB_MOD_ALT }, 981 | {"\x1b[1;4A", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, 982 | {"\x1b[1;5A", TB_KEY_ARROW_UP, TB_MOD_CTRL }, 983 | {"\x1b[1;6A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_SHIFT }, 984 | {"\x1b[1;7A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, 985 | {"\x1b[1;8A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 986 | 987 | {"\x1b[1;2B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, 988 | {"\x1b[1;3B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, 989 | {"\x1b[1;4B", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, 990 | {"\x1b[1;5B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, 991 | {"\x1b[1;6B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_SHIFT }, 992 | {"\x1b[1;7B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, 993 | {"\x1b[1;8B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 994 | 995 | {"\x1b[1;2C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, 996 | {"\x1b[1;3C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, 997 | {"\x1b[1;4C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, 998 | {"\x1b[1;5C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, 999 | {"\x1b[1;6C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT }, 1000 | {"\x1b[1;7C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, 1001 | {"\x1b[1;8C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1002 | 1003 | {"\x1b[1;2D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, 1004 | {"\x1b[1;3D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, 1005 | {"\x1b[1;4D", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, 1006 | {"\x1b[1;5D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, 1007 | {"\x1b[1;6D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_SHIFT }, 1008 | {"\x1b[1;7D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, 1009 | {"\x1b[1;8D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1010 | 1011 | // xterm keys 1012 | {"\x1b[1;2H", TB_KEY_HOME, TB_MOD_SHIFT }, 1013 | {"\x1b[1;3H", TB_KEY_HOME, TB_MOD_ALT }, 1014 | {"\x1b[1;4H", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, 1015 | {"\x1b[1;5H", TB_KEY_HOME, TB_MOD_CTRL }, 1016 | {"\x1b[1;6H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, 1017 | {"\x1b[1;7H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, 1018 | {"\x1b[1;8H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1019 | 1020 | {"\x1b[1;2F", TB_KEY_END, TB_MOD_SHIFT }, 1021 | {"\x1b[1;3F", TB_KEY_END, TB_MOD_ALT }, 1022 | {"\x1b[1;4F", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, 1023 | {"\x1b[1;5F", TB_KEY_END, TB_MOD_CTRL }, 1024 | {"\x1b[1;6F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, 1025 | {"\x1b[1;7F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, 1026 | {"\x1b[1;8F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1027 | 1028 | {"\x1b[2;2~", TB_KEY_INSERT, TB_MOD_SHIFT }, 1029 | {"\x1b[2;3~", TB_KEY_INSERT, TB_MOD_ALT }, 1030 | {"\x1b[2;4~", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, 1031 | {"\x1b[2;5~", TB_KEY_INSERT, TB_MOD_CTRL }, 1032 | {"\x1b[2;6~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, 1033 | {"\x1b[2;7~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, 1034 | {"\x1b[2;8~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1035 | 1036 | {"\x1b[3;2~", TB_KEY_DELETE, TB_MOD_SHIFT }, 1037 | {"\x1b[3;3~", TB_KEY_DELETE, TB_MOD_ALT }, 1038 | {"\x1b[3;4~", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, 1039 | {"\x1b[3;5~", TB_KEY_DELETE, TB_MOD_CTRL }, 1040 | {"\x1b[3;6~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, 1041 | {"\x1b[3;7~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, 1042 | {"\x1b[3;8~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1043 | 1044 | {"\x1b[5;2~", TB_KEY_PGUP, TB_MOD_SHIFT }, 1045 | {"\x1b[5;3~", TB_KEY_PGUP, TB_MOD_ALT }, 1046 | {"\x1b[5;4~", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, 1047 | {"\x1b[5;5~", TB_KEY_PGUP, TB_MOD_CTRL }, 1048 | {"\x1b[5;6~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, 1049 | {"\x1b[5;7~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, 1050 | {"\x1b[5;8~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1051 | 1052 | {"\x1b[6;2~", TB_KEY_PGDN, TB_MOD_SHIFT }, 1053 | {"\x1b[6;3~", TB_KEY_PGDN, TB_MOD_ALT }, 1054 | {"\x1b[6;4~", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, 1055 | {"\x1b[6;5~", TB_KEY_PGDN, TB_MOD_CTRL }, 1056 | {"\x1b[6;6~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, 1057 | {"\x1b[6;7~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, 1058 | {"\x1b[6;8~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1059 | 1060 | {"\x1b[1;2P", TB_KEY_F1, TB_MOD_SHIFT }, 1061 | {"\x1b[1;3P", TB_KEY_F1, TB_MOD_ALT }, 1062 | {"\x1b[1;4P", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, 1063 | {"\x1b[1;5P", TB_KEY_F1, TB_MOD_CTRL }, 1064 | {"\x1b[1;6P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, 1065 | {"\x1b[1;7P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, 1066 | {"\x1b[1;8P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1067 | 1068 | {"\x1b[1;2Q", TB_KEY_F2, TB_MOD_SHIFT }, 1069 | {"\x1b[1;3Q", TB_KEY_F2, TB_MOD_ALT }, 1070 | {"\x1b[1;4Q", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, 1071 | {"\x1b[1;5Q", TB_KEY_F2, TB_MOD_CTRL }, 1072 | {"\x1b[1;6Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, 1073 | {"\x1b[1;7Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, 1074 | {"\x1b[1;8Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1075 | 1076 | {"\x1b[1;2R", TB_KEY_F3, TB_MOD_SHIFT }, 1077 | {"\x1b[1;3R", TB_KEY_F3, TB_MOD_ALT }, 1078 | {"\x1b[1;4R", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, 1079 | {"\x1b[1;5R", TB_KEY_F3, TB_MOD_CTRL }, 1080 | {"\x1b[1;6R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, 1081 | {"\x1b[1;7R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, 1082 | {"\x1b[1;8R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1083 | 1084 | {"\x1b[1;2S", TB_KEY_F4, TB_MOD_SHIFT }, 1085 | {"\x1b[1;3S", TB_KEY_F4, TB_MOD_ALT }, 1086 | {"\x1b[1;4S", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, 1087 | {"\x1b[1;5S", TB_KEY_F4, TB_MOD_CTRL }, 1088 | {"\x1b[1;6S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, 1089 | {"\x1b[1;7S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, 1090 | {"\x1b[1;8S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1091 | 1092 | {"\x1b[15;2~", TB_KEY_F5, TB_MOD_SHIFT }, 1093 | {"\x1b[15;3~", TB_KEY_F5, TB_MOD_ALT }, 1094 | {"\x1b[15;4~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, 1095 | {"\x1b[15;5~", TB_KEY_F5, TB_MOD_CTRL }, 1096 | {"\x1b[15;6~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, 1097 | {"\x1b[15;7~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, 1098 | {"\x1b[15;8~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1099 | 1100 | {"\x1b[17;2~", TB_KEY_F6, TB_MOD_SHIFT }, 1101 | {"\x1b[17;3~", TB_KEY_F6, TB_MOD_ALT }, 1102 | {"\x1b[17;4~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, 1103 | {"\x1b[17;5~", TB_KEY_F6, TB_MOD_CTRL }, 1104 | {"\x1b[17;6~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, 1105 | {"\x1b[17;7~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, 1106 | {"\x1b[17;8~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1107 | 1108 | {"\x1b[18;2~", TB_KEY_F7, TB_MOD_SHIFT }, 1109 | {"\x1b[18;3~", TB_KEY_F7, TB_MOD_ALT }, 1110 | {"\x1b[18;4~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, 1111 | {"\x1b[18;5~", TB_KEY_F7, TB_MOD_CTRL }, 1112 | {"\x1b[18;6~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, 1113 | {"\x1b[18;7~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, 1114 | {"\x1b[18;8~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1115 | 1116 | {"\x1b[19;2~", TB_KEY_F8, TB_MOD_SHIFT }, 1117 | {"\x1b[19;3~", TB_KEY_F8, TB_MOD_ALT }, 1118 | {"\x1b[19;4~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, 1119 | {"\x1b[19;5~", TB_KEY_F8, TB_MOD_CTRL }, 1120 | {"\x1b[19;6~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, 1121 | {"\x1b[19;7~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, 1122 | {"\x1b[19;8~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1123 | 1124 | {"\x1b[20;2~", TB_KEY_F9, TB_MOD_SHIFT }, 1125 | {"\x1b[20;3~", TB_KEY_F9, TB_MOD_ALT }, 1126 | {"\x1b[20;4~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, 1127 | {"\x1b[20;5~", TB_KEY_F9, TB_MOD_CTRL }, 1128 | {"\x1b[20;6~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, 1129 | {"\x1b[20;7~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, 1130 | {"\x1b[20;8~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1131 | 1132 | {"\x1b[21;2~", TB_KEY_F10, TB_MOD_SHIFT }, 1133 | {"\x1b[21;3~", TB_KEY_F10, TB_MOD_ALT }, 1134 | {"\x1b[21;4~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, 1135 | {"\x1b[21;5~", TB_KEY_F10, TB_MOD_CTRL }, 1136 | {"\x1b[21;6~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, 1137 | {"\x1b[21;7~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, 1138 | {"\x1b[21;8~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1139 | 1140 | {"\x1b[23;2~", TB_KEY_F11, TB_MOD_SHIFT }, 1141 | {"\x1b[23;3~", TB_KEY_F11, TB_MOD_ALT }, 1142 | {"\x1b[23;4~", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, 1143 | {"\x1b[23;5~", TB_KEY_F11, TB_MOD_CTRL }, 1144 | {"\x1b[23;6~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, 1145 | {"\x1b[23;7~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, 1146 | {"\x1b[23;8~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1147 | 1148 | {"\x1b[24;2~", TB_KEY_F12, TB_MOD_SHIFT }, 1149 | {"\x1b[24;3~", TB_KEY_F12, TB_MOD_ALT }, 1150 | {"\x1b[24;4~", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, 1151 | {"\x1b[24;5~", TB_KEY_F12, TB_MOD_CTRL }, 1152 | {"\x1b[24;6~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, 1153 | {"\x1b[24;7~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, 1154 | {"\x1b[24;8~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1155 | 1156 | // rxvt arrows 1157 | {"\x1b[a", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, 1158 | {"\x1b\x1b[A", TB_KEY_ARROW_UP, TB_MOD_ALT }, 1159 | {"\x1b\x1b[a", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, 1160 | {"\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL }, 1161 | {"\x1b\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, 1162 | 1163 | {"\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, 1164 | {"\x1b\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, 1165 | {"\x1b\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, 1166 | {"\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, 1167 | {"\x1b\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, 1168 | 1169 | {"\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, 1170 | {"\x1b\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, 1171 | {"\x1b\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, 1172 | {"\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, 1173 | {"\x1b\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, 1174 | 1175 | {"\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, 1176 | {"\x1b\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, 1177 | {"\x1b\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, 1178 | {"\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, 1179 | {"\x1b\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, 1180 | 1181 | // rxvt keys 1182 | {"\x1b[7$", TB_KEY_HOME, TB_MOD_SHIFT }, 1183 | {"\x1b\x1b[7~", TB_KEY_HOME, TB_MOD_ALT }, 1184 | {"\x1b\x1b[7$", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, 1185 | {"\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL }, 1186 | {"\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, 1187 | {"\x1b\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, 1188 | {"\x1b\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1189 | 1190 | {"\x1b\x1b[8~", TB_KEY_END, TB_MOD_ALT }, 1191 | {"\x1b\x1b[8$", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, 1192 | {"\x1b[8^", TB_KEY_END, TB_MOD_CTRL }, 1193 | {"\x1b\x1b[8^", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, 1194 | {"\x1b\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1195 | {"\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, 1196 | {"\x1b[8$", TB_KEY_END, TB_MOD_SHIFT }, 1197 | 1198 | {"\x1b\x1b[2~", TB_KEY_INSERT, TB_MOD_ALT }, 1199 | {"\x1b\x1b[2$", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, 1200 | {"\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL }, 1201 | {"\x1b\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, 1202 | {"\x1b\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1203 | {"\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, 1204 | {"\x1b[2$", TB_KEY_INSERT, TB_MOD_SHIFT }, 1205 | 1206 | {"\x1b\x1b[3~", TB_KEY_DELETE, TB_MOD_ALT }, 1207 | {"\x1b\x1b[3$", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, 1208 | {"\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL }, 1209 | {"\x1b\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, 1210 | {"\x1b\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1211 | {"\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, 1212 | {"\x1b[3$", TB_KEY_DELETE, TB_MOD_SHIFT }, 1213 | 1214 | {"\x1b\x1b[5~", TB_KEY_PGUP, TB_MOD_ALT }, 1215 | {"\x1b\x1b[5$", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, 1216 | {"\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL }, 1217 | {"\x1b\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, 1218 | {"\x1b\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1219 | {"\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, 1220 | {"\x1b[5$", TB_KEY_PGUP, TB_MOD_SHIFT }, 1221 | 1222 | {"\x1b\x1b[6~", TB_KEY_PGDN, TB_MOD_ALT }, 1223 | {"\x1b\x1b[6$", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, 1224 | {"\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL }, 1225 | {"\x1b\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, 1226 | {"\x1b\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1227 | {"\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, 1228 | {"\x1b[6$", TB_KEY_PGDN, TB_MOD_SHIFT }, 1229 | 1230 | {"\x1b\x1b[11~", TB_KEY_F1, TB_MOD_ALT }, 1231 | {"\x1b\x1b[23~", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, 1232 | {"\x1b[11^", TB_KEY_F1, TB_MOD_CTRL }, 1233 | {"\x1b\x1b[11^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, 1234 | {"\x1b\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1235 | {"\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, 1236 | {"\x1b[23~", TB_KEY_F1, TB_MOD_SHIFT }, 1237 | 1238 | {"\x1b\x1b[12~", TB_KEY_F2, TB_MOD_ALT }, 1239 | {"\x1b\x1b[24~", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, 1240 | {"\x1b[12^", TB_KEY_F2, TB_MOD_CTRL }, 1241 | {"\x1b\x1b[12^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, 1242 | {"\x1b\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1243 | {"\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, 1244 | {"\x1b[24~", TB_KEY_F2, TB_MOD_SHIFT }, 1245 | 1246 | {"\x1b\x1b[13~", TB_KEY_F3, TB_MOD_ALT }, 1247 | {"\x1b\x1b[25~", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, 1248 | {"\x1b[13^", TB_KEY_F3, TB_MOD_CTRL }, 1249 | {"\x1b\x1b[13^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, 1250 | {"\x1b\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1251 | {"\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, 1252 | {"\x1b[25~", TB_KEY_F3, TB_MOD_SHIFT }, 1253 | 1254 | {"\x1b\x1b[14~", TB_KEY_F4, TB_MOD_ALT }, 1255 | {"\x1b\x1b[26~", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, 1256 | {"\x1b[14^", TB_KEY_F4, TB_MOD_CTRL }, 1257 | {"\x1b\x1b[14^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, 1258 | {"\x1b\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1259 | {"\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, 1260 | {"\x1b[26~", TB_KEY_F4, TB_MOD_SHIFT }, 1261 | 1262 | {"\x1b\x1b[15~", TB_KEY_F5, TB_MOD_ALT }, 1263 | {"\x1b\x1b[28~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, 1264 | {"\x1b[15^", TB_KEY_F5, TB_MOD_CTRL }, 1265 | {"\x1b\x1b[15^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, 1266 | {"\x1b\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1267 | {"\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, 1268 | {"\x1b[28~", TB_KEY_F5, TB_MOD_SHIFT }, 1269 | 1270 | {"\x1b\x1b[17~", TB_KEY_F6, TB_MOD_ALT }, 1271 | {"\x1b\x1b[29~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, 1272 | {"\x1b[17^", TB_KEY_F6, TB_MOD_CTRL }, 1273 | {"\x1b\x1b[17^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, 1274 | {"\x1b\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1275 | {"\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, 1276 | {"\x1b[29~", TB_KEY_F6, TB_MOD_SHIFT }, 1277 | 1278 | {"\x1b\x1b[18~", TB_KEY_F7, TB_MOD_ALT }, 1279 | {"\x1b\x1b[31~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, 1280 | {"\x1b[18^", TB_KEY_F7, TB_MOD_CTRL }, 1281 | {"\x1b\x1b[18^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, 1282 | {"\x1b\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1283 | {"\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, 1284 | {"\x1b[31~", TB_KEY_F7, TB_MOD_SHIFT }, 1285 | 1286 | {"\x1b\x1b[19~", TB_KEY_F8, TB_MOD_ALT }, 1287 | {"\x1b\x1b[32~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, 1288 | {"\x1b[19^", TB_KEY_F8, TB_MOD_CTRL }, 1289 | {"\x1b\x1b[19^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, 1290 | {"\x1b\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1291 | {"\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, 1292 | {"\x1b[32~", TB_KEY_F8, TB_MOD_SHIFT }, 1293 | 1294 | {"\x1b\x1b[20~", TB_KEY_F9, TB_MOD_ALT }, 1295 | {"\x1b\x1b[33~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, 1296 | {"\x1b[20^", TB_KEY_F9, TB_MOD_CTRL }, 1297 | {"\x1b\x1b[20^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, 1298 | {"\x1b\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1299 | {"\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, 1300 | {"\x1b[33~", TB_KEY_F9, TB_MOD_SHIFT }, 1301 | 1302 | {"\x1b\x1b[21~", TB_KEY_F10, TB_MOD_ALT }, 1303 | {"\x1b\x1b[34~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, 1304 | {"\x1b[21^", TB_KEY_F10, TB_MOD_CTRL }, 1305 | {"\x1b\x1b[21^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, 1306 | {"\x1b\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1307 | {"\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, 1308 | {"\x1b[34~", TB_KEY_F10, TB_MOD_SHIFT }, 1309 | 1310 | {"\x1b\x1b[23~", TB_KEY_F11, TB_MOD_ALT }, 1311 | {"\x1b\x1b[23$", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, 1312 | {"\x1b[23^", TB_KEY_F11, TB_MOD_CTRL }, 1313 | {"\x1b\x1b[23^", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, 1314 | {"\x1b\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1315 | {"\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, 1316 | {"\x1b[23$", TB_KEY_F11, TB_MOD_SHIFT }, 1317 | 1318 | {"\x1b\x1b[24~", TB_KEY_F12, TB_MOD_ALT }, 1319 | {"\x1b\x1b[24$", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, 1320 | {"\x1b[24^", TB_KEY_F12, TB_MOD_CTRL }, 1321 | {"\x1b\x1b[24^", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, 1322 | {"\x1b\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, 1323 | {"\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, 1324 | {"\x1b[24$", TB_KEY_F12, TB_MOD_SHIFT }, 1325 | 1326 | // linux console/putty arrows 1327 | {"\x1b[A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, 1328 | {"\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, 1329 | {"\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, 1330 | {"\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, 1331 | 1332 | // more putty arrows 1333 | {"\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL }, 1334 | {"\x1b\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, 1335 | {"\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, 1336 | {"\x1b\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, 1337 | {"\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, 1338 | {"\x1b\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, 1339 | {"\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, 1340 | {"\x1b\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, 1341 | 1342 | {NULL, 0, 0 }, 1343 | }; 1344 | 1345 | static const unsigned char utf8_length[256] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1346 | 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, 1347 | 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, 1348 | 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, 1349 | 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, 1350 | 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, 1351 | 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, 1352 | 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, 1353 | 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1354 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1355 | 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1}; 1356 | 1357 | static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01}; 1358 | 1359 | static int tb_reset(void); 1360 | static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, 1361 | size_t *out_w, const char *fmt, va_list vl); 1362 | static int init_term_attrs(void); 1363 | static int init_term_caps(void); 1364 | static int init_cap_trie(void); 1365 | static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod); 1366 | static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, 1367 | size_t *depth); 1368 | static int cap_trie_deinit(struct cap_trie_t *node); 1369 | static int init_resize_handler(void); 1370 | static int send_init_escape_codes(void); 1371 | static int send_clear(void); 1372 | static int update_term_size(void); 1373 | static int update_term_size_via_esc(void); 1374 | static int init_cellbuf(void); 1375 | static int tb_deinit(void); 1376 | static int load_terminfo(void); 1377 | static int load_terminfo_from_path(const char *path, const char *term); 1378 | static int read_terminfo_path(const char *path); 1379 | static int parse_terminfo_caps(void); 1380 | static int load_builtin_caps(void); 1381 | static const char *get_terminfo_string(int16_t str_offsets_pos, 1382 | int16_t str_table_pos, int16_t str_table_len, int16_t str_index); 1383 | static int wait_event(struct tb_event *event, int timeout); 1384 | static int extract_event(struct tb_event *event); 1385 | static int extract_esc(struct tb_event *event); 1386 | static int extract_esc_user(struct tb_event *event, int is_post); 1387 | static int extract_esc_cap(struct tb_event *event); 1388 | static int extract_esc_mouse(struct tb_event *event); 1389 | static int resize_cellbufs(void); 1390 | static void handle_resize(int sig); 1391 | static int send_attr(uintattr_t fg, uintattr_t bg); 1392 | static int send_sgr(uintattr_t fg, uintattr_t bg, uintattr_t fg_is_default, 1393 | uintattr_t bg_is_default); 1394 | static int send_cursor_if(int x, int y); 1395 | static int send_char(int x, int y, uint32_t ch); 1396 | static int send_cluster(int x, int y, uint32_t *ch, size_t nch); 1397 | static int convert_num(uint32_t num, char *buf); 1398 | static int cell_cmp(struct tb_cell *a, struct tb_cell *b); 1399 | static int cell_copy(struct tb_cell *dst, struct tb_cell *src); 1400 | static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, 1401 | uintattr_t fg, uintattr_t bg); 1402 | static int cell_reserve_ech(struct tb_cell *cell, size_t n); 1403 | static int cell_free(struct tb_cell *cell); 1404 | static int cellbuf_init(struct cellbuf_t *c, int w, int h); 1405 | static int cellbuf_free(struct cellbuf_t *c); 1406 | static int cellbuf_clear(struct cellbuf_t *c); 1407 | static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out); 1408 | static int cellbuf_resize(struct cellbuf_t *c, int w, int h); 1409 | static int bytebuf_puts(struct bytebuf_t *b, const char *str); 1410 | static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr); 1411 | static int bytebuf_shift(struct bytebuf_t *b, size_t n); 1412 | static int bytebuf_flush(struct bytebuf_t *b, int fd); 1413 | static int bytebuf_reserve(struct bytebuf_t *b, size_t sz); 1414 | static int bytebuf_free(struct bytebuf_t *b); 1415 | 1416 | int tb_init(void) { 1417 | return tb_init_file("/dev/tty"); 1418 | } 1419 | 1420 | int tb_init_file(const char *path) { 1421 | if (global.initialized) { 1422 | return TB_ERR_INIT_ALREADY; 1423 | } 1424 | int ttyfd = open(path, O_RDWR); 1425 | if (ttyfd < 0) { 1426 | global.last_errno = errno; 1427 | return TB_ERR_INIT_OPEN; 1428 | } 1429 | global.ttyfd_open = 1; 1430 | return tb_init_fd(ttyfd); 1431 | } 1432 | 1433 | int tb_init_fd(int ttyfd) { 1434 | return tb_init_rwfd(ttyfd, ttyfd); 1435 | } 1436 | 1437 | int tb_init_rwfd(int rfd, int wfd) { 1438 | int rv; 1439 | 1440 | tb_reset(); 1441 | global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1; 1442 | global.rfd = rfd; 1443 | global.wfd = wfd; 1444 | 1445 | do { 1446 | if_err_break(rv, init_term_attrs()); 1447 | if_err_break(rv, init_term_caps()); 1448 | if_err_break(rv, init_cap_trie()); 1449 | if_err_break(rv, init_resize_handler()); 1450 | if_err_break(rv, send_init_escape_codes()); 1451 | if_err_break(rv, send_clear()); 1452 | if_err_break(rv, update_term_size()); 1453 | if_err_break(rv, init_cellbuf()); 1454 | global.initialized = 1; 1455 | } while (0); 1456 | 1457 | if (rv != TB_OK) { 1458 | tb_deinit(); 1459 | } 1460 | 1461 | return rv; 1462 | } 1463 | 1464 | int tb_shutdown(void) { 1465 | if_not_init_return(); 1466 | tb_deinit(); 1467 | return TB_OK; 1468 | } 1469 | 1470 | int tb_width(void) { 1471 | if_not_init_return(); 1472 | return global.width; 1473 | } 1474 | 1475 | int tb_height(void) { 1476 | if_not_init_return(); 1477 | return global.height; 1478 | } 1479 | 1480 | int tb_clear(void) { 1481 | if_not_init_return(); 1482 | return cellbuf_clear(&global.back); 1483 | } 1484 | 1485 | int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) { 1486 | if_not_init_return(); 1487 | global.fg = fg; 1488 | global.bg = bg; 1489 | return TB_OK; 1490 | } 1491 | 1492 | int tb_present(void) { 1493 | if_not_init_return(); 1494 | 1495 | int rv; 1496 | 1497 | // TODO Assert global.back.(width,height) == global.front.(width,height) 1498 | 1499 | global.last_x = -1; 1500 | global.last_y = -1; 1501 | 1502 | int x, y, i; 1503 | for (y = 0; y < global.front.height; y++) { 1504 | for (x = 0; x < global.front.width;) { 1505 | struct tb_cell *back, *front; 1506 | if_err_return(rv, cellbuf_get(&global.back, x, y, &back)); 1507 | if_err_return(rv, cellbuf_get(&global.front, x, y, &front)); 1508 | 1509 | int w; 1510 | { 1511 | #ifdef TB_OPT_EGC 1512 | if (back->nech > 0) 1513 | w = wcswidth((wchar_t *)back->ech, back->nech); 1514 | else 1515 | #endif 1516 | /* wcwidth() simply returns -1 on overflow of wchar_t */ 1517 | w = wcwidth((wchar_t)back->ch); 1518 | } 1519 | if (w < 1) { 1520 | w = 1; 1521 | } 1522 | 1523 | if (cell_cmp(back, front) != 0) { 1524 | cell_copy(front, back); 1525 | 1526 | send_attr(back->fg, back->bg); 1527 | if (w > 1 && x >= global.front.width - (w - 1)) { 1528 | for (i = x; i < global.front.width; i++) { 1529 | send_char(i, y, ' '); 1530 | } 1531 | } else { 1532 | { 1533 | #ifdef TB_OPT_EGC 1534 | if (back->nech > 0) 1535 | send_cluster(x, y, back->ech, back->nech); 1536 | else 1537 | #endif 1538 | send_char(x, y, back->ch); 1539 | } 1540 | for (i = 1; i < w; i++) { 1541 | struct tb_cell *front_wide; 1542 | if_err_return(rv, 1543 | cellbuf_get(&global.front, x + i, y, &front_wide)); 1544 | if_err_return(rv, 1545 | cell_set(front_wide, 0, 1, back->fg, back->bg)); 1546 | } 1547 | } 1548 | } 1549 | x += w; 1550 | } 1551 | } 1552 | 1553 | if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); 1554 | if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); 1555 | 1556 | return TB_OK; 1557 | } 1558 | 1559 | int tb_set_cursor(int cx, int cy) { 1560 | if_not_init_return(); 1561 | int rv; 1562 | if (cx < 0) 1563 | cx = 0; 1564 | if (cy < 0) 1565 | cy = 0; 1566 | if (global.cursor_x == -1) { 1567 | if_err_return(rv, 1568 | bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR])); 1569 | } 1570 | if_err_return(rv, send_cursor_if(cx, cy)); 1571 | global.cursor_x = cx; 1572 | global.cursor_y = cy; 1573 | return TB_OK; 1574 | } 1575 | 1576 | int tb_hide_cursor(void) { 1577 | if_not_init_return(); 1578 | int rv; 1579 | if (global.cursor_x >= 0) { 1580 | if_err_return(rv, 1581 | bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); 1582 | } 1583 | global.cursor_x = -1; 1584 | global.cursor_y = -1; 1585 | return TB_OK; 1586 | } 1587 | 1588 | int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) { 1589 | if_not_init_return(); 1590 | return tb_set_cell_ex(x, y, &ch, 1, fg, bg); 1591 | } 1592 | 1593 | int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, 1594 | uintattr_t bg) { 1595 | if_not_init_return(); 1596 | int rv; 1597 | struct tb_cell *cell; 1598 | if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); 1599 | if_err_return(rv, cell_set(cell, ch, nch, fg, bg)); 1600 | return TB_OK; 1601 | } 1602 | 1603 | int tb_extend_cell(int x, int y, uint32_t ch) { 1604 | if_not_init_return(); 1605 | #ifdef TB_OPT_EGC 1606 | int rv; 1607 | struct tb_cell *cell; 1608 | size_t nech; 1609 | if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); 1610 | if (cell->nech > 0) { // append to ech 1611 | nech = cell->nech + 1; 1612 | if_err_return(rv, cell_reserve_ech(cell, nech)); 1613 | cell->ech[nech - 1] = ch; 1614 | } else { // make new ech 1615 | nech = 2; 1616 | if_err_return(rv, cell_reserve_ech(cell, nech)); 1617 | cell->ech[0] = cell->ch; 1618 | cell->ech[1] = ch; 1619 | } 1620 | cell->ech[nech] = '\0'; 1621 | cell->nech = nech; 1622 | return TB_OK; 1623 | #else 1624 | (void)x; 1625 | (void)y; 1626 | (void)ch; 1627 | return TB_ERR; 1628 | #endif 1629 | } 1630 | 1631 | int tb_set_input_mode(int mode) { 1632 | if_not_init_return(); 1633 | if (mode == TB_INPUT_CURRENT) { 1634 | return global.input_mode; 1635 | } 1636 | 1637 | if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) { 1638 | mode |= TB_INPUT_ESC; 1639 | } 1640 | 1641 | if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) 1642 | { 1643 | mode &= ~TB_INPUT_ALT; 1644 | } 1645 | 1646 | if (mode & TB_INPUT_MOUSE) { 1647 | bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE); 1648 | bytebuf_flush(&global.out, global.wfd); 1649 | } else { 1650 | bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); 1651 | bytebuf_flush(&global.out, global.wfd); 1652 | } 1653 | 1654 | global.input_mode = mode; 1655 | return TB_OK; 1656 | } 1657 | 1658 | int tb_set_output_mode(int mode) { 1659 | if_not_init_return(); 1660 | switch (mode) { 1661 | case TB_OUTPUT_CURRENT: 1662 | return global.output_mode; 1663 | case TB_OUTPUT_NORMAL: 1664 | case TB_OUTPUT_256: 1665 | case TB_OUTPUT_216: 1666 | case TB_OUTPUT_GRAYSCALE: 1667 | #ifdef TB_OPT_TRUECOLOR 1668 | case TB_OUTPUT_TRUECOLOR: 1669 | #endif 1670 | global.output_mode = mode; 1671 | return TB_OK; 1672 | } 1673 | return TB_ERR; 1674 | } 1675 | 1676 | int tb_peek_event(struct tb_event *event, int timeout_ms) { 1677 | if_not_init_return(); 1678 | return wait_event(event, timeout_ms); 1679 | } 1680 | 1681 | int tb_poll_event(struct tb_event *event) { 1682 | if_not_init_return(); 1683 | return wait_event(event, -1); 1684 | } 1685 | 1686 | int tb_get_fds(int *ttyfd, int *resizefd) { 1687 | if_not_init_return(); 1688 | 1689 | *ttyfd = global.rfd; 1690 | *resizefd = global.resize_pipefd[0]; 1691 | 1692 | return TB_OK; 1693 | } 1694 | 1695 | int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) { 1696 | return tb_print_ex(x, y, fg, bg, NULL, str); 1697 | } 1698 | 1699 | int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, 1700 | const char *str) { 1701 | int rv; 1702 | uint32_t uni; 1703 | int w, ix = x; 1704 | if (out_w) { 1705 | *out_w = 0; 1706 | } 1707 | while (*str) { 1708 | str += tb_utf8_char_to_unicode(&uni, str); 1709 | w = wcwidth((wchar_t)uni); 1710 | if (w < 0) { 1711 | w = 1; 1712 | } 1713 | if (w == 0 && x > ix) { 1714 | if_err_return(rv, tb_extend_cell(x - 1, y, uni)); 1715 | } else { 1716 | if_err_return(rv, tb_set_cell(x, y, uni, fg, bg)); 1717 | } 1718 | x += w; 1719 | if (out_w) { 1720 | *out_w += w; 1721 | } 1722 | } 1723 | return TB_OK; 1724 | } 1725 | 1726 | int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, 1727 | ...) { 1728 | int rv; 1729 | va_list vl; 1730 | va_start(vl, fmt); 1731 | rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl); 1732 | va_end(vl); 1733 | return rv; 1734 | } 1735 | 1736 | int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, 1737 | const char *fmt, ...) { 1738 | int rv; 1739 | va_list vl; 1740 | va_start(vl, fmt); 1741 | rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl); 1742 | va_end(vl); 1743 | return rv; 1744 | } 1745 | 1746 | int tb_send(const char *buf, size_t nbuf) { 1747 | return bytebuf_nputs(&global.out, buf, nbuf); 1748 | } 1749 | 1750 | int tb_sendf(const char *fmt, ...) { 1751 | int rv; 1752 | char buf[TB_OPT_PRINTF_BUF]; 1753 | va_list vl; 1754 | va_start(vl, fmt); 1755 | rv = vsnprintf(buf, sizeof(buf), fmt, vl); 1756 | va_end(vl); 1757 | if (rv < 0 || rv >= (int)sizeof(buf)) { 1758 | return TB_ERR; 1759 | } 1760 | return tb_send(buf, (size_t)rv); 1761 | } 1762 | 1763 | int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) { 1764 | switch (fn_type) { 1765 | case TB_FUNC_EXTRACT_PRE: 1766 | global.fn_extract_esc_pre = fn; 1767 | return TB_OK; 1768 | case TB_FUNC_EXTRACT_POST: 1769 | global.fn_extract_esc_post = fn; 1770 | return TB_OK; 1771 | } 1772 | return TB_ERR; 1773 | } 1774 | 1775 | struct tb_cell *tb_cell_buffer(void) { 1776 | if (!global.initialized) 1777 | return NULL; 1778 | return global.back.cells; 1779 | } 1780 | 1781 | int tb_utf8_char_length(char c) { 1782 | return utf8_length[(unsigned char)c]; 1783 | } 1784 | 1785 | int tb_utf8_char_to_unicode(uint32_t *out, const char *c) { 1786 | if (*c == 0) { 1787 | return TB_ERR; 1788 | } 1789 | 1790 | int i; 1791 | unsigned char len = tb_utf8_char_length(*c); 1792 | unsigned char mask = utf8_mask[len - 1]; 1793 | uint32_t result = c[0] & mask; 1794 | for (i = 1; i < len; ++i) { 1795 | result <<= 6; 1796 | result |= c[i] & 0x3f; 1797 | } 1798 | 1799 | *out = result; 1800 | return (int)len; 1801 | } 1802 | 1803 | int tb_utf8_unicode_to_char(char *out, uint32_t c) { 1804 | int len = 0; 1805 | int first; 1806 | int i; 1807 | 1808 | if (c < 0x80) { 1809 | first = 0; 1810 | len = 1; 1811 | } else if (c < 0x800) { 1812 | first = 0xc0; 1813 | len = 2; 1814 | } else if (c < 0x10000) { 1815 | first = 0xe0; 1816 | len = 3; 1817 | } else if (c < 0x200000) { 1818 | first = 0xf0; 1819 | len = 4; 1820 | } else if (c < 0x4000000) { 1821 | first = 0xf8; 1822 | len = 5; 1823 | } else { 1824 | first = 0xfc; 1825 | len = 6; 1826 | } 1827 | 1828 | for (i = len - 1; i > 0; --i) { 1829 | out[i] = (c & 0x3f) | 0x80; 1830 | c >>= 6; 1831 | } 1832 | out[0] = c | first; 1833 | 1834 | return len; 1835 | } 1836 | 1837 | int tb_last_errno(void) { 1838 | return global.last_errno; 1839 | } 1840 | 1841 | const char *tb_strerror(int err) { 1842 | switch (err) { 1843 | case TB_OK: 1844 | return "Success"; 1845 | case TB_ERR_NEED_MORE: 1846 | return "Not enough input"; 1847 | case TB_ERR_INIT_ALREADY: 1848 | return "Termbox initialized already"; 1849 | case TB_ERR_MEM: 1850 | return "Out of memory"; 1851 | case TB_ERR_NO_EVENT: 1852 | return "No event"; 1853 | case TB_ERR_NO_TERM: 1854 | return "No TERM in environment"; 1855 | case TB_ERR_NOT_INIT: 1856 | return "Termbox not initialized"; 1857 | case TB_ERR_OUT_OF_BOUNDS: 1858 | return "Out of bounds"; 1859 | case TB_ERR_UNSUPPORTED_TERM: 1860 | return "Unsupported terminal"; 1861 | case TB_ERR_CAP_COLLISION: 1862 | return "Termcaps collision"; 1863 | case TB_ERR_RESIZE_SSCANF: 1864 | return "Terminal width/height not received by sscanf() after " 1865 | "resize"; 1866 | case TB_ERR: 1867 | case TB_ERR_INIT_OPEN: 1868 | case TB_ERR_READ: 1869 | case TB_ERR_RESIZE_IOCTL: 1870 | case TB_ERR_RESIZE_PIPE: 1871 | case TB_ERR_RESIZE_SIGACTION: 1872 | case TB_ERR_POLL: 1873 | case TB_ERR_TCGETATTR: 1874 | case TB_ERR_TCSETATTR: 1875 | case TB_ERR_RESIZE_WRITE: 1876 | case TB_ERR_RESIZE_POLL: 1877 | case TB_ERR_RESIZE_READ: 1878 | default: 1879 | strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf)); 1880 | return (const char *)global.errbuf; 1881 | } 1882 | } 1883 | 1884 | int tb_has_truecolor(void) { 1885 | #ifdef TB_OPT_TRUECOLOR 1886 | return 1; 1887 | #else 1888 | return 0; 1889 | #endif 1890 | } 1891 | 1892 | int tb_has_egc(void) { 1893 | #ifdef TB_OPT_EGC 1894 | return 1; 1895 | #else 1896 | return 0; 1897 | #endif 1898 | } 1899 | 1900 | const char *tb_version(void) { 1901 | return TB_VERSION_STR; 1902 | } 1903 | 1904 | static int tb_reset(void) { 1905 | int ttyfd_open = global.ttyfd_open; 1906 | memset(&global, 0, sizeof(global)); 1907 | global.ttyfd = -1; 1908 | global.rfd = -1; 1909 | global.wfd = -1; 1910 | global.ttyfd_open = ttyfd_open; 1911 | global.resize_pipefd[0] = -1; 1912 | global.resize_pipefd[1] = -1; 1913 | global.width = -1; 1914 | global.height = -1; 1915 | global.cursor_x = -1; 1916 | global.cursor_y = -1; 1917 | global.last_x = -1; 1918 | global.last_y = -1; 1919 | global.fg = TB_DEFAULT; 1920 | global.bg = TB_DEFAULT; 1921 | global.last_fg = ~global.fg; 1922 | global.last_bg = ~global.bg; 1923 | global.input_mode = TB_INPUT_ESC; 1924 | global.output_mode = TB_OUTPUT_NORMAL; 1925 | return TB_OK; 1926 | } 1927 | 1928 | static int init_term_attrs(void) { 1929 | if (global.ttyfd < 0) { 1930 | return TB_OK; 1931 | } 1932 | 1933 | if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) { 1934 | global.last_errno = errno; 1935 | return TB_ERR_TCGETATTR; 1936 | } 1937 | 1938 | struct termios tios; 1939 | memcpy(&tios, &global.orig_tios, sizeof(tios)); 1940 | global.has_orig_tios = 1; 1941 | 1942 | cfmakeraw(&tios); 1943 | tios.c_cc[VMIN] = 1; 1944 | tios.c_cc[VTIME] = 0; 1945 | 1946 | if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) { 1947 | global.last_errno = errno; 1948 | return TB_ERR_TCSETATTR; 1949 | } 1950 | 1951 | return TB_OK; 1952 | } 1953 | 1954 | int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, 1955 | const char *fmt, va_list vl) { 1956 | int rv; 1957 | char buf[TB_OPT_PRINTF_BUF]; 1958 | rv = vsnprintf(buf, sizeof(buf), fmt, vl); 1959 | if (rv < 0 || rv >= (int)sizeof(buf)) { 1960 | return TB_ERR; 1961 | } 1962 | return tb_print_ex(x, y, fg, bg, out_w, buf); 1963 | } 1964 | 1965 | static int init_term_caps(void) { 1966 | if (load_terminfo() == TB_OK) { 1967 | return parse_terminfo_caps(); 1968 | } 1969 | return load_builtin_caps(); 1970 | } 1971 | 1972 | static int init_cap_trie(void) { 1973 | int rv, i; 1974 | 1975 | // Add caps from terminfo or built-in 1976 | for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { 1977 | if_err_return(rv, cap_trie_add(global.caps[i], tb_key_i(i), 0)); 1978 | } 1979 | 1980 | // Add built-in mod caps 1981 | for (i = 0; builtin_mod_caps[i].cap != NULL; i++) { 1982 | rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key, 1983 | builtin_mod_caps[i].mod); 1984 | // Collisions are OK. This can happen if global.caps collides with 1985 | // builtin_mod_caps. It is desirable to give precedence to global.caps 1986 | // here. 1987 | if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) { 1988 | return rv; 1989 | } 1990 | } 1991 | 1992 | return TB_OK; 1993 | } 1994 | 1995 | static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) { 1996 | struct cap_trie_t *next, *node = &global.cap_trie; 1997 | size_t i, j; 1998 | for (i = 0; cap[i] != '\0'; i++) { 1999 | char c = cap[i]; 2000 | next = NULL; 2001 | 2002 | // Check if c is already a child of node 2003 | for (j = 0; j < node->nchildren; j++) { 2004 | if (node->children[j].c == c) { 2005 | next = &node->children[j]; 2006 | break; 2007 | } 2008 | } 2009 | if (!next) { 2010 | // We need to add a new child to node 2011 | node->nchildren += 1; 2012 | node->children = 2013 | tb_realloc(node->children, sizeof(*node) * node->nchildren); 2014 | if (!node->children) { 2015 | return TB_ERR_MEM; 2016 | } 2017 | next = &node->children[node->nchildren - 1]; 2018 | memset(next, 0, sizeof(*next)); 2019 | next->c = c; 2020 | } 2021 | 2022 | // Continue 2023 | node = next; 2024 | } 2025 | 2026 | if (node->is_leaf) { 2027 | // Already a leaf here 2028 | return TB_ERR_CAP_COLLISION; 2029 | } 2030 | 2031 | node->is_leaf = 1; 2032 | node->key = key; 2033 | node->mod = mod; 2034 | return TB_OK; 2035 | } 2036 | 2037 | static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, 2038 | size_t *depth) { 2039 | struct cap_trie_t *next, *node = &global.cap_trie; 2040 | size_t i, j; 2041 | *last = node; 2042 | *depth = 0; 2043 | for (i = 0; i < nbuf; i++) { 2044 | char c = buf[i]; 2045 | next = NULL; 2046 | 2047 | // Find c in node.children 2048 | for (j = 0; j < node->nchildren; j++) { 2049 | if (node->children[j].c == c) { 2050 | next = &node->children[j]; 2051 | break; 2052 | } 2053 | } 2054 | if (!next) { 2055 | // Not found 2056 | return TB_OK; 2057 | } 2058 | node = next; 2059 | *last = node; 2060 | *depth += 1; 2061 | if (node->is_leaf && node->nchildren < 1) { 2062 | break; 2063 | } 2064 | } 2065 | return TB_OK; 2066 | } 2067 | 2068 | static int cap_trie_deinit(struct cap_trie_t *node) { 2069 | size_t j; 2070 | for (j = 0; j < node->nchildren; j++) { 2071 | cap_trie_deinit(&node->children[j]); 2072 | } 2073 | if (node->children) { 2074 | tb_free(node->children); 2075 | } 2076 | memset(node, 0, sizeof(*node)); 2077 | return TB_OK; 2078 | } 2079 | 2080 | static int init_resize_handler(void) { 2081 | if (pipe(global.resize_pipefd) != 0) { 2082 | global.last_errno = errno; 2083 | return TB_ERR_RESIZE_PIPE; 2084 | } 2085 | 2086 | struct sigaction sa; 2087 | memset(&sa, 0, sizeof(sa)); 2088 | sa.sa_handler = handle_resize; 2089 | if (sigaction(SIGWINCH, &sa, NULL) != 0) { 2090 | global.last_errno = errno; 2091 | return TB_ERR_RESIZE_SIGACTION; 2092 | } 2093 | 2094 | return TB_OK; 2095 | } 2096 | 2097 | static int send_init_escape_codes(void) { 2098 | int rv; 2099 | if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_CA])); 2100 | if_err_return(rv, 2101 | bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_KEYPAD])); 2102 | if_err_return(rv, 2103 | bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); 2104 | return TB_OK; 2105 | } 2106 | 2107 | static int send_clear(void) { 2108 | int rv; 2109 | 2110 | if_err_return(rv, send_attr(global.fg, global.bg)); 2111 | if_err_return(rv, 2112 | bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN])); 2113 | 2114 | if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); 2115 | if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); 2116 | 2117 | global.last_x = -1; 2118 | global.last_y = -1; 2119 | 2120 | return TB_OK; 2121 | } 2122 | 2123 | static int update_term_size(void) { 2124 | int rv, ioctl_errno; 2125 | 2126 | if (global.ttyfd < 0) { 2127 | return TB_OK; 2128 | } 2129 | 2130 | struct winsize sz; 2131 | memset(&sz, 0, sizeof(sz)); 2132 | 2133 | // Try ioctl TIOCGWINSZ 2134 | if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) { 2135 | global.width = sz.ws_col; 2136 | global.height = sz.ws_row; 2137 | return TB_OK; 2138 | } 2139 | ioctl_errno = errno; 2140 | 2141 | // Try >cursor(9999,9999), >u7, = 0) { 2204 | bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); 2205 | bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]); 2206 | bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]); 2207 | bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]); 2208 | bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]); 2209 | bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); 2210 | bytebuf_flush(&global.out, global.wfd); 2211 | } 2212 | if (global.ttyfd >= 0) { 2213 | if (global.has_orig_tios) { 2214 | tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios); 2215 | } 2216 | if (global.ttyfd_open) { 2217 | close(global.ttyfd); 2218 | global.ttyfd_open = 0; 2219 | } 2220 | } 2221 | 2222 | sigaction(SIGWINCH, &(struct sigaction){.sa_handler = SIG_DFL}, NULL); 2223 | if (global.resize_pipefd[0] >= 0) 2224 | close(global.resize_pipefd[0]); 2225 | if (global.resize_pipefd[1] >= 0) 2226 | close(global.resize_pipefd[1]); 2227 | 2228 | cellbuf_free(&global.back); 2229 | cellbuf_free(&global.front); 2230 | bytebuf_free(&global.in); 2231 | bytebuf_free(&global.out); 2232 | 2233 | if (global.terminfo) 2234 | tb_free(global.terminfo); 2235 | 2236 | cap_trie_deinit(&global.cap_trie); 2237 | 2238 | tb_reset(); 2239 | return TB_OK; 2240 | } 2241 | 2242 | static int load_terminfo(void) { 2243 | int rv; 2244 | char tmp[PATH_MAX]; 2245 | 2246 | // See terminfo(5) "Fetching Compiled Descriptions" for a description of 2247 | // this behavior. Some of these paths are compile-time ncurses options, so 2248 | // best guesses are used here. 2249 | const char *term = getenv("TERM"); 2250 | if (!term) { 2251 | return TB_ERR; 2252 | } 2253 | 2254 | // If TERMINFO is set, try that directory and stop 2255 | const char *terminfo = getenv("TERMINFO"); 2256 | if (terminfo) { 2257 | return load_terminfo_from_path(terminfo, term); 2258 | } 2259 | 2260 | // Next try ~/.terminfo 2261 | const char *home = getenv("HOME"); 2262 | if (home) { 2263 | snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home); 2264 | if_ok_return(rv, load_terminfo_from_path(tmp, term)); 2265 | } 2266 | 2267 | // Next try TERMINFO_DIRS 2268 | // 2269 | // Note, empty entries are supposed to be interpretted as the "compiled-in 2270 | // default", which is of course system-dependent. Previously /etc/terminfo 2271 | // was used here. Let's skip empty entries altogether rather than give 2272 | // precedence to a guess, and check common paths after this loop. 2273 | const char *dirs = getenv("TERMINFO_DIRS"); 2274 | if (dirs) { 2275 | snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs); 2276 | char *dir = strtok(tmp, ":"); 2277 | while (dir) { 2278 | const char *cdir = dir; 2279 | if (*cdir != '\0') { 2280 | if_ok_return(rv, load_terminfo_from_path(cdir, term)); 2281 | } 2282 | dir = strtok(NULL, ":"); 2283 | } 2284 | } 2285 | 2286 | #ifdef TB_TERMINFO_DIR 2287 | if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term)); 2288 | #endif 2289 | if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term)); 2290 | if_ok_return(rv, 2291 | load_terminfo_from_path("/usr/local/share/terminfo", term)); 2292 | if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term)); 2293 | if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term)); 2294 | if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term)); 2295 | if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term)); 2296 | if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term)); 2297 | if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term)); 2298 | 2299 | return TB_ERR; 2300 | } 2301 | 2302 | static int load_terminfo_from_path(const char *path, const char *term) { 2303 | int rv; 2304 | char tmp[PATH_MAX]; 2305 | 2306 | // Look for term at this terminfo location, e.g., /x/xterm 2307 | snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); 2308 | if_ok_return(rv, read_terminfo_path(tmp)); 2309 | 2310 | #ifdef __APPLE__ 2311 | // Try the Darwin equivalent path, e.g., /78/xterm 2312 | snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); 2313 | return read_terminfo_path(tmp); 2314 | #endif 2315 | 2316 | return TB_ERR; 2317 | } 2318 | 2319 | static int read_terminfo_path(const char *path) { 2320 | FILE *fp = fopen(path, "rb"); 2321 | if (!fp) { 2322 | return TB_ERR; 2323 | } 2324 | 2325 | struct stat st; 2326 | if (fstat(fileno(fp), &st) != 0) { 2327 | fclose(fp); 2328 | return TB_ERR; 2329 | } 2330 | 2331 | size_t fsize = st.st_size; 2332 | char *data = tb_malloc(fsize); 2333 | if (!data) { 2334 | fclose(fp); 2335 | return TB_ERR; 2336 | } 2337 | 2338 | if (fread(data, 1, fsize, fp) != fsize) { 2339 | fclose(fp); 2340 | tb_free(data); 2341 | return TB_ERR; 2342 | } 2343 | 2344 | global.terminfo = data; 2345 | global.nterminfo = fsize; 2346 | 2347 | fclose(fp); 2348 | return TB_OK; 2349 | } 2350 | 2351 | static int parse_terminfo_caps(void) { 2352 | // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a 2353 | // description of this behavior. 2354 | 2355 | // Ensure there's at least a header's worth of data 2356 | if (global.nterminfo < 6) { 2357 | return TB_ERR; 2358 | } 2359 | 2360 | int16_t *header = (int16_t *)global.terminfo; 2361 | // header[0] the magic number (octal 0432 or 01036) 2362 | // header[1] the size, in bytes, of the names section 2363 | // header[2] the number of bytes in the boolean section 2364 | // header[3] the number of short integers in the numbers section 2365 | // header[4] the number of offsets (short integers) in the strings section 2366 | // header[5] the size, in bytes, of the string table 2367 | 2368 | // Legacy ints are 16-bit, extended ints are 32-bit 2369 | const int bytes_per_int = header[0] == 01036 ? 4 // 32-bit 2370 | : 2; // 16-bit 2371 | 2372 | // > Between the boolean section and the number section, a null byte will be 2373 | // > inserted, if necessary, to ensure that the number section begins on an 2374 | // > even byte 2375 | const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0; 2376 | 2377 | const int pos_str_offsets = 2378 | (6 * sizeof(int16_t)) // header (12 bytes) 2379 | + header[1] // length of names section 2380 | + header[2] // length of boolean section 2381 | + align_offset + 2382 | (header[3] * bytes_per_int); // length of numbers section 2383 | 2384 | const int pos_str_table = 2385 | pos_str_offsets + 2386 | (header[4] * sizeof(int16_t)); // length of string offsets table 2387 | 2388 | // Load caps 2389 | int i; 2390 | for (i = 0; i < TB_CAP__COUNT; i++) { 2391 | const char *cap = get_terminfo_string(pos_str_offsets, pos_str_table, 2392 | header[5], terminfo_cap_indexes[i]); 2393 | if (!cap) { 2394 | // Something is not right 2395 | return TB_ERR; 2396 | } 2397 | global.caps[i] = cap; 2398 | } 2399 | 2400 | return TB_OK; 2401 | } 2402 | 2403 | static int load_builtin_caps(void) { 2404 | int i, j; 2405 | const char *term = getenv("TERM"); 2406 | 2407 | if (!term) { 2408 | return TB_ERR_NO_TERM; 2409 | } 2410 | 2411 | // Check for exact TERM match 2412 | for (i = 0; builtin_terms[i].name != NULL; i++) { 2413 | if (strcmp(term, builtin_terms[i].name) == 0) { 2414 | for (j = 0; j < TB_CAP__COUNT; j++) { 2415 | global.caps[j] = builtin_terms[i].caps[j]; 2416 | } 2417 | return TB_OK; 2418 | } 2419 | } 2420 | 2421 | // Check for partial TERM or alias match 2422 | for (i = 0; builtin_terms[i].name != NULL; i++) { 2423 | if (strstr(term, builtin_terms[i].name) != NULL || 2424 | (*(builtin_terms[i].alias) != '\0' && 2425 | strstr(term, builtin_terms[i].alias) != NULL)) 2426 | { 2427 | for (j = 0; j < TB_CAP__COUNT; j++) { 2428 | global.caps[j] = builtin_terms[i].caps[j]; 2429 | } 2430 | return TB_OK; 2431 | } 2432 | } 2433 | 2434 | return TB_ERR_UNSUPPORTED_TERM; 2435 | } 2436 | 2437 | static const char *get_terminfo_string(int16_t str_offsets_pos, 2438 | int16_t str_table_pos, int16_t str_table_len, int16_t str_index) { 2439 | const int16_t *str_offset = 2440 | (int16_t *)(global.terminfo + (int)str_offsets_pos + 2441 | ((int)str_index * (int)sizeof(int16_t))); 2442 | if (*str_offset < 0) { 2443 | // A negative indicates the cap is absent from this terminal 2444 | return ""; 2445 | } 2446 | if (*str_offset >= str_table_len) { 2447 | // Invalid string offset 2448 | return NULL; 2449 | } 2450 | if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { 2451 | // Truncated/corrupt terminfo? 2452 | return NULL; 2453 | } 2454 | return ( 2455 | const char *)(global.terminfo + (int)str_table_pos + (int)*str_offset); 2456 | } 2457 | 2458 | static int wait_event(struct tb_event *event, int timeout) { 2459 | int rv; 2460 | char buf[TB_OPT_READ_BUF]; 2461 | 2462 | memset(event, 0, sizeof(*event)); 2463 | if_ok_return(rv, extract_event(event)); 2464 | 2465 | fd_set fds; 2466 | struct timeval tv; 2467 | tv.tv_sec = timeout / 1000; 2468 | tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; 2469 | 2470 | do { 2471 | FD_ZERO(&fds); 2472 | FD_SET(global.rfd, &fds); 2473 | FD_SET(global.resize_pipefd[0], &fds); 2474 | 2475 | int maxfd = global.resize_pipefd[0] > global.rfd 2476 | ? global.resize_pipefd[0] 2477 | : global.rfd; 2478 | 2479 | int select_rv = 2480 | select(maxfd + 1, &fds, NULL, NULL, (timeout < 0) ? NULL : &tv); 2481 | 2482 | if (select_rv < 0) { 2483 | // Let EINTR/EAGAIN bubble up 2484 | global.last_errno = errno; 2485 | return TB_ERR_POLL; 2486 | } else if (select_rv == 0) { 2487 | return TB_ERR_NO_EVENT; 2488 | } 2489 | 2490 | int tty_has_events = (FD_ISSET(global.rfd, &fds)); 2491 | int resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds)); 2492 | 2493 | if (tty_has_events) { 2494 | ssize_t read_rv = read(global.rfd, buf, sizeof(buf)); 2495 | if (read_rv < 0) { 2496 | global.last_errno = errno; 2497 | return TB_ERR_READ; 2498 | } else if (read_rv > 0) { 2499 | bytebuf_nputs(&global.in, buf, read_rv); 2500 | } 2501 | } 2502 | 2503 | if (resize_has_events) { 2504 | int ignore = 0; 2505 | read(global.resize_pipefd[0], &ignore, sizeof(ignore)); 2506 | // TODO Harden against errors encountered mid-resize 2507 | if_err_return(rv, update_term_size()); 2508 | if_err_return(rv, resize_cellbufs()); 2509 | event->type = TB_EVENT_RESIZE; 2510 | event->w = global.width; 2511 | event->h = global.height; 2512 | return TB_OK; 2513 | } 2514 | 2515 | memset(event, 0, sizeof(*event)); 2516 | if_ok_return(rv, extract_event(event)); 2517 | } while (timeout == -1); 2518 | 2519 | return rv; 2520 | } 2521 | 2522 | static int extract_event(struct tb_event *event) { 2523 | int rv; 2524 | struct bytebuf_t *in = &global.in; 2525 | 2526 | if (in->len == 0) { 2527 | return TB_ERR; 2528 | } 2529 | 2530 | if (in->buf[0] == '\x1b') { 2531 | // Escape sequence? 2532 | // In TB_INPUT_ESC, skip if the buffer is a single escape char 2533 | if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) { 2534 | if_ok_or_need_more_return(rv, extract_esc(event)); 2535 | } 2536 | 2537 | // Escape key? 2538 | if (global.input_mode & TB_INPUT_ESC) { 2539 | event->type = TB_EVENT_KEY; 2540 | event->ch = 0; 2541 | event->key = TB_KEY_ESC; 2542 | event->mod = 0; 2543 | bytebuf_shift(in, 1); 2544 | return TB_OK; 2545 | } 2546 | 2547 | // Recurse for alt key 2548 | event->mod |= TB_MOD_ALT; 2549 | bytebuf_shift(in, 1); 2550 | return extract_event(event); 2551 | } 2552 | 2553 | // ASCII control key? 2554 | if ((uint16_t)in->buf[0] < TB_KEY_SPACE || in->buf[0] == TB_KEY_BACKSPACE2) 2555 | { 2556 | event->type = TB_EVENT_KEY; 2557 | event->ch = 0; 2558 | event->key = (uint16_t)in->buf[0]; 2559 | event->mod |= TB_MOD_CTRL; 2560 | bytebuf_shift(in, 1); 2561 | return TB_OK; 2562 | } 2563 | 2564 | // UTF-8? 2565 | if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) { 2566 | event->type = TB_EVENT_KEY; 2567 | tb_utf8_char_to_unicode(&event->ch, in->buf); 2568 | event->key = 0; 2569 | bytebuf_shift(in, tb_utf8_char_length(in->buf[0])); 2570 | return TB_OK; 2571 | } 2572 | 2573 | // Need more input 2574 | return TB_ERR; 2575 | } 2576 | 2577 | static int extract_esc(struct tb_event *event) { 2578 | int rv; 2579 | if_ok_or_need_more_return(rv, extract_esc_user(event, 0)); 2580 | if_ok_or_need_more_return(rv, extract_esc_cap(event)); 2581 | if_ok_or_need_more_return(rv, extract_esc_mouse(event)); 2582 | if_ok_or_need_more_return(rv, extract_esc_user(event, 1)); 2583 | return TB_ERR; 2584 | } 2585 | 2586 | static int extract_esc_user(struct tb_event *event, int is_post) { 2587 | int rv; 2588 | size_t consumed = 0; 2589 | struct bytebuf_t *in = &global.in; 2590 | int (*fn)(struct tb_event *, size_t *); 2591 | 2592 | fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre; 2593 | 2594 | if (!fn) { 2595 | return TB_ERR; 2596 | } 2597 | 2598 | rv = fn(event, &consumed); 2599 | if (rv == TB_OK) { 2600 | bytebuf_shift(in, consumed); 2601 | } 2602 | 2603 | if_ok_or_need_more_return(rv, rv); 2604 | return TB_ERR; 2605 | } 2606 | 2607 | static int extract_esc_cap(struct tb_event *event) { 2608 | int rv; 2609 | struct bytebuf_t *in = &global.in; 2610 | struct cap_trie_t *node; 2611 | size_t depth; 2612 | 2613 | if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth)); 2614 | if (node->is_leaf) { 2615 | // Found a leaf node 2616 | event->type = TB_EVENT_KEY; 2617 | event->ch = 0; 2618 | event->key = node->key; 2619 | event->mod = node->mod; 2620 | bytebuf_shift(in, depth); 2621 | return TB_OK; 2622 | } else if (node->nchildren > 0 && in->len <= depth) { 2623 | // Found a branch node (not enough input) 2624 | return TB_ERR_NEED_MORE; 2625 | } 2626 | 2627 | return TB_ERR; 2628 | } 2629 | 2630 | static int extract_esc_mouse(struct tb_event *event) { 2631 | struct bytebuf_t *in = &global.in; 2632 | 2633 | enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; 2634 | 2635 | char *cmp[TYPE_MAX] = {// 2636 | // X10 mouse encoding, the simplest one 2637 | // \x1b [ M Cb Cx Cy 2638 | [TYPE_VT200] = "\x1b[M", 2639 | // xterm 1006 extended mode or urxvt 1015 extended mode 2640 | // xterm: \x1b [ < Cb ; Cx ; Cy (M or m) 2641 | [TYPE_1006] = "\x1b[<", 2642 | // urxvt: \x1b [ Cb ; Cx ; Cy M 2643 | [TYPE_1015] = "\x1b["}; 2644 | 2645 | enum type type = 0; 2646 | int ret = TB_ERR; 2647 | 2648 | // Unrolled at compile-time (probably) 2649 | for (; type < TYPE_MAX; type++) { 2650 | size_t size = strlen(cmp[type]); 2651 | 2652 | if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) { 2653 | break; 2654 | } 2655 | } 2656 | 2657 | if (type == TYPE_MAX) { 2658 | ret = TB_ERR; // No match 2659 | return ret; 2660 | } 2661 | 2662 | size_t buf_shift = 0; 2663 | 2664 | switch (type) { 2665 | case TYPE_VT200: 2666 | if (in->len >= 6) { 2667 | int b = in->buf[3] - 0x20; 2668 | int fail = 0; 2669 | 2670 | switch (b & 3) { 2671 | case 0: 2672 | event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP 2673 | : TB_KEY_MOUSE_LEFT; 2674 | break; 2675 | case 1: 2676 | event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN 2677 | : TB_KEY_MOUSE_MIDDLE; 2678 | break; 2679 | case 2: 2680 | event->key = TB_KEY_MOUSE_RIGHT; 2681 | break; 2682 | case 3: 2683 | event->key = TB_KEY_MOUSE_RELEASE; 2684 | break; 2685 | default: 2686 | ret = TB_ERR; 2687 | fail = 1; 2688 | break; 2689 | } 2690 | 2691 | if (!fail) { 2692 | if ((b & 32) != 0) { 2693 | event->mod |= TB_MOD_MOTION; 2694 | } 2695 | 2696 | // the coord is 1,1 for upper left 2697 | event->x = ((uint8_t)in->buf[4]) - 0x21; 2698 | event->y = ((uint8_t)in->buf[5]) - 0x21; 2699 | 2700 | ret = TB_OK; 2701 | } 2702 | 2703 | buf_shift = 6; 2704 | } 2705 | break; 2706 | case TYPE_1006: 2707 | // fallthrough 2708 | case TYPE_1015: { 2709 | size_t index_fail = (size_t)-1; 2710 | 2711 | enum { 2712 | FIRST_M = 0, 2713 | FIRST_SEMICOLON, 2714 | LAST_SEMICOLON, 2715 | FIRST_LAST_MAX 2716 | }; 2717 | 2718 | size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail, 2719 | index_fail}; 2720 | int m_is_capital = 0; 2721 | 2722 | for (size_t i = 0; i < in->len; i++) { 2723 | if (in->buf[i] == ';') { 2724 | if (indices[FIRST_SEMICOLON] == index_fail) { 2725 | indices[FIRST_SEMICOLON] = i; 2726 | } else { 2727 | indices[LAST_SEMICOLON] = i; 2728 | } 2729 | } else if (indices[FIRST_M] == index_fail) { 2730 | if (in->buf[i] == 'm' || in->buf[i] == 'M') { 2731 | m_is_capital = (in->buf[i] == 'M'); 2732 | indices[FIRST_M] = i; 2733 | } 2734 | } 2735 | } 2736 | 2737 | if (indices[FIRST_M] == index_fail || 2738 | indices[FIRST_SEMICOLON] == index_fail || 2739 | indices[LAST_SEMICOLON] == index_fail) 2740 | { 2741 | ret = TB_ERR; 2742 | } else { 2743 | int start = (type == TYPE_1015 ? 2 : 3); 2744 | 2745 | unsigned n1 = strtoul(&in->buf[start], NULL, 10); 2746 | unsigned n2 = 2747 | strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); 2748 | unsigned n3 = 2749 | strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10); 2750 | 2751 | if (type == TYPE_1015) { 2752 | n1 -= 0x20; 2753 | } 2754 | 2755 | int fail = 0; 2756 | 2757 | switch (n1 & 3) { 2758 | case 0: 2759 | event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP 2760 | : TB_KEY_MOUSE_LEFT; 2761 | break; 2762 | case 1: 2763 | event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN 2764 | : TB_KEY_MOUSE_MIDDLE; 2765 | break; 2766 | case 2: 2767 | event->key = TB_KEY_MOUSE_RIGHT; 2768 | break; 2769 | case 3: 2770 | event->key = TB_KEY_MOUSE_RELEASE; 2771 | break; 2772 | default: 2773 | ret = TB_ERR; 2774 | fail = 1; 2775 | break; 2776 | } 2777 | 2778 | buf_shift = in->len; 2779 | 2780 | if (!fail) { 2781 | if (!m_is_capital) { 2782 | // on xterm mouse release is signaled by lowercase m 2783 | event->key = TB_KEY_MOUSE_RELEASE; 2784 | } 2785 | 2786 | if ((n1 & 32) != 0) { 2787 | event->mod |= TB_MOD_MOTION; 2788 | } 2789 | 2790 | event->x = ((uint8_t)n2) - 1; 2791 | event->y = ((uint8_t)n3) - 1; 2792 | 2793 | ret = TB_OK; 2794 | } 2795 | } 2796 | } break; 2797 | case TYPE_MAX: 2798 | ret = TB_ERR; 2799 | } 2800 | 2801 | if (buf_shift > 0) { 2802 | bytebuf_shift(in, buf_shift); 2803 | } 2804 | 2805 | if (ret == TB_OK) { 2806 | event->type = TB_EVENT_MOUSE; 2807 | } 2808 | 2809 | return ret; 2810 | } 2811 | 2812 | static int resize_cellbufs(void) { 2813 | int rv; 2814 | if_err_return(rv, 2815 | cellbuf_resize(&global.back, global.width, global.height)); 2816 | if_err_return(rv, 2817 | cellbuf_resize(&global.front, global.width, global.height)); 2818 | if_err_return(rv, cellbuf_clear(&global.front)); 2819 | if_err_return(rv, send_clear()); 2820 | return TB_OK; 2821 | } 2822 | 2823 | static void handle_resize(int sig) { 2824 | int errno_copy = errno; 2825 | write(global.resize_pipefd[1], &sig, sizeof(sig)); 2826 | errno = errno_copy; 2827 | } 2828 | 2829 | static int send_attr(uintattr_t fg, uintattr_t bg) { 2830 | int rv; 2831 | 2832 | if (fg == global.last_fg && bg == global.last_bg) { 2833 | return TB_OK; 2834 | } 2835 | 2836 | if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0])); 2837 | 2838 | uintattr_t cfg, cbg; 2839 | switch (global.output_mode) { 2840 | default: 2841 | case TB_OUTPUT_NORMAL: 2842 | cfg = fg & 0x0f; 2843 | cbg = bg & 0x0f; 2844 | break; 2845 | 2846 | case TB_OUTPUT_256: 2847 | cfg = fg & 0xff; 2848 | cbg = bg & 0xff; 2849 | break; 2850 | 2851 | case TB_OUTPUT_216: 2852 | cfg = fg & 0xff; 2853 | cbg = bg & 0xff; 2854 | if (cfg > 216) 2855 | cfg = 216; 2856 | if (cbg > 216) 2857 | cbg = 216; 2858 | cfg += 0x0f; 2859 | cbg += 0x0f; 2860 | break; 2861 | 2862 | case TB_OUTPUT_GRAYSCALE: 2863 | cfg = fg & 0xff; 2864 | cbg = bg & 0xff; 2865 | if (cfg > 24) 2866 | cfg = 24; 2867 | if (cbg > 24) 2868 | cbg = 24; 2869 | cfg += 0xe7; 2870 | cbg += 0xe7; 2871 | break; 2872 | 2873 | #ifdef TB_OPT_TRUECOLOR 2874 | case TB_OUTPUT_TRUECOLOR: 2875 | cfg = fg & 0xffffff; 2876 | cbg = bg & 0xffffff; 2877 | break; 2878 | #endif 2879 | } 2880 | 2881 | uintattr_t attr_bold, attr_blink, attr_italic, attr_underline, attr_reverse, 2882 | attr_default; 2883 | #ifdef TB_OPT_TRUECOLOR 2884 | if (global.output_mode == TB_OUTPUT_TRUECOLOR) { 2885 | attr_bold = TB_TRUECOLOR_BOLD; 2886 | attr_blink = TB_TRUECOLOR_BLINK; 2887 | attr_italic = TB_TRUECOLOR_ITALIC; 2888 | attr_underline = TB_TRUECOLOR_UNDERLINE; 2889 | attr_reverse = TB_TRUECOLOR_REVERSE; 2890 | attr_default = TB_TRUECOLOR_DEFAULT; 2891 | } else 2892 | #endif 2893 | { 2894 | attr_bold = TB_BOLD; 2895 | attr_blink = TB_BLINK; 2896 | attr_italic = TB_ITALIC; 2897 | attr_underline = TB_UNDERLINE; 2898 | attr_reverse = TB_REVERSE; 2899 | attr_default = TB_DEFAULT; 2900 | } 2901 | 2902 | /* For convenience (and some back compat), interpret 0 as default in some 2903 | * modes */ 2904 | if (global.output_mode == TB_OUTPUT_NORMAL || 2905 | global.output_mode == TB_OUTPUT_216 || 2906 | global.output_mode == TB_OUTPUT_GRAYSCALE) 2907 | { 2908 | if ((fg & 0xff) == 0) 2909 | fg |= attr_default; 2910 | if ((bg & 0xff) == 0) 2911 | bg |= attr_default; 2912 | } 2913 | 2914 | if (fg & attr_bold) 2915 | if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD])); 2916 | 2917 | if (fg & attr_blink) 2918 | if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK])); 2919 | 2920 | if (fg & attr_underline) 2921 | if_err_return(rv, 2922 | bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE])); 2923 | 2924 | if (fg & attr_italic) 2925 | if_err_return(rv, 2926 | bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC])); 2927 | 2928 | if ((fg & attr_reverse) || (bg & attr_reverse)) 2929 | if_err_return(rv, 2930 | bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE])); 2931 | 2932 | if_err_return(rv, send_sgr(cfg, cbg, fg & attr_default, bg & attr_default)); 2933 | 2934 | global.last_fg = fg; 2935 | global.last_bg = bg; 2936 | 2937 | return TB_OK; 2938 | } 2939 | 2940 | static int send_sgr(uintattr_t cfg, uintattr_t cbg, uintattr_t fg_is_default, 2941 | uintattr_t bg_is_default) { 2942 | int rv; 2943 | char nbuf[32]; 2944 | 2945 | if (fg_is_default && bg_is_default) { 2946 | return TB_OK; 2947 | } 2948 | 2949 | switch (global.output_mode) { 2950 | default: 2951 | case TB_OUTPUT_NORMAL: 2952 | send_literal(rv, "\x1b["); 2953 | if (!fg_is_default) { 2954 | send_literal(rv, "3"); 2955 | send_num(rv, nbuf, cfg - 1); 2956 | if (!bg_is_default) { 2957 | send_literal(rv, ";"); 2958 | } 2959 | } 2960 | if (!bg_is_default) { 2961 | send_literal(rv, "4"); 2962 | send_num(rv, nbuf, cbg - 1); 2963 | } 2964 | send_literal(rv, "m"); 2965 | break; 2966 | 2967 | case TB_OUTPUT_256: 2968 | case TB_OUTPUT_216: 2969 | case TB_OUTPUT_GRAYSCALE: 2970 | send_literal(rv, "\x1b["); 2971 | if (!fg_is_default) { 2972 | send_literal(rv, "38;5;"); 2973 | send_num(rv, nbuf, cfg); 2974 | if (!bg_is_default) { 2975 | send_literal(rv, ";"); 2976 | } 2977 | } 2978 | if (!bg_is_default) { 2979 | send_literal(rv, "48;5;"); 2980 | send_num(rv, nbuf, cbg); 2981 | } 2982 | send_literal(rv, "m"); 2983 | break; 2984 | 2985 | #ifdef TB_OPT_TRUECOLOR 2986 | case TB_OUTPUT_TRUECOLOR: 2987 | send_literal(rv, "\x1b["); 2988 | if (!fg_is_default) { 2989 | send_literal(rv, "38;2;"); 2990 | send_num(rv, nbuf, (cfg >> 16) & 0xff); 2991 | send_literal(rv, ";"); 2992 | send_num(rv, nbuf, (cfg >> 8) & 0xff); 2993 | send_literal(rv, ";"); 2994 | send_num(rv, nbuf, cfg & 0xff); 2995 | if (!bg_is_default) { 2996 | send_literal(rv, ";"); 2997 | } 2998 | } 2999 | if (!bg_is_default) { 3000 | send_literal(rv, "48;2;"); 3001 | send_num(rv, nbuf, (cbg >> 16) & 0xff); 3002 | send_literal(rv, ";"); 3003 | send_num(rv, nbuf, (cbg >> 8) & 0xff); 3004 | send_literal(rv, ";"); 3005 | send_num(rv, nbuf, cbg & 0xff); 3006 | } 3007 | send_literal(rv, "m"); 3008 | break; 3009 | #endif 3010 | } 3011 | return TB_OK; 3012 | } 3013 | 3014 | static int send_cursor_if(int x, int y) { 3015 | int rv; 3016 | char nbuf[32]; 3017 | if (x < 0 || y < 0) { 3018 | return TB_OK; 3019 | } 3020 | send_literal(rv, "\x1b["); 3021 | send_num(rv, nbuf, y + 1); 3022 | send_literal(rv, ";"); 3023 | send_num(rv, nbuf, x + 1); 3024 | send_literal(rv, "H"); 3025 | return TB_OK; 3026 | } 3027 | 3028 | static int send_char(int x, int y, uint32_t ch) { 3029 | return send_cluster(x, y, &ch, 1); 3030 | } 3031 | 3032 | static int send_cluster(int x, int y, uint32_t *ch, size_t nch) { 3033 | int rv; 3034 | char abuf[8]; 3035 | 3036 | if (global.last_x != x - 1 || global.last_y != y) { 3037 | if_err_return(rv, send_cursor_if(x, y)); 3038 | } 3039 | global.last_x = x; 3040 | global.last_y = y; 3041 | 3042 | int i; 3043 | for (i = 0; i < (int)nch; i++) { 3044 | uint32_t ach = *(ch + i); 3045 | int aw = tb_utf8_unicode_to_char(abuf, ach); 3046 | if (!ach) { 3047 | abuf[0] = ' '; 3048 | } 3049 | if_err_return(rv, bytebuf_nputs(&global.out, abuf, (size_t)aw)); 3050 | } 3051 | 3052 | return TB_OK; 3053 | } 3054 | 3055 | static int convert_num(uint32_t num, char *buf) { 3056 | int i, l = 0; 3057 | char ch; 3058 | do { 3059 | /* '0' = 48; 48 + num%10 < 58 < MAX_8bitCHAR */ 3060 | buf[l++] = (char)('0' + (num % 10)); 3061 | num /= 10; 3062 | } while (num); 3063 | for (i = 0; i < l / 2; i++) { 3064 | ch = buf[i]; 3065 | buf[i] = buf[l - 1 - i]; 3066 | buf[l - 1 - i] = ch; 3067 | } 3068 | return l; 3069 | } 3070 | 3071 | static int cell_cmp(struct tb_cell *a, struct tb_cell *b) { 3072 | if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) { 3073 | return 1; 3074 | } 3075 | #ifdef TB_OPT_EGC 3076 | if (a->nech != b->nech) { 3077 | return 1; 3078 | } else if (a->nech > 0) { // a->nech == b->nech 3079 | return memcmp(a->ech, b->ech, a->nech); 3080 | } 3081 | #endif 3082 | return 0; 3083 | } 3084 | 3085 | static int cell_copy(struct tb_cell *dst, struct tb_cell *src) { 3086 | #ifdef TB_OPT_EGC 3087 | if (src->nech > 0) { 3088 | return cell_set(dst, src->ech, src->nech, src->fg, src->bg); 3089 | } 3090 | #endif 3091 | return cell_set(dst, &src->ch, 1, src->fg, src->bg); 3092 | } 3093 | 3094 | static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, 3095 | uintattr_t fg, uintattr_t bg) { 3096 | cell->ch = ch ? *ch : 0; 3097 | cell->fg = fg; 3098 | cell->bg = bg; 3099 | #ifdef TB_OPT_EGC 3100 | if (nch <= 1) { 3101 | cell->nech = 0; 3102 | } else { 3103 | int rv; 3104 | if_err_return(rv, cell_reserve_ech(cell, nch + 1)); 3105 | memcpy(cell->ech, ch, nch); 3106 | cell->ech[nch] = '\0'; 3107 | cell->nech = nch; 3108 | } 3109 | #else 3110 | (void)nch; 3111 | (void)cell_reserve_ech; 3112 | #endif 3113 | return TB_OK; 3114 | } 3115 | 3116 | static int cell_reserve_ech(struct tb_cell *cell, size_t n) { 3117 | #ifdef TB_OPT_EGC 3118 | if (cell->cech >= n) { 3119 | return TB_OK; 3120 | } 3121 | if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) { 3122 | return TB_ERR_MEM; 3123 | } 3124 | cell->cech = n; 3125 | return TB_OK; 3126 | #else 3127 | (void)cell; 3128 | (void)n; 3129 | return TB_ERR; 3130 | #endif 3131 | } 3132 | 3133 | static int cell_free(struct tb_cell *cell) { 3134 | #ifdef TB_OPT_EGC 3135 | if (cell->ech) { 3136 | tb_free(cell->ech); 3137 | } 3138 | #endif 3139 | memset(cell, 0, sizeof(*cell)); 3140 | return TB_OK; 3141 | } 3142 | 3143 | static int cellbuf_init(struct cellbuf_t *c, int w, int h) { 3144 | c->cells = tb_malloc(sizeof(struct tb_cell) * w * h); 3145 | if (!c->cells) { 3146 | return TB_ERR_MEM; 3147 | } 3148 | memset(c->cells, 0, sizeof(struct tb_cell) * w * h); 3149 | c->width = w; 3150 | c->height = h; 3151 | return TB_OK; 3152 | } 3153 | 3154 | static int cellbuf_free(struct cellbuf_t *c) { 3155 | if (c->cells) { 3156 | int i; 3157 | for (i = 0; i < c->width * c->height; i++) { 3158 | cell_free(&c->cells[i]); 3159 | } 3160 | tb_free(c->cells); 3161 | } 3162 | memset(c, 0, sizeof(*c)); 3163 | return TB_OK; 3164 | } 3165 | 3166 | static int cellbuf_clear(struct cellbuf_t *c) { 3167 | int rv, i; 3168 | uint32_t space = (uint32_t)' '; 3169 | for (i = 0; i < c->width * c->height; i++) { 3170 | if_err_return(rv, 3171 | cell_set(&c->cells[i], &space, 1, global.fg, global.bg)); 3172 | } 3173 | return TB_OK; 3174 | } 3175 | 3176 | static int cellbuf_get(struct cellbuf_t *c, int x, int y, 3177 | struct tb_cell **out) { 3178 | if (x < 0 || x >= c->width || y < 0 || y >= c->height) { 3179 | *out = NULL; 3180 | return TB_ERR_OUT_OF_BOUNDS; 3181 | } 3182 | *out = &c->cells[(y * c->width) + x]; 3183 | return TB_OK; 3184 | } 3185 | 3186 | static int cellbuf_resize(struct cellbuf_t *c, int w, int h) { 3187 | int rv; 3188 | 3189 | int ow = c->width; 3190 | int oh = c->height; 3191 | 3192 | if (ow == w && oh == h) { 3193 | return TB_OK; 3194 | } 3195 | 3196 | w = w < 1 ? 1 : w; 3197 | h = h < 1 ? 1 : h; 3198 | 3199 | int minw = (w < ow) ? w : ow; 3200 | int minh = (h < oh) ? h : oh; 3201 | 3202 | struct tb_cell *prev = c->cells; 3203 | 3204 | if_err_return(rv, cellbuf_init(c, w, h)); 3205 | if_err_return(rv, cellbuf_clear(c)); 3206 | 3207 | int x, y; 3208 | for (x = 0; x < minw; x++) { 3209 | for (y = 0; y < minh; y++) { 3210 | struct tb_cell *src, *dst; 3211 | src = &prev[(y * ow) + x]; 3212 | if_err_return(rv, cellbuf_get(c, x, y, &dst)); 3213 | if_err_return(rv, cell_copy(dst, src)); 3214 | } 3215 | } 3216 | 3217 | tb_free(prev); 3218 | 3219 | return TB_OK; 3220 | } 3221 | 3222 | static int bytebuf_puts(struct bytebuf_t *b, const char *str) { 3223 | return bytebuf_nputs(b, str, (size_t)strlen(str)); 3224 | } 3225 | 3226 | static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) { 3227 | int rv; 3228 | if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1)); 3229 | memcpy(b->buf + b->len, str, nstr); 3230 | b->len += nstr; 3231 | b->buf[b->len] = '\0'; 3232 | return TB_OK; 3233 | } 3234 | 3235 | static int bytebuf_shift(struct bytebuf_t *b, size_t n) { 3236 | if (n > b->len) { 3237 | n = b->len; 3238 | } 3239 | size_t nmove = b->len - n; 3240 | memmove(b->buf, b->buf + n, nmove); 3241 | b->len -= n; 3242 | return TB_OK; 3243 | } 3244 | 3245 | static int bytebuf_flush(struct bytebuf_t *b, int fd) { 3246 | if (b->len <= 0) { 3247 | return TB_OK; 3248 | } 3249 | ssize_t write_rv = write(fd, b->buf, b->len); 3250 | if (write_rv < 0 || (size_t)write_rv != b->len) { 3251 | // Note, errno will be 0 on partial write 3252 | global.last_errno = errno; 3253 | return TB_ERR; 3254 | } 3255 | b->len = 0; 3256 | return TB_OK; 3257 | } 3258 | 3259 | static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) { 3260 | if (b->cap >= sz) { 3261 | return TB_OK; 3262 | } 3263 | size_t newcap = b->cap > 0 ? b->cap : 1; 3264 | while (newcap < sz) { 3265 | newcap *= 2; 3266 | } 3267 | char *newbuf; 3268 | if (b->buf) { 3269 | newbuf = tb_realloc(b->buf, newcap); 3270 | } else { 3271 | newbuf = tb_malloc(newcap); 3272 | } 3273 | if (!newbuf) { 3274 | return TB_ERR_MEM; 3275 | } 3276 | b->buf = newbuf; 3277 | b->cap = newcap; 3278 | return TB_OK; 3279 | } 3280 | 3281 | static int bytebuf_free(struct bytebuf_t *b) { 3282 | if (b->buf) { 3283 | tb_free(b->buf); 3284 | } 3285 | memset(b, 0, sizeof(*b)); 3286 | return TB_OK; 3287 | } 3288 | 3289 | #endif /* TB_IMPL */ 3290 | --------------------------------------------------------------------------------