nyancat.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * Copyright (c) 2011-2018 K. Lange. All rights reserved.
  3. *
  4. * Developed by: K. Lange
  5. * http://gitlab.com/klange/nyancat
  6. * http://nyancat.dakko.us
  7. *
  8. * 40-column support by: Peter Hazenberg
  9. * http://github.com/Peetz0r/nyancat
  10. * http://peter.haas-en-berg.nl
  11. *
  12. * Build tools unified by: Aaron Peschel
  13. * https://github.com/apeschel
  14. *
  15. * For a complete listing of contributers, please see the git commit history.
  16. *
  17. * This is a simple telnet server / standalone application which renders the
  18. * classic Nyan Cat (or "poptart cat") to your terminal.
  19. *
  20. * It makes use of various ANSI escape sequences to render color, or in the case
  21. * of a VT220, simply dumps text to the screen.
  22. *
  23. * For more information, please see:
  24. *
  25. * http://nyancat.dakko.us
  26. *
  27. * Permission is hereby granted, free of charge, to any person obtaining a copy
  28. * of this software and associated documentation files (the "Software"), to
  29. * deal with the Software without restriction, including without limitation the
  30. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  31. * sell copies of the Software, and to permit persons to whom the Software is
  32. * furnished to do so, subject to the following conditions:
  33. * 1. Redistributions of source code must retain the above copyright notice,
  34. * this list of conditions and the following disclaimers.
  35. * 2. Redistributions in binary form must reproduce the above copyright
  36. * notice, this list of conditions and the following disclaimers in the
  37. * documentation and/or other materials provided with the distribution.
  38. * 3. Neither the names of the Association for Computing Machinery, K.
  39. * Lange, nor the names of its contributors may be used to endorse
  40. * or promote products derived from this Software without specific prior
  41. * written permission.
  42. *
  43. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  44. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  45. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  46. * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  47. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  48. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  49. * WITH THE SOFTWARE.
  50. */
  51. #define _XOPEN_SOURCE 500
  52. #include <ctype.h>
  53. #include <stdio.h>
  54. #include <stdint.h>
  55. #include <string.h>
  56. #include <stdlib.h>
  57. #include <unistd.h>
  58. #include <signal.h>
  59. #include <time.h>
  60. #include <setjmp.h>
  61. #include <getopt.h>
  62. #include <sys/ioctl.h>
  63. #ifndef TIOCGWINSZ
  64. #include <termios.h>
  65. #endif
  66. #ifdef ECHO
  67. #undef ECHO
  68. #endif
  69. /*
  70. * telnet.h contains some #defines for the various
  71. * commands, escape characters, and modes for telnet.
  72. * (it surprises some people that telnet is, really,
  73. * a protocol, and not just raw text transmission)
  74. */
  75. #include "nyancat/telnet.h"
  76. /*
  77. * The animation frames are stored separately in
  78. * this header so they don't clutter the core source
  79. */
  80. #include "nyancat/animation.h"
  81. /*
  82. * Color palette to use for final output
  83. * Specifically, this should be either control sequences
  84. * or raw characters (ie, for vt220 mode)
  85. */
  86. const char * colors[256] = {NULL};
  87. /*
  88. * For most modes, we output spaces, but for some
  89. * we will use block characters (or even nothing)
  90. */
  91. const char * output = " ";
  92. /*
  93. * Are we currently in telnet mode?
  94. */
  95. int telnet = 0;
  96. /*
  97. * Whether or not to show the counter
  98. */
  99. int show_counter = 1;
  100. /*
  101. * Number of frames to show before quitting
  102. * or 0 to repeat forever (default)
  103. */
  104. unsigned int frame_count = 0;
  105. /*
  106. * Clear the screen between frames (as opposed to reseting
  107. * the cursor position)
  108. */
  109. int clear_screen = 1;
  110. /*
  111. * Force-set the terminal title.
  112. */
  113. int set_title = 1;
  114. /*
  115. * Environment to use for setjmp/longjmp
  116. * when breaking out of options handler
  117. */
  118. jmp_buf environment;
  119. /*
  120. * I refuse to include libm to keep this low
  121. * on external dependencies.
  122. *
  123. * Count the number of digits in a number for
  124. * use with string output.
  125. */
  126. int digits(int val) {
  127. int d = 1, c;
  128. if (val >= 0) for (c = 10; c <= val; c *= 10) d++;
  129. else for (c = -10 ; c >= val; c *= 10) d++;
  130. return (c < 0) ? ++d : d;
  131. }
  132. /*
  133. * These values crop the animation, as we have a full 64x64 stored,
  134. * but we only want to display 40x24 (double width).
  135. */
  136. int min_row = -1;
  137. int max_row = -1;
  138. int min_col = -1;
  139. int max_col = -1;
  140. /*
  141. * Actual width/height of terminal.
  142. */
  143. int terminal_width = 80;
  144. int terminal_height = 24;
  145. /*
  146. * Flags to keep track of whether width/height were automatically set.
  147. */
  148. char using_automatic_width = 0;
  149. char using_automatic_height = 0;
  150. /*
  151. * Print escape sequences to return cursor to visible mode
  152. * and exit the application.
  153. */
  154. void finish() {
  155. if (clear_screen) {
  156. printf("\033[?25h\033[0m\033[H\033[2J");
  157. } else {
  158. printf("\033[0m\n");
  159. }
  160. exit(0);
  161. }
  162. /*
  163. * In the standalone mode, we want to handle an interrupt signal
  164. * (^C) so that we can restore the cursor and clear the terminal.
  165. */
  166. void SIGINT_handler(int sig){
  167. (void)sig;
  168. finish();
  169. }
  170. /*
  171. * Handle the alarm which breaks us off of options
  172. * handling if we didn't receive a terminal
  173. */
  174. #if 0
  175. void SIGALRM_handler(int sig) {
  176. (void)sig;
  177. alarm(0);
  178. longjmp(environment, 1);
  179. /* Unreachable */
  180. }
  181. #endif
  182. /*
  183. * Handle the loss of stdout, as would be the case when
  184. * in telnet mode and the client disconnects
  185. */
  186. void SIGPIPE_handler(int sig) {
  187. (void)sig;
  188. finish();
  189. }
  190. void SIGWINCH_handler(int sig) {
  191. (void)sig;
  192. struct winsize w;
  193. ioctl(0, TIOCGWINSZ, &w);
  194. terminal_width = w.ws_col;
  195. terminal_height = w.ws_row;
  196. if (using_automatic_width) {
  197. min_col = (FRAME_WIDTH - terminal_width/2) / 2;
  198. max_col = (FRAME_WIDTH + terminal_width/2) / 2;
  199. }
  200. if (using_automatic_height) {
  201. min_row = (FRAME_HEIGHT - (terminal_height-1)) / 2;
  202. max_row = (FRAME_HEIGHT + (terminal_height-1)) / 2;
  203. }
  204. signal(SIGWINCH, SIGWINCH_handler);
  205. }
  206. /*
  207. * Telnet requires us to send a specific sequence
  208. * for a line break (\r\000\n), so let's make it happy.
  209. */
  210. void newline(int n) {
  211. int i = 0;
  212. for (i = 0; i < n; ++i) {
  213. /* We will send `n` linefeeds to the client */
  214. if (telnet) {
  215. /* Send the telnet newline sequence */
  216. fputc('\r', stdout);
  217. fputc(0, stdout);
  218. fputc('\n', stdout);
  219. } else {
  220. /* Send a regular line feed */
  221. fputc('\n', stdout);
  222. }
  223. }
  224. }
  225. /*
  226. * These are the options we want to use as
  227. * a telnet server. These are set in set_options()
  228. */
  229. unsigned char telnet_options[256] = { 0 };
  230. unsigned char telnet_willack[256] = { 0 };
  231. /*
  232. * These are the values we have set or
  233. * agreed to during our handshake.
  234. * These are set in send_command(...)
  235. */
  236. unsigned char telnet_do_set[256] = { 0 };
  237. unsigned char telnet_will_set[256]= { 0 };
  238. /*
  239. * Set the default options for the telnet server.
  240. */
  241. void set_options() {
  242. /* We will not echo input */
  243. telnet_options[ECHO] = WONT;
  244. /* We will set graphics modes */
  245. telnet_options[SGA] = WILL;
  246. /* We will not set new environments */
  247. telnet_options[NEW_ENVIRON] = WONT;
  248. /* The client should echo its own input */
  249. telnet_willack[ECHO] = DO;
  250. /* The client can set a graphics mode */
  251. telnet_willack[SGA] = DO;
  252. /* The client should not change, but it should tell us its window size */
  253. telnet_willack[NAWS] = DO;
  254. /* The client should tell us its terminal type (very important) */
  255. telnet_willack[TTYPE] = DO;
  256. /* No linemode */
  257. telnet_willack[LINEMODE] = DONT;
  258. /* And the client can set a new environment */
  259. telnet_willack[NEW_ENVIRON] = DO;
  260. }
  261. /*
  262. * Send a command (cmd) to the telnet client
  263. * Also does special handling for DO/DONT/WILL/WONT
  264. */
  265. void send_command(int cmd, int opt) {
  266. /* Send a command to the telnet client */
  267. if (cmd == DO || cmd == DONT) {
  268. /* DO commands say what the client should do. */
  269. if (((cmd == DO) && (telnet_do_set[opt] != DO)) ||
  270. ((cmd == DONT) && (telnet_do_set[opt] != DONT))) {
  271. /* And we only send them if there is a disagreement */
  272. telnet_do_set[opt] = cmd;
  273. printf("%c%c%c", IAC, cmd, opt);
  274. }
  275. } else if (cmd == WILL || cmd == WONT) {
  276. /* Similarly, WILL commands say what the server will do. */
  277. if (((cmd == WILL) && (telnet_will_set[opt] != WILL)) ||
  278. ((cmd == WONT) && (telnet_will_set[opt] != WONT))) {
  279. /* And we only send them during disagreements */
  280. telnet_will_set[opt] = cmd;
  281. printf("%c%c%c", IAC, cmd, opt);
  282. }
  283. } else {
  284. /* Other commands are sent raw */
  285. printf("%c%c", IAC, cmd);
  286. }
  287. }
  288. static int _tolower(int c) {
  289. if (c >= 'A' && c <= 'Z') {
  290. return c - 'A' + 'a';
  291. }
  292. return c;
  293. }
  294. /*
  295. * Print the usage / help text describing options
  296. */
  297. void usage(char * argv[]) {
  298. printf(
  299. "Terminal Nyancat\n"
  300. "\n"
  301. "usage: %s [-hitn] [-f \033[3mframes\033[0m]\n"
  302. "\n"
  303. " -i --intro \033[3mShow the introduction / about information at startup.\033[0m\n"
  304. " -t --telnet \033[3mTelnet mode.\033[0m\n"
  305. " -n --no-counter \033[3mDo not display the timer\033[0m\n"
  306. " -s --no-title \033[3mDo not set the titlebar text\033[0m\n"
  307. " -e --no-clear \033[3mDo not clear the display between frames\033[0m\n"
  308. " -f --frames \033[3mDisplay the requested number of frames, then quit\033[0m\n"
  309. " -r --min-rows \033[3mCrop the animation from the top\033[0m\n"
  310. " -R --max-rows \033[3mCrop the animation from the bottom\033[0m\n"
  311. " -c --min-cols \033[3mCrop the animation from the left\033[0m\n"
  312. " -C --max-cols \033[3mCrop the animation from the right\033[0m\n"
  313. " -W --width \033[3mCrop the animation to the given width\033[0m\n"
  314. " -H --height \033[3mCrop the animation to the given height\033[0m\n"
  315. " -h --help \033[3mShow this help message.\033[0m\n",
  316. argv[0]);
  317. }
  318. int main(int argc, char ** argv) {
  319. /* The default terminal is ANSI */
  320. char term[1024] = {'a','n','s','i', 0};
  321. unsigned int k;
  322. int ttype;
  323. //uint32_t option = 0, done = 0, sb_mode = 0;
  324. /* Various pieces for the telnet communication */
  325. //char sb[1024] = {0};
  326. //unsigned short sb_len = 0;
  327. /* Whether or not to show the MOTD intro */
  328. char show_intro = 0;
  329. char skip_intro = 0;
  330. /* Long option names */
  331. static struct option long_opts[] = {
  332. {"help", no_argument, 0, 'h'},
  333. {"telnet", no_argument, 0, 't'},
  334. {"intro", no_argument, 0, 'i'},
  335. {"skip-intro", no_argument, 0, 'I'},
  336. {"no-counter", no_argument, 0, 'n'},
  337. {"no-title", no_argument, 0, 's'},
  338. {"no-clear", no_argument, 0, 'e'},
  339. {"frames", required_argument, 0, 'f'},
  340. {"min-rows", required_argument, 0, 'r'},
  341. {"max-rows", required_argument, 0, 'R'},
  342. {"min-cols", required_argument, 0, 'c'},
  343. {"max-cols", required_argument, 0, 'C'},
  344. {"width", required_argument, 0, 'W'},
  345. {"height", required_argument, 0, 'H'},
  346. {0,0,0,0}
  347. };
  348. /* Process arguments */
  349. int index, c;
  350. while ((c = getopt_long(argc, argv, "eshiItnf:r:R:c:C:W:H:", long_opts, &index)) != -1) {
  351. if (!c) {
  352. if (long_opts[index].flag == 0) {
  353. c = long_opts[index].val;
  354. }
  355. }
  356. switch (c) {
  357. case 'e':
  358. clear_screen = 0;
  359. break;
  360. case 's':
  361. set_title = 0;
  362. break;
  363. case 'i': /* Show introduction */
  364. show_intro = 1;
  365. break;
  366. case 'I':
  367. skip_intro = 1;
  368. break;
  369. case 't': /* Expect telnet bits */
  370. telnet = 1;
  371. break;
  372. case 'h': /* Show help and exit */
  373. usage(argv);
  374. exit(0);
  375. break;
  376. case 'n':
  377. show_counter = 0;
  378. break;
  379. case 'f':
  380. frame_count = atoi(optarg);
  381. break;
  382. case 'r':
  383. min_row = atoi(optarg);
  384. break;
  385. case 'R':
  386. max_row = atoi(optarg);
  387. break;
  388. case 'c':
  389. min_col = atoi(optarg);
  390. break;
  391. case 'C':
  392. max_col = atoi(optarg);
  393. break;
  394. case 'W':
  395. min_col = (FRAME_WIDTH - atoi(optarg)) / 2;
  396. max_col = (FRAME_WIDTH + atoi(optarg)) / 2;
  397. break;
  398. case 'H':
  399. min_row = (FRAME_HEIGHT - atoi(optarg)) / 2;
  400. max_row = (FRAME_HEIGHT + atoi(optarg)) / 2;
  401. break;
  402. default:
  403. break;
  404. }
  405. }
  406. (void)skip_intro;
  407. #if 0
  408. if (telnet) {
  409. /* Telnet mode */
  410. /* show_intro is implied unless skip_intro was set */
  411. show_intro = (skip_intro == 0) ? 1 : 0;
  412. /* Set the default options */
  413. set_options();
  414. /* Let the client know what we're using */
  415. for (option = 0; option < 256; option++) {
  416. if (telnet_options[option]) {
  417. send_command(telnet_options[option], option);
  418. fflush(stdout);
  419. }
  420. }
  421. for (option = 0; option < 256; option++) {
  422. if (telnet_willack[option]) {
  423. send_command(telnet_willack[option], option);
  424. fflush(stdout);
  425. }
  426. }
  427. /* Set the alarm handler to execute the longjmp */
  428. signal(SIGALRM, SIGALRM_handler);
  429. /* Negotiate options */
  430. if (!setjmp(environment)) {
  431. /* We will stop handling options after one second */
  432. alarm(1);
  433. /* Let's do this */
  434. while (!feof(stdin) && done < 2) {
  435. /* Get either IAC (start command) or a regular character (break, unless in SB mode) */
  436. unsigned char i = getchar();
  437. unsigned char opt = 0;
  438. if (i == IAC) {
  439. /* If IAC, get the command */
  440. i = getchar();
  441. switch (i) {
  442. case SE:
  443. /* End of extended option mode */
  444. sb_mode = 0;
  445. if (sb[0] == TTYPE) {
  446. /* This was a response to the TTYPE command, meaning
  447. * that this should be a terminal type */
  448. alarm(2);
  449. strcpy(term, &sb[2]);
  450. done++;
  451. }
  452. else if (sb[0] == NAWS) {
  453. /* This was a response to the NAWS command, meaning
  454. * that this should be a window size */
  455. alarm(2);
  456. terminal_width = (sb[1] << 8) | sb[2];
  457. terminal_height = (sb[3] << 8) | sb[4];
  458. done++;
  459. }
  460. break;
  461. case NOP:
  462. /* No Op */
  463. send_command(NOP, 0);
  464. fflush(stdout);
  465. break;
  466. case WILL:
  467. case WONT:
  468. /* Will / Won't Negotiation */
  469. opt = getchar();
  470. if (!telnet_willack[opt]) {
  471. /* We default to WONT */
  472. telnet_willack[opt] = WONT;
  473. }
  474. send_command(telnet_willack[opt], opt);
  475. fflush(stdout);
  476. if ((i == WILL) && (opt == TTYPE)) {
  477. /* WILL TTYPE? Great, let's do that now! */
  478. printf("%c%c%c%c%c%c", IAC, SB, TTYPE, SEND, IAC, SE);
  479. fflush(stdout);
  480. }
  481. break;
  482. case DO:
  483. case DONT:
  484. /* Do / Don't Negotiation */
  485. opt = getchar();
  486. if (!telnet_options[opt]) {
  487. /* We default to DONT */
  488. telnet_options[opt] = DONT;
  489. }
  490. send_command(telnet_options[opt], opt);
  491. fflush(stdout);
  492. break;
  493. case SB:
  494. /* Begin Extended Option Mode */
  495. sb_mode = 1;
  496. sb_len = 0;
  497. memset(sb, 0, sizeof(sb));
  498. break;
  499. case IAC:
  500. /* IAC IAC? That's probably not right. */
  501. done = 2;
  502. break;
  503. default:
  504. break;
  505. }
  506. } else if (sb_mode) {
  507. /* Extended Option Mode -> Accept character */
  508. if (sb_len < sizeof(sb) - 1) {
  509. /* Append this character to the SB string,
  510. * but only if it doesn't put us over
  511. * our limit; honestly, we shouldn't hit
  512. * the limit, as we're only collecting characters
  513. * for a terminal type or window size, but better safe than
  514. * sorry (and vulnerable).
  515. */
  516. sb[sb_len] = i;
  517. sb_len++;
  518. }
  519. }
  520. }
  521. }
  522. alarm(0);
  523. } else {
  524. #else
  525. {
  526. #endif
  527. /* We are running standalone, retrieve the
  528. * terminal type from the environment. */
  529. char * nterm = getenv("TERM");
  530. if (nterm) {
  531. strcpy(term, nterm);
  532. }
  533. /* Also get the number of columns */
  534. struct winsize w;
  535. ioctl(0, TIOCGWINSZ, &w);
  536. terminal_width = w.ws_col;
  537. terminal_height = w.ws_row;
  538. }
  539. /* Convert the entire terminal string to lower case */
  540. for (k = 0; k < strlen(term); ++k) {
  541. term[k] = _tolower(term[k]);
  542. }
  543. /* Do our terminal detection */
  544. if (strstr(term, "xterm")) {
  545. ttype = 1; /* 256-color, spaces */
  546. } else if (strstr(term, "toaru")) {
  547. ttype = 1; /* emulates xterm */
  548. } else if (strstr(term, "linux")) {
  549. ttype = 3; /* Spaces and blink attribute */
  550. } else if (strstr(term, "vtnt")) {
  551. ttype = 5; /* Extended ASCII fallback == Windows */
  552. } else if (strstr(term, "cygwin")) {
  553. ttype = 5; /* Extended ASCII fallback == Windows */
  554. } else if (strstr(term, "vt220")) {
  555. ttype = 6; /* No color support */
  556. } else if (strstr(term, "fallback")) {
  557. ttype = 4; /* Unicode fallback */
  558. } else if (strstr(term, "rxvt")) {
  559. ttype = 3; /* Accepts LINUX mode */
  560. } else if (strstr(term, "vt100") && terminal_width == 40) {
  561. ttype = 7; /* No color support, only 40 columns */
  562. } else if (strstr(term, "st") == term) {
  563. ttype = 1; /* suckless simple terminal is xterm-256color-compatible */
  564. } else {
  565. ttype = 2; /* Everything else */
  566. }
  567. int always_escape = 0; /* Used for text mode */
  568. /* Accept ^C -> restore cursor */
  569. signal(SIGINT, SIGINT_handler);
  570. /* Handle loss of stdout */
  571. signal(SIGPIPE, SIGPIPE_handler);
  572. /* Handle window changes */
  573. if (!telnet) {
  574. signal(SIGWINCH, SIGWINCH_handler);
  575. }
  576. switch (ttype) {
  577. case 1:
  578. colors[','] = "\033[48;5;17m"; /* Blue background */
  579. colors['.'] = "\033[48;5;231m"; /* White stars */
  580. colors['\''] = "\033[48;5;16m"; /* Black border */
  581. colors['@'] = "\033[48;5;230m"; /* Tan poptart */
  582. colors['$'] = "\033[48;5;175m"; /* Pink poptart */
  583. colors['-'] = "\033[48;5;162m"; /* Red poptart */
  584. colors['>'] = "\033[48;5;196m"; /* Red rainbow */
  585. colors['&'] = "\033[48;5;214m"; /* Orange rainbow */
  586. colors['+'] = "\033[48;5;226m"; /* Yellow Rainbow */
  587. colors['#'] = "\033[48;5;118m"; /* Green rainbow */
  588. colors['='] = "\033[48;5;33m"; /* Light blue rainbow */
  589. colors[';'] = "\033[48;5;19m"; /* Dark blue rainbow */
  590. colors['*'] = "\033[48;5;240m"; /* Gray cat face */
  591. colors['%'] = "\033[48;5;175m"; /* Pink cheeks */
  592. break;
  593. case 2:
  594. colors[','] = "\033[104m"; /* Blue background */
  595. colors['.'] = "\033[107m"; /* White stars */
  596. colors['\''] = "\033[40m"; /* Black border */
  597. colors['@'] = "\033[47m"; /* Tan poptart */
  598. colors['$'] = "\033[105m"; /* Pink poptart */
  599. colors['-'] = "\033[101m"; /* Red poptart */
  600. colors['>'] = "\033[101m"; /* Red rainbow */
  601. colors['&'] = "\033[43m"; /* Orange rainbow */
  602. colors['+'] = "\033[103m"; /* Yellow Rainbow */
  603. colors['#'] = "\033[102m"; /* Green rainbow */
  604. colors['='] = "\033[104m"; /* Light blue rainbow */
  605. colors[';'] = "\033[44m"; /* Dark blue rainbow */
  606. colors['*'] = "\033[100m"; /* Gray cat face */
  607. colors['%'] = "\033[105m"; /* Pink cheeks */
  608. break;
  609. case 3:
  610. colors[','] = "\033[25;44m"; /* Blue background */
  611. colors['.'] = "\033[5;47m"; /* White stars */
  612. colors['\''] = "\033[25;40m"; /* Black border */
  613. colors['@'] = "\033[5;47m"; /* Tan poptart */
  614. colors['$'] = "\033[5;45m"; /* Pink poptart */
  615. colors['-'] = "\033[5;41m"; /* Red poptart */
  616. colors['>'] = "\033[5;41m"; /* Red rainbow */
  617. colors['&'] = "\033[25;43m"; /* Orange rainbow */
  618. colors['+'] = "\033[5;43m"; /* Yellow Rainbow */
  619. colors['#'] = "\033[5;42m"; /* Green rainbow */
  620. colors['='] = "\033[25;44m"; /* Light blue rainbow */
  621. colors[';'] = "\033[5;44m"; /* Dark blue rainbow */
  622. colors['*'] = "\033[5;40m"; /* Gray cat face */
  623. colors['%'] = "\033[5;45m"; /* Pink cheeks */
  624. break;
  625. case 4:
  626. colors[','] = "\033[0;34;44m"; /* Blue background */
  627. colors['.'] = "\033[1;37;47m"; /* White stars */
  628. colors['\''] = "\033[0;30;40m"; /* Black border */
  629. colors['@'] = "\033[1;37;47m"; /* Tan poptart */
  630. colors['$'] = "\033[1;35;45m"; /* Pink poptart */
  631. colors['-'] = "\033[1;31;41m"; /* Red poptart */
  632. colors['>'] = "\033[1;31;41m"; /* Red rainbow */
  633. colors['&'] = "\033[0;33;43m"; /* Orange rainbow */
  634. colors['+'] = "\033[1;33;43m"; /* Yellow Rainbow */
  635. colors['#'] = "\033[1;32;42m"; /* Green rainbow */
  636. colors['='] = "\033[1;34;44m"; /* Light blue rainbow */
  637. colors[';'] = "\033[0;34;44m"; /* Dark blue rainbow */
  638. colors['*'] = "\033[1;30;40m"; /* Gray cat face */
  639. colors['%'] = "\033[1;35;45m"; /* Pink cheeks */
  640. output = "██";
  641. break;
  642. case 5:
  643. colors[','] = "\033[0;34;44m"; /* Blue background */
  644. colors['.'] = "\033[1;37;47m"; /* White stars */
  645. colors['\''] = "\033[0;30;40m"; /* Black border */
  646. colors['@'] = "\033[1;37;47m"; /* Tan poptart */
  647. colors['$'] = "\033[1;35;45m"; /* Pink poptart */
  648. colors['-'] = "\033[1;31;41m"; /* Red poptart */
  649. colors['>'] = "\033[1;31;41m"; /* Red rainbow */
  650. colors['&'] = "\033[0;33;43m"; /* Orange rainbow */
  651. colors['+'] = "\033[1;33;43m"; /* Yellow Rainbow */
  652. colors['#'] = "\033[1;32;42m"; /* Green rainbow */
  653. colors['='] = "\033[1;34;44m"; /* Light blue rainbow */
  654. colors[';'] = "\033[0;34;44m"; /* Dark blue rainbow */
  655. colors['*'] = "\033[1;30;40m"; /* Gray cat face */
  656. colors['%'] = "\033[1;35;45m"; /* Pink cheeks */
  657. output = "\333\333";
  658. break;
  659. case 6:
  660. colors[','] = "::"; /* Blue background */
  661. colors['.'] = "@@"; /* White stars */
  662. colors['\''] = " "; /* Black border */
  663. colors['@'] = "##"; /* Tan poptart */
  664. colors['$'] = "??"; /* Pink poptart */
  665. colors['-'] = "<>"; /* Red poptart */
  666. colors['>'] = "##"; /* Red rainbow */
  667. colors['&'] = "=="; /* Orange rainbow */
  668. colors['+'] = "--"; /* Yellow Rainbow */
  669. colors['#'] = "++"; /* Green rainbow */
  670. colors['='] = "~~"; /* Light blue rainbow */
  671. colors[';'] = "$$"; /* Dark blue rainbow */
  672. colors['*'] = ";;"; /* Gray cat face */
  673. colors['%'] = "()"; /* Pink cheeks */
  674. always_escape = 1;
  675. break;
  676. case 7:
  677. colors[','] = "."; /* Blue background */
  678. colors['.'] = "@"; /* White stars */
  679. colors['\''] = " "; /* Black border */
  680. colors['@'] = "#"; /* Tan poptart */
  681. colors['$'] = "?"; /* Pink poptart */
  682. colors['-'] = "O"; /* Red poptart */
  683. colors['>'] = "#"; /* Red rainbow */
  684. colors['&'] = "="; /* Orange rainbow */
  685. colors['+'] = "-"; /* Yellow Rainbow */
  686. colors['#'] = "+"; /* Green rainbow */
  687. colors['='] = "~"; /* Light blue rainbow */
  688. colors[';'] = "$"; /* Dark blue rainbow */
  689. colors['*'] = ";"; /* Gray cat face */
  690. colors['%'] = "o"; /* Pink cheeks */
  691. always_escape = 1;
  692. terminal_width = 40;
  693. break;
  694. default:
  695. break;
  696. }
  697. if (min_col == max_col) {
  698. min_col = (FRAME_WIDTH - terminal_width/2) / 2;
  699. max_col = (FRAME_WIDTH + terminal_width/2) / 2;
  700. using_automatic_width = 1;
  701. }
  702. if (min_row == max_row) {
  703. min_row = (FRAME_HEIGHT - (terminal_height-1)) / 2;
  704. max_row = (FRAME_HEIGHT + (terminal_height-1)) / 2;
  705. using_automatic_height = 1;
  706. }
  707. /* Attempt to set terminal title */
  708. if (set_title) {
  709. printf("\033kNyanyanyanyanyanyanya...\033\134");
  710. printf("\033]1;Nyanyanyanyanyanyanya...\007");
  711. printf("\033]2;Nyanyanyanyanyanyanya...\007");
  712. }
  713. if (clear_screen) {
  714. /* Clear the screen */
  715. printf("\033[H\033[2J\033[?25l");
  716. } else {
  717. printf("\033[s");
  718. }
  719. if (show_intro) {
  720. /* Display the MOTD */
  721. unsigned int countdown_clock = 5;
  722. for (k = 0; k < countdown_clock; ++k) {
  723. newline(3);
  724. printf(" \033[1mNyancat Telnet Server\033[0m");
  725. newline(2);
  726. printf(" written and run by \033[1;32mK. Lange\033[1;34m @_klange\033[0m");
  727. newline(2);
  728. printf(" If things don't look right, try:");
  729. newline(1);
  730. printf(" TERM=fallback telnet ...");
  731. newline(2);
  732. printf(" Or on Windows:");
  733. newline(1);
  734. printf(" telnet -t vtnt ...");
  735. newline(2);
  736. printf(" Problems? Check the website:");
  737. newline(1);
  738. printf(" \033[1;34mhttp://nyancat.dakko.us\033[0m");
  739. newline(2);
  740. printf(" This is a telnet server, remember your escape keys!");
  741. newline(1);
  742. printf(" \033[1;31m^]quit\033[0m to exit");
  743. newline(2);
  744. printf(" Starting in %d... \n", countdown_clock-k);
  745. fflush(stdout);
  746. usleep(400000);
  747. if (clear_screen) {
  748. printf("\033[H"); /* Reset cursor */
  749. } else {
  750. printf("\033[u");
  751. }
  752. }
  753. if (clear_screen) {
  754. /* Clear the screen again */
  755. printf("\033[H\033[2J\033[?25l");
  756. }
  757. }
  758. /* Store the start time */
  759. time_t start, current;
  760. time(&start);
  761. int playing = 1; /* Animation should continue [left here for modifications] */
  762. size_t i = 0; /* Current frame # */
  763. unsigned int f = 0; /* Total frames passed */
  764. char last = 0; /* Last color index rendered */
  765. int y, x; /* x/y coordinates of what we're drawing */
  766. while (playing) {
  767. /* Reset cursor */
  768. if (clear_screen) {
  769. printf("\033[H");
  770. } else {
  771. printf("\033[u");
  772. }
  773. /* Render the frame */
  774. for (y = min_row; y < max_row; ++y) {
  775. for (x = min_col; x < max_col; ++x) {
  776. char color;
  777. if (y > 23 && y < 43 && x < 0) {
  778. /*
  779. * Generate the rainbow tail.
  780. *
  781. * This is done with a pretty simplistic square wave.
  782. */
  783. int mod_x = ((-x+2) % 16) / 8;
  784. if ((i / 2) % 2) {
  785. mod_x = 1 - mod_x;
  786. }
  787. /*
  788. * Our rainbow, with some padding.
  789. */
  790. const char *rainbow = ",,>>&&&+++###==;;;,,";
  791. color = rainbow[mod_x + y-23];
  792. if (color == 0) color = ',';
  793. } else if (x < 0 || y < 0 || y >= FRAME_HEIGHT || x >= FRAME_WIDTH) {
  794. /* Fill all other areas with background */
  795. color = ',';
  796. } else {
  797. /* Otherwise, get the color from the animation frame. */
  798. color = frames[i][y][x];
  799. }
  800. if (always_escape) {
  801. /* Text mode (or "Always Send Color Escapes") */
  802. printf("%s", colors[(int)color]);
  803. } else {
  804. if (color != last && colors[(int)color]) {
  805. /* Normal Mode, send escape (because the color changed) */
  806. last = color;
  807. printf("%s%s", colors[(int)color], output);
  808. } else {
  809. /* Same color, just send the output characters */
  810. printf("%s", output);
  811. }
  812. }
  813. }
  814. /* End of row, send newline */
  815. newline(1);
  816. }
  817. if (show_counter) {
  818. /* Get the current time for the "You have nyaned..." string */
  819. time(&current);
  820. double diff = difftime(current, start);
  821. /* Now count the length of the time difference so we can center */
  822. int nLen = digits((int)diff);
  823. /*
  824. * 29 = the length of the rest of the string;
  825. * XXX: Replace this was actually checking the written bytes from a
  826. * call to sprintf or something
  827. */
  828. int width = (terminal_width - 29 - nLen) / 2;
  829. /* Spit out some spaces so that we're actually centered */
  830. while (width > 0) {
  831. printf(" ");
  832. width--;
  833. }
  834. /* You have nyaned for [n] seconds!
  835. * The \033[J ensures that the rest of the line has the dark blue
  836. * background, and the \033[1;37m ensures that our text is bright white.
  837. * The \033[0m prevents the Apple ][ from flipping everything, but
  838. * makes the whole nyancat less bright on the vt220
  839. */
  840. //printf("\033[1;37mYou have nyaned for %0.0f seconds!\033[J\033[0m", diff);
  841. printf("\033[1;37mYou have nyaned for %d seconds!\033[J\033[0m", (int)diff);
  842. }
  843. /* Reset the last color so that the escape sequences rewrite */
  844. last = 0;
  845. /* Update frame count */
  846. ++f;
  847. if (frame_count != 0 && f == frame_count) {
  848. finish();
  849. }
  850. ++i;
  851. if (!frames[i]) {
  852. /* Loop animation */
  853. i = 0;
  854. }
  855. /* Wait */
  856. usleep(90000);
  857. }
  858. return 0;
  859. }