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