nyancat.c (21629B)
1 /* 2 * Copyright (c) 2020 Santtu Lakkala 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted. 6 * 7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 13 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 */ 15 16 #define _POSIX_C_SOURCE 199309L 17 18 #include <stdarg.h> 19 #include <stdlib.h> 20 #include <stdio.h> 21 #include <string.h> 22 #include <stdbool.h> 23 #include <stdint.h> 24 25 #include <sys/types.h> 26 #include <signal.h> 27 #include <time.h> 28 29 #include <math.h> 30 31 #include <fcntl.h> 32 #include <limits.h> 33 #include <unistd.h> 34 35 #include <termios.h> 36 #include <sys/ioctl.h> 37 38 #include <unicode/uchar.h> 39 #include <unicode/utf8.h> 40 41 #define PI 3.1415926535898 42 #define N(a) ((sizeof(a) / sizeof(*(a)))) 43 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 44 #define MAX(a, b) ((a) < (b) ? (b) : (a)) 45 #define CLAMP(a, b, c) MIN(MAX(a, b), c) 46 #define GRADSTEPS 16 47 48 struct lolcat { 49 int x; 50 int y; 51 int w; 52 int h; 53 int sx; 54 int sy; 55 bool transpose; 56 bool bash_escape; 57 uint32_t fg; 58 uint32_t bg; 59 bool bg_set; 60 bool bold; 61 bool reverse; 62 bool underline; 63 bool was_bold; 64 bool was_reverse; 65 bool was_underline; 66 bool was_bgset; 67 bool xterm_workaround; 68 enum { TRUECOLOR, INDEXED256, INDEXED16, 69 NYANANIMATE, GRADANIMATE, STOP } mode; 70 enum { NONE, 71 FCOLOR, FI256, FR, FG, FB, 72 BCOLOR, BI256, BR, BG, BB } cstate; 73 74 bool (*write)(const char *buffer, size_t buflen, void *data); 75 void *write_data; 76 }; 77 78 int rainbow(float freq, int i) 79 { 80 return ((int)(sin(freq * i + 0) * 127 + 128)) << 16 | 81 ((int)(sin(freq * i + 2 * PI / 3) * 127 + 128)) << 8 | 82 ((int)(sin(freq * i + 4 * PI / 3) * 127 + 128)); 83 } 84 85 const uint32_t animation_colors[] = { 86 0xff0000, 87 0xffaf00, 88 0xffff00, 89 0x87ff00, 90 0x0087ff, 91 0x0000af 92 }; 93 94 size_t alter_palette(char *buffer, int index, uint32_t color) 95 { 96 return sprintf(buffer, "\x1b]4;%d;rgb:%02x/%02x/%02x\x1b\\", 97 index, 98 (unsigned)(color >> 16), 99 (unsigned)((color >> 8) & 0xff), 100 (unsigned)(color & 0xff)); 101 } 102 103 bool animate_gradpalette(struct lolcat *lc, int offset) 104 { 105 size_t i; 106 107 for (i = 0; i < GRADSTEPS; i++) { 108 char buffer[128]; 109 if (!lc->write(buffer, 110 alter_palette(buffer, 111 16 + (i + offset) % GRADSTEPS, 112 rainbow(PI * 2 / GRADSTEPS, i)), 113 lc->write_data)) 114 return false; 115 } 116 117 if (lc->xterm_workaround) 118 lc->write("\x1b[s\x1b[u", 6, lc->write_data); 119 120 return true; 121 } 122 123 bool animate_palette(struct lolcat *lc, bool up) 124 { 125 size_t i; 126 127 for (i = 0; i < N(animation_colors); i++) { 128 char buffer[128]; 129 if (!lc->write(buffer, 130 alter_palette(buffer, 131 16 + 132 (3 * i + N(animation_colors) * 133 3 - 1 - !!up) % 134 (N(animation_colors) * 3), 135 animation_colors[i]), 136 lc->write_data) || 137 !lc->write(buffer, 138 alter_palette(buffer, 139 16 + 140 (3 * i + 1 + !!up) 141 % (N(animation_colors) * 3), 142 animation_colors[i]), 143 lc->write_data)) 144 return false; 145 } 146 147 return true; 148 } 149 150 void prepare_palette(struct lolcat *lc) 151 { 152 size_t i; 153 154 for (i = 0; i < N(animation_colors); i++) { 155 char buffer[128]; 156 lc->write(buffer, alter_palette(buffer, 3 * i + 16, 157 animation_colors[i]), 158 lc->write_data); 159 } 160 161 animate_palette(lc, false); 162 } 163 164 int nyan(int x, int y, bool transpose) 165 { 166 if (transpose) 167 return sin(y * PI / 18) * 18 + x * 18; 168 return sin(x * PI / 18) * 18 + y * 18; 169 } 170 171 static size_t strnspn_printable(const char *str, size_t len) 172 { 173 size_t rv = 0; 174 while (rv < len && (str[rv] & ~0x9f)) 175 rv++; 176 return rv; 177 } 178 179 static size_t strnspn(const char *str, size_t len, const char *allowed) 180 { 181 uint32_t allowed_mask[(1U << CHAR_BIT) / 32] = { 0 }; 182 size_t i; 183 for (; *allowed; allowed++) 184 allowed_mask[*(unsigned char *)allowed / 32] |= 1 << 185 (*(unsigned char *)allowed & 31); 186 for (i = 0; i < len; i++) 187 if (!(allowed_mask[((unsigned char *)str)[i] / 32] & 1 << 188 (((unsigned char *)str)[i] & 31))) 189 break; 190 return i; 191 } 192 193 static void strtok_foreach(const char *str, int len, char delim, 194 int (*cb)(const char *tok, int toklen, void *data), 195 void *data) 196 { 197 const char *i = str; 198 const char *sep; 199 200 for (sep = memchr(str, delim, len); 201 sep; 202 i = sep + 1, sep = memchr(i, delim, len - (i - str))) 203 if (cb(i, sep - i, data)) 204 return; 205 cb(i, len - (i - str), data); 206 } 207 208 struct strtok_int_data { 209 int (*cb)(int val, void *data); 210 void *data; 211 }; 212 213 static int _strtok_fi_cb(const char *tok, int toklen, void *data) 214 { 215 struct strtok_int_data *fidata = data; 216 int val = 0; 217 218 while (toklen-- && *tok >= '0' && *tok <= '9') 219 val = val * 10 + *tok++ - '0'; 220 return fidata->cb(val, fidata->data); 221 } 222 223 static void strtok_foreach_int(const char *str, int len, char delim, 224 int (*cb)(int val, void *data), void *data) 225 { 226 struct strtok_int_data fidata; 227 228 fidata.cb = cb; 229 fidata.data = data; 230 231 strtok_foreach(str, len, delim, _strtok_fi_cb, &fidata); 232 } 233 234 struct lolcat *lolcat_init(bool (*write_cb)(const char *buffer, size_t buflen, 235 void *data), void *cbdata) 236 { 237 struct lolcat *rv = calloc(1, sizeof(struct lolcat)); 238 239 rv->write = write_cb; 240 rv->write_data = cbdata; 241 rv->fg = 0xc0c0c0; 242 rv->bg = 0x000000; 243 rv->mode = TRUECOLOR; 244 245 return rv; 246 } 247 248 static char *strcpy_alter(char *to, const char *from, char f, char t) 249 { 250 char *w = to; 251 for (; *from; from++) { 252 if (*from == f) 253 *w++ = t; 254 else 255 *w++ = *from; 256 } 257 258 return to; 259 } 260 261 static inline int _bold(int color) 262 { 263 if (!color) 264 return 0x808080; 265 if (color == 0xc0c0c0) 266 return 0xffffff; 267 return (color << 1) - (color >> 7); 268 } 269 270 static uint32_t _color8(uint8_t color) 271 { 272 if (color == 7) 273 return 0xc0c0c0; 274 return (color & 1) << 23 | 275 (color & 2) << 14 | 276 (color & 4) << 5; 277 } 278 279 static uint32_t _ccube(uint8_t idx) 280 { 281 if (!idx) 282 return 0; 283 return 255 - 40 * (5 - idx); 284 } 285 286 static uint32_t _color256(uint8_t color) 287 { 288 if (color < 8) 289 return _color8(color); 290 if (color < 16) 291 return _bold(_color8(color)); 292 if (color < 232) 293 return _ccube((color - 16) / 36) << 16 | 294 _ccube((color - 16) / 6 % 6) << 8 | 295 _ccube((color - 16) % 6); 296 return 0x010101 * (0xee - 10 * (255 - color)); 297 } 298 299 static int _lc_arg_m(int arg, void *data) 300 { 301 struct lolcat *lc = data; 302 303 switch (lc->cstate) { 304 case FCOLOR: 305 if (arg == 2) 306 lc->cstate = FR; 307 else if (arg == 5) 308 lc->cstate = FI256; 309 else 310 return 1; 311 lc->fg = 0; 312 return 0; 313 case FI256: 314 lc->fg = _color256(arg); 315 lc->cstate = NONE; 316 return 0; 317 case FB: 318 lc->cstate = -1; 319 /* fall-through */ 320 case FR: 321 case FG: 322 lc->fg = lc->fg << 8 | arg; 323 lc->cstate++; 324 return 0; 325 326 case BCOLOR: 327 if (arg == 2) 328 lc->cstate = BR; 329 else if (arg == 5) 330 lc->cstate = BI256; 331 else 332 return 1; 333 lc->bg = 0; 334 return 0; 335 case BI256: 336 lc->bg = _color256(arg); 337 lc->bg_set = true; 338 lc->cstate = NONE; 339 return 0; 340 case BB: 341 lc->cstate = -1; 342 lc->bg_set = true; 343 /* fall-through */ 344 case BR: 345 case BG: 346 lc->bg = lc->bg << 8 | arg; 347 lc->cstate++; 348 return 0; 349 default: 350 break; 351 } 352 353 switch (arg) { 354 case 0: 355 lc->bold = false; 356 lc->reverse = false; 357 lc->fg = 0xc0c0c0; 358 lc->bg = 0x000000; 359 lc->bg_set = false; 360 break; 361 case 1: 362 lc->bold = true; 363 break; 364 case 4: 365 lc->underline = true; 366 break; 367 case 7: 368 lc->reverse = true; 369 break; 370 case 24: 371 lc->underline = false; 372 break; 373 case 30: 374 case 31: 375 case 32: 376 case 33: 377 case 34: 378 case 35: 379 case 36: 380 lc->fg = ((arg - 30) & 1) << 23 | 381 ((arg - 30) & 2) << 14 | 382 ((arg - 30) & 4) << 5; 383 break; 384 case 37: 385 lc->fg = 0xc0c0c0; 386 break; 387 case 38: 388 lc->cstate = FCOLOR; 389 break; 390 case 40: 391 case 41: 392 case 42: 393 case 43: 394 case 44: 395 case 45: 396 case 46: 397 lc->bg = ((arg - 40) & 1) << 23 | 398 ((arg - 40) & 2) << 14 | 399 ((arg - 40) & 4) << 5; 400 lc->bg_set = true; 401 break; 402 case 47: 403 lc->bg = 0xc0c0c0; 404 lc->bg_set = true; 405 break; 406 case 48: 407 lc->cstate = BCOLOR; 408 break; 409 410 default: 411 break; 412 } 413 414 return 0; 415 } 416 417 static int _add(int a, int b, double amount) 418 { 419 int r = (a >> 16) + ((b >> 16) - 0xc0) * amount; 420 int g = ((a >> 8) & 0xff) + (((b >> 8) & 0xff) - 0xc0) * amount; 421 int bl = (a & 0xff) + ((b & 0xff) - 0xc0) * amount; 422 return CLAMP(r, 0, 255) << 16 | 423 CLAMP(g, 0, 255) << 8 | 424 CLAMP(bl, 0, 255); 425 } 426 427 static inline uint8_t absd(uint8_t a, uint8_t b) 428 { 429 if (a > b) 430 return a - b; 431 return b - a; 432 } 433 434 static inline int _index16(uint32_t col, bool *bright) 435 { 436 uint8_t r = col >> 16; 437 uint8_t g = (col >> 8) & 255; 438 uint8_t b = col & 255; 439 440 uint8_t ri = ((uint16_t)r + 64) * 2 / 256; 441 uint8_t gi = ((uint16_t)g + 64) * 2 / 256; 442 uint8_t bi = ((uint16_t)b + 64) * 2 / 256; 443 444 if (ri == 2 || gi == 2 || bi == 2) { 445 ri = r > 127U; 446 gi = g > 127U; 447 bi = b > 127U; 448 if (bright) 449 *bright = true; 450 } else { 451 if (bright) 452 *bright = false; 453 } 454 455 return ri | bi << 1 | gi << 2; 456 } 457 458 static inline int _index256(uint32_t col) 459 { 460 uint8_t r = col >> 16; 461 uint8_t g = (col >> 8) & 255; 462 uint8_t b = col & 255; 463 464 uint8_t cr = r < 35 ? 0 : (r - 35) / 40; 465 uint8_t cb = b < 35 ? 0 : (b - 35) / 40; 466 uint8_t cg = g < 35 ? 0 : (g - 35) / 40; 467 468 uint8_t gs = (0xee - ((uint16_t)r + g + b) / 3) / 10; 469 470 if (gs > 23 || 471 absd(cr * 40 + !!cr * 55, r) + 472 absd(cg * 40 + !!cr * 55, g) + 473 absd(cb * 40 + !!cr * 55, b) <= 474 absd(0xee - 10 * gs, r) + 475 absd(0xee - 10 * gs, g) + 476 absd(0xee - 10 * gs, b)) 477 return cr * 36 + cg * 6 + cb + 16; 478 return 255 - gs; 479 } 480 481 static void _lc_colorize(struct lolcat *lc, const char *u8_char, size_t len) 482 { 483 char buffer[128] = "\x1b["; 484 int col; 485 char *bw = buffer + 2; 486 487 if (len == 1 && u8_char[0] == ' ' && 488 !lc->reverse && !lc->was_reverse && 489 !lc->bg_set && !lc->was_bgset) { 490 lc->write(" ", 1, lc->write_data); 491 return; 492 } 493 494 if (lc->mode == NYANANIMATE) { 495 if (lc->transpose) 496 col = 16 + (((lc->x / 2 % N(animation_colors)) * 3) + 497 (lc->x & 1) * 498 (1 + (((lc->y + 25) / 30) & 1))); 499 else 500 col = 16 + (((lc->y / 2 % N(animation_colors)) * 3) + 501 (lc->y & 1) * 502 (1 + (((lc->x + 25) / 30) & 1))); 503 } else if (lc->mode == GRADANIMATE) { 504 col = 16 + (lc->x + lc->y) % GRADSTEPS; 505 } else { 506 if (lc->bold) 507 col = _bold(lc->fg); 508 else 509 col = lc->fg; 510 col = _add(rainbow(0.03, 511 nyan(lc->x, lc->y, lc->transpose)), 512 col, 0.5); 513 514 if (lc->mode == INDEXED16) 515 col = _index16(col, &lc->bold); 516 else if (lc->mode == INDEXED256) 517 col = _index256(col); 518 } 519 520 if ((lc->was_bold && !lc->bold) || 521 (lc->was_reverse && !lc->reverse) || 522 (lc->was_bgset && !lc->bg_set)) { 523 lc->was_underline = false; 524 lc->was_bold = false; 525 lc->was_reverse = false; 526 lc->was_bgset = false; 527 *bw++ = '0'; 528 *bw++ = ';'; 529 } 530 if (lc->was_underline != lc->underline) { 531 if (lc->was_underline) 532 *bw++ = '2'; 533 *bw++ = '4'; 534 *bw++ = ';'; 535 lc->was_underline = lc->underline; 536 } 537 if (lc->bold && !lc->was_bold) { 538 *bw++ = '1'; 539 *bw++ = ';'; 540 lc->was_bold = true; 541 } 542 if (lc->reverse && !lc->was_reverse) { 543 *bw++ = '7'; 544 *bw++ = ';'; 545 lc->was_reverse = true; 546 } 547 548 if (lc->bg_set) { 549 if (lc->mode == INDEXED16) 550 bw += sprintf(bw, "4%d;", 551 _index16(lc->bg, NULL)); 552 else if (lc->mode == INDEXED256) 553 bw += sprintf(bw, "48;5;%d;", 554 _index256(lc->bg)); 555 else 556 bw += sprintf(bw, "48;2;%d;%d;%d;", 557 lc->bg >> 16, 558 (lc->bg >> 8) & 255, 559 lc->bg & 255); 560 lc->was_bgset = true; 561 } 562 563 if (lc->mode == INDEXED16) 564 bw += sprintf(bw, "3%dm", 565 col); 566 else if (lc->mode == INDEXED256 || 567 lc->mode == GRADANIMATE || 568 lc->mode == NYANANIMATE) 569 bw += sprintf(bw, "38;5;%dm", 570 col); 571 else 572 bw += sprintf(bw, "38;2;%d;%d;%dm", 573 col >> 16, 574 (col >> 8) & 255, 575 col & 255); 576 577 if (lc->bash_escape) 578 lc->write("\\[", 2, lc->write_data); 579 lc->write(buffer, bw - buffer, lc->write_data); 580 if (lc->bash_escape) 581 lc->write("\\]", 2, lc->write_data); 582 lc->write(u8_char, len, lc->write_data); 583 } 584 585 struct strnsplit_int_data { 586 va_list args; 587 int n; 588 }; 589 590 static int _strnsplit_int_cb(int val, void *data) 591 { 592 struct strnsplit_int_data *ssidata = data; 593 int *a = va_arg(ssidata->args, int *); 594 *a = val; 595 return !--ssidata->n; 596 } 597 598 static int strnsplit_int(const char *str, size_t len, char delim, int n, ...) 599 { 600 struct strnsplit_int_data data; 601 va_start(data.args, n); 602 data.n = n; 603 strtok_foreach_int(str, len, delim, _strnsplit_int_cb, &data); 604 va_end(data.args); 605 606 return n - data.n; 607 } 608 609 ssize_t lc_process(struct lolcat *lc, const char *buffer, size_t len) 610 { 611 size_t i = 0; 612 size_t ip = 0; 613 while (i < len) { 614 UChar32 c; 615 int eaw; 616 617 ip = i; 618 U8_NEXT(buffer, i, len, c); 619 620 if (c < 0) 621 return ip; 622 623 if (c == '\x1b') { 624 lc->cstate = NONE; 625 if (i >= len) 626 return ip; 627 if (buffer[i] == '[') { 628 size_t n_args; 629 char cmd; 630 if (i + 1 >= len) 631 return ip; 632 if (buffer[i + 1] == '?') { 633 n_args = strnspn(buffer + i + 2, 634 len - i - 2, 635 "0123456789;"); 636 if (i + 2 + n_args >= len) 637 return ip; 638 lc->write(buffer + ip, n_args + 4, 639 lc->write_data); 640 i += n_args + 3; 641 continue; 642 } 643 n_args = strnspn(buffer + i + 1, len - i - 1, 644 "0123456789;"); 645 if (i + 1 + n_args >= len) 646 return ip; 647 cmd = buffer[i + 1 + n_args]; 648 649 if (!cmd) 650 return ip; 651 652 if (cmd == 'H') { 653 int x, y; 654 strnsplit_int(buffer + i + 1, 655 len - i - 1, ';', 656 2, &y, &x); 657 lc->x = x - 1; 658 lc->y = y - 1; 659 } 660 if (cmd >= 'A' && cmd <= 'D') { 661 int n = 1; 662 int dirs[][2] = { 663 { 0, -1 }, 664 { 0, 1 }, 665 { 1, 0 }, 666 { -1, 0 } 667 }; 668 strnsplit_int(buffer + i + 1, 669 len - i - 1, ';', 1, 670 &n); 671 lc->x += dirs[cmd - 'A'][0] * n; 672 lc->y += dirs[cmd - 'A'][1] * n; 673 } 674 if (cmd == 'G') { 675 int x; 676 strnsplit_int(buffer + i + 1, 677 len - i - 1, ';', 1, 678 &x); 679 lc->x = x - 1; 680 } 681 if (cmd == 'd') { 682 int y; 683 strnsplit_int(buffer + i + 1, 684 len - i - 1, ';', 1, 685 &y); 686 lc->y = y - 1; 687 } 688 if (cmd == 'J') { 689 if (atoi(buffer + i + 1) == 2) { 690 lc->x = 0; 691 lc->y = 0; 692 } 693 } 694 if (cmd == 'm') { 695 if (n_args == 0) 696 _lc_arg_m(0, lc); 697 else 698 strtok_foreach_int( 699 buffer + i + 1, 700 n_args, ';', 701 _lc_arg_m, lc); 702 i += n_args + 2; 703 continue; 704 } 705 if (cmd == 's') { 706 lc->sx = lc->x; 707 lc->sy = lc->y; 708 } 709 if (cmd == 'u') { 710 lc->x = lc->sx; 711 lc->y = lc->sy; 712 } 713 lc->write(buffer + ip, n_args + 3, 714 lc->write_data); 715 i += n_args + 2; 716 continue; 717 } 718 if (buffer[i] == '(') { 719 size_t n_args; 720 n_args = strnspn(buffer + i + 1, len - i - 1, 721 "0123456789;"); 722 if (i + 1 + n_args >= len) 723 return ip; 724 725 lc->write(buffer + ip, n_args + 3, 726 lc->write_data); 727 i += n_args + 2; 728 continue; 729 } 730 if (buffer[i] == ']') { 731 ssize_t dlen = strnspn_printable( 732 buffer + i + 1, 733 len - i - 1); 734 735 if (dlen + i + 1 == len) 736 return ip; 737 if (buffer[i + dlen + 1] == '\007' || 738 buffer[i + dlen + 1] == '\x9c') { 739 lc->write(buffer + ip, dlen + 3, 740 lc->write_data); 741 i += dlen + 2; 742 continue; 743 } 744 if (buffer[i + dlen + 1] == '\x1b') { 745 if (dlen + i + 2 == len) 746 return ip; 747 if (buffer[i + dlen + 2] == '\\') 748 lc->write(buffer + ip, 749 dlen + 4, 750 lc->write_data); 751 i += dlen + 3; 752 continue; 753 } 754 } 755 if (buffer[i] == '>') { 756 lc->write(buffer + ip, 2, lc->write_data); 757 i += 1; 758 continue; 759 } 760 } else if (c == '\t') { 761 lc->x = (lc->x + 8) & ~(int)0x7; 762 lc->write("\t", 1, lc->write_data); 763 continue; 764 } else if (c == '\r') { 765 lc->x = 0; 766 lc->y++; 767 lc->write("\r", 1, lc->write_data); 768 continue; 769 } else if (c == '') { 770 if (lc->x) 771 lc->x--; 772 lc->write("", 1, lc->write_data); 773 continue; 774 } else if (c == '\n') { 775 if ((lc->was_bgset && !lc->bg) || 776 (lc->was_reverse && !lc->reverse)) { 777 const char buffer[] = "\x1b[0m"; 778 lc->was_bgset = lc->was_reverse = 779 lc->was_bold = lc->was_underline = 780 false; 781 if (lc->bash_escape) 782 lc->write("\\[", 2, lc->write_data); 783 lc->write(buffer, sizeof(buffer), 784 lc->write_data); 785 if (lc->bash_escape) 786 lc->write("\\[", 2, lc->write_data); 787 } 788 lc->x = 0; 789 lc->y++; 790 lc->write("\n", 1, lc->write_data); 791 continue; 792 } 793 794 eaw = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH); 795 796 if (lc->w && lc->x + (eaw == U_EA_WIDE || 797 eaw == U_EA_FULLWIDTH) >= lc->w) { 798 lc->x = 0; 799 lc->y++; 800 } 801 802 _lc_colorize(lc, buffer + ip, i - ip); 803 804 if (eaw == U_EA_WIDE || 805 eaw == U_EA_FULLWIDTH) 806 lc->x++; 807 lc->x++; 808 } 809 810 return i; 811 } 812 813 bool _write(const char *data, size_t len, void *user_data) 814 { 815 return fwrite(data, 1, len, user_data) == len; 816 } 817 818 void lolcat_set_size(struct lolcat *lc, int w, int h) 819 { 820 lc->w = w; 821 lc->h = h; 822 } 823 824 static void usage(const char *binname) 825 { 826 fprintf(stderr, 827 "Usage: %s [-t12vb] [file...]\n" 828 " -t 24-bit true color output (default)\n" 829 " -1 16 color output\n" 830 " -2 256 color output\n" 831 " -b Escape color codes with \\[ \\] for bash prompt\n" 832 " -n Animate the rainbowaves\n" 833 " -g Animate rainbow gradient\n" 834 " -s Stop animation\n" 835 " -v Make rainbowaves vertical\n", 836 binname); 837 exit(0); 838 } 839 840 static bool hupped = false; 841 static void sig_hup(int signum) 842 { 843 (void)signum; 844 hupped = true; 845 } 846 847 int main(int argc, char **argv) 848 { 849 struct lolcat *lc = lolcat_init(_write, stdout); 850 char buffer[2048]; 851 size_t used = 0; 852 struct termios old_tio = { 0 }, new_tio; 853 int i = 1; 854 855 struct winsize ws = { 0 }; 856 857 if (!ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) || 858 !ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) 859 lolcat_set_size(lc, ws.ws_col, ws.ws_row); 860 861 while (i < argc) { 862 const char *opt; 863 if (argv[i][0] != '-' || argv[i][1] == '\0') 864 break; 865 if (argv[i][1] == '-' && argv[i][2] == '\0') { 866 i++; 867 break; 868 } 869 opt = &argv[i++][1]; 870 871 while (*opt) { 872 switch (*opt++) { 873 case 't': 874 lc->mode = TRUECOLOR; 875 break; 876 case '1': 877 lc->mode = INDEXED16; 878 break; 879 case '2': 880 lc->mode = INDEXED256; 881 break; 882 case 'n': 883 lc->mode = NYANANIMATE; 884 break; 885 case 'g': 886 lc->mode = GRADANIMATE; 887 break; 888 case 's': 889 lc->mode = STOP; 890 break; 891 case 'v': 892 lc->transpose = true; 893 break; 894 case 'b': 895 lc->bash_escape = true; 896 break; 897 case 'h': 898 default: 899 usage(argv[0]); 900 break; 901 } 902 } 903 } 904 905 906 if ((lc->mode == GRADANIMATE || lc->mode == NYANANIMATE || 907 lc->mode == STOP) && isatty(STDERR_FILENO)) { 908 char buffer[8192]; 909 const char *tty = ttyname(STDERR_FILENO); 910 strcpy_alter(buffer + 911 sprintf(buffer, "/tmp/.nyancat-%u-", getuid()), 912 tty, '/', '_'); 913 FILE *pf = fopen(buffer, "r"); 914 long unsigned pid = 0; 915 const char *term; 916 917 if ((term = getenv("TERM"))) 918 lc->xterm_workaround = !strncmp(term, "xterm", 5); 919 920 if (pf) { 921 fscanf(pf, " %lu", &pid); 922 fclose(pf); 923 } 924 925 if (lc->mode == STOP) { 926 if (pid) 927 kill(pid, SIGHUP); 928 exit(0); 929 } else if (!pid || kill(pid, 0)) { 930 if ((pid = fork())) { 931 pf = fopen(buffer, "w"); 932 if (pf) { 933 fprintf(pf, "%lu\n", pid); 934 fclose(pf); 935 } 936 } else { 937 size_t n = 0; 938 if (lc->mode == NYANANIMATE) 939 prepare_palette(lc); 940 else 941 animate_gradpalette(lc, 0); 942 fclose(stderr); 943 fclose(stdout); 944 fclose(stdin); 945 946 signal(SIGHUP, sig_hup); 947 signal(SIGINT, sig_hup); 948 949 while (!hupped) { 950 nanosleep(&(struct timespec){ 951 .tv_sec = lc->mode == 952 NYANANIMATE ? 1 : 0, 953 .tv_nsec = lc->mode == 954 NYANANIMATE ? 0 : 100000000 }, 955 NULL); 956 lc->write_data = fopen(tty, "w"); 957 if (!lc->write_data) 958 break; 959 if (lc->mode == NYANANIMATE) { 960 if (!animate_palette(lc, 961 n ^= 1)) 962 break; 963 } else { 964 if (!animate_gradpalette( 965 lc, 966 n = (n + 1) % 967 GRADSTEPS)) 968 break; 969 } 970 if (fflush(lc->write_data)) 971 break; 972 fclose(lc->write_data); 973 } 974 975 close(STDOUT_FILENO); 976 unlink(buffer); 977 exit(0); 978 } 979 } 980 } 981 982 if (i >= argc) { 983 tcgetattr(STDIN_FILENO, &old_tio); 984 985 new_tio = old_tio; 986 987 new_tio.c_lflag &= (~ICANON & ~ECHO); 988 tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); 989 990 while (TRUE) { 991 ssize_t bytes = read(STDIN_FILENO, buffer + used, 992 sizeof(buffer) - used); 993 size_t processed; 994 995 if (bytes <= 0) 996 break; 997 998 used += bytes; 999 processed = lc_process(lc, buffer, used); 1000 1001 if (processed != used) 1002 memmove(buffer, buffer + processed, 1003 used - processed); 1004 used -= processed; 1005 1006 fflush(stdout); 1007 } 1008 1009 fflush(stdout); 1010 tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); 1011 } else { 1012 for (; i < argc; i++) { 1013 int fd; 1014 if (!strcmp(argv[i], "-")) 1015 fd = STDIN_FILENO; 1016 else 1017 fd = open(argv[i], O_RDONLY); 1018 while (TRUE) { 1019 ssize_t bytes = read(fd, buffer + used, 1020 sizeof(buffer) - used); 1021 size_t processed; 1022 1023 if (bytes <= 0) 1024 break; 1025 1026 used += bytes; 1027 processed = lc_process(lc, buffer, used); 1028 1029 if (processed != used) 1030 memmove(buffer, buffer + processed, 1031 used - processed); 1032 used -= processed; 1033 1034 fflush(stdout); 1035 } 1036 if (fd != STDIN_FILENO) 1037 close(fd); 1038 } 1039 } 1040 1041 if (lc->bash_escape) 1042 fprintf(stdout, "\\[\x1b[0m\\]"); 1043 else 1044 fprintf(stdout, "\x1b[0m"); 1045 fflush(stdout); 1046 1047 1048 return 0; 1049 }