From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / xmatrix.c
1 /* xscreensaver, Copyright (c) 1999-2018 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Matrix -- simulate the text scrolls from the movie "The Matrix".
12  *
13  * The movie people distribute their own Windows/Mac screensaver that does
14  * a similar thing, so I wrote one for Unix.  However, that version (the
15  * Windows/Mac version at http://www.whatisthematrix.com/) doesn't match
16  * what the computer screens in the movie looked like, so my `xmatrix' does
17  * things differently.
18  *
19  * See also my `glmatrix' program, which does a 3D rendering of the similar
20  * effect that appeared in the title sequence of the movies.
21  *
22  *
23  *     ==========================================================
24  *
25  *         NOTE:
26  *
27  *         People just love to hack on this one.  I get sent
28  *         patches to this all the time saying, ``here, I made
29  *         it better!''  Mostly this hasn't been true.
30  *
31  *         If you've made changes to xmatrix, when you send me
32  *         your patch, please explain, in English, both *what*
33  *         your changes are, and *why* you think those changes
34  *         make this screensaver behave more like the displays
35  *         in the movie did.  I'd rather not have to read your
36  *         diffs to try and figure that out for myself...
37  *
38  *         In particular, note that the characters in the movie
39  *         were, in fact, low resolution and somewhat blurry/
40  *         washed out.  They also definitely scrolled a
41  *         character at a time, not a pixel at a time.
42  *
43  *         And keep in mind that this program emulates the
44  *         behavior of the computer screens that were visible
45  *         in the movies -- not the behavior of the effects in
46  *         the title sequences.  "GLMatrix" does that.
47  *
48  *     ==========================================================
49  *
50  */
51
52 #ifdef HAVE_UNISTD_H
53 # include <unistd.h>
54 #endif
55
56 #include "screenhack.h"
57 #include "textclient.h"
58 #include "ximage-loader.h"
59 #include <stdio.h>
60 #include <sys/wait.h>
61
62 #ifndef HAVE_JWXYZ
63 # include <X11/Intrinsic.h>
64 #endif
65
66 #include "images/gen/matrix1_png.h"
67 #include "images/gen/matrix2_png.h"
68 #include "images/gen/matrix1b_png.h"
69 #include "images/gen/matrix2b_png.h"
70
71 #define CHAR_COLS 16
72 #define CHAR_ROWS 13
73 #define CHAR_MAPS 3
74 #define PLAIN_MAP 1
75 #define GLOW_MAP  2
76
77 typedef struct {
78            int glow    : 8;
79   unsigned int glyph   : 9;  /* note: 9 bit characters! */
80   unsigned int changed : 1;
81   unsigned int spinner : 1;
82 } m_cell;
83
84 typedef struct {
85   int pipe_loc;
86   int remaining;
87   int throttle;
88   int y;
89 } m_feeder;
90
91 #define countof(x) (sizeof(x)/sizeof(*(x)))
92
93 static const int matrix_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
94                                        192, 193, 194, 195, 196, 197, 198, 199,
95                                        200, 201, 202, 203, 204, 205, 206, 207 };
96 static const int decimal_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
97 static const int hex_encoding[]     = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
98                                         33, 34, 35, 36, 37, 38 };
99 static const int binary_encoding[] = { 16, 17 };
100 static const int dna_encoding[]    = { 33, 35, 39, 52 };
101 static const int ascii_encoding[]  = {
102     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  
103     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
104     0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 
105    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 
106    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
107    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 
108    64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 
109    80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 
110    96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
111   112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
112 };
113 static const unsigned char char_map[256] = {
114     3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /*   0 */
115     3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /*  16 */
116     0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,  /*  32 */
117    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,  /*  48 */
118    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,  /*  64 */
119    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,  /*  80 */
120    64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,  /*  96 */
121    80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,  /* 112 */
122     3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /* 128 */
123     3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /* 144 */
124    96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,  /* 160 */
125   112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,  /* 176 */
126   128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,  /* 192 */
127   144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,  /* 208 */
128   160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,  /* 224 */
129   176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191   /* 240 */
130 };
131
132 #define CURSOR_GLYPH 97
133
134 /* larger numbers should mean more variability between columns */
135 #define BUF_SIZE 200
136
137 typedef enum { DRAIN_TRACE_A,
138                TRACE_TEXT_A,            /* Call trans opt: received. */
139                TRACE_A,                 /* (31_) 5__-0_9_ */
140                TRACE_DONE,
141
142                DRAIN_TRACE_B,
143                TRACE_TEXT_B,            /* Call trans opt: received. */
144                TRACE_B,
145                TRACE_FAIL,              /* System Failure */
146
147                DRAIN_KNOCK,
148                KNOCK,                   /* Wake up, Neo... */
149
150                DRAIN_NMAP,
151                NMAP,                    /* Starting nmap V. 2.54BETA25 */
152
153                DRAIN_MATRIX,
154                MATRIX,
155                DNA,
156                BINARY,
157                DEC,
158                HEX,
159                ASCII } m_mode;
160
161 typedef struct {
162   Display *dpy;
163   Window window;
164   XWindowAttributes xgwa;
165   GC draw_gc, erase_gc, scratch_gc;
166   int grid_width, grid_height;
167   int char_width, char_height;
168   m_cell *cells;
169   m_cell *background;
170   m_feeder *feeders;
171   int nspinners;
172   Bool knock_knock_p;
173   Bool small_p;
174   Bool insert_top_p, insert_bottom_p;
175   Bool use_pipe_p;
176   m_mode mode;
177   m_mode def_mode; /* Mode to return to after trace etc. */
178
179   text_data *tc;
180   char buf [BUF_SIZE*2+1]; /* ring buffer */
181   Bool do_fill_buff;
182   int buf_done;
183   int buf_pos;
184   Bool start_reveal_back_p; /* start reveal process for pipe */
185   Bool back_text_full_p; /* is the pipe buffer (background) full ? */
186   char back_line [BUF_SIZE*2+1]; /* line buffer for background */
187   int back_pos; /* background line buffer position */
188   int back_y;
189
190   signed char *tracing;
191   int density;
192
193   const char *typing;
194   Bool typing_scroll_p;
195   Bool typing_cursor_p;
196   Bool typing_bold_p;
197   Bool typing_stutter_p;
198   int typing_left_margin;
199   int typing_char_delay;
200   int typing_line_delay;
201   int typing_delay;
202
203   Bool cursor_on;
204   int cursor_x, cursor_y;
205   XtIntervalId cursor_timer;
206
207   Pixmap images[CHAR_MAPS];
208   int image_width, image_height;
209   Bool images_flipped_p;
210
211   int nglyphs;
212   const int *glyph_map;
213
214   unsigned long colors[5];
215   int delay;
216 } m_state;
217
218
219 static void
220 load_images_1 (Display *dpy, m_state *state, int which)
221 {
222   const unsigned char *png = 0;
223   unsigned long size = 0;
224   if (which == 1)
225     {
226       if (state->small_p)
227         png = matrix1b_png, size = sizeof(matrix1b_png);
228       else
229         png = matrix1_png, size = sizeof(matrix1_png);
230     }
231   else
232     {
233       if (state->small_p)
234         png = matrix2b_png, size = sizeof(matrix2b_png);
235       else
236         png = matrix2_png, size = sizeof(matrix2_png);
237     }
238   state->images[which] =
239     image_data_to_pixmap (state->dpy, state->window, png, size,
240                           &state->image_width, &state->image_height, 0);
241 }
242
243
244 static void
245 load_images (Display *dpy, m_state *state)
246 {
247   load_images_1 (dpy, state, 1);
248   load_images_1 (dpy, state, 2);
249 }
250
251
252 static void
253 flip_images_1 (m_state *state, int which)
254 {
255   XImage *im = XGetImage (state->dpy, state->images[which], 0, 0,
256                           state->image_width, state->image_height,
257                           ~0L, (state->xgwa.depth > 1 ? ZPixmap : XYPixmap));
258   int x, y, xx;
259   int ww = state->char_width;
260   unsigned long *row = (unsigned long *) malloc (sizeof(*row) * ww);
261
262   for (y = 0; y < state->image_height; y++)
263     {
264       for (x = 0; x < CHAR_COLS; x++)
265         {
266           for (xx = 0; xx < ww; xx++)
267             row[xx] = XGetPixel (im, (x * ww) + xx, y);
268           for (xx = 0; xx < ww; xx++)
269             XPutPixel (im, (x * ww) + xx, y, row[ww - xx - 1]);
270         }
271     }
272
273   XPutImage (state->dpy, state->images[which], state->draw_gc, im, 0, 0, 0, 0,
274              state->image_width, state->image_height);
275   XDestroyImage (im);
276   free (row);
277 }
278
279 static void
280 flip_images (m_state *state, Bool flipped_p)
281 {
282   if (flipped_p != state->images_flipped_p)
283     {
284       state->images_flipped_p = flipped_p;
285       flip_images_1 (state, 1);
286       flip_images_1 (state, 2);
287     }
288 }
289
290
291 /* When the subprocess has generated some output, this reads as much as it
292    can into s->buf at s->buf_tail.
293  */
294 static void
295 fill_input (m_state *s)
296 {
297   int n = 0;
298   int loadBytes;
299   if(s->buf_done > s->buf_pos){
300     loadBytes = (s->buf_done - s->buf_pos) - 1;
301   }
302   else{
303     loadBytes = ((BUF_SIZE - s->buf_pos) + s->buf_done) - 1;
304   }
305
306   if (!s->tc)
307     return;
308
309   if (loadBytes > 0)
310     {
311       int c = textclient_getc (s->tc);
312       n = (c > 0 ? 1 : -1);
313       s->buf [s->buf_pos] = (char) c;
314     }
315
316
317   if (n > 0)
318     {
319         s->do_fill_buff = False;
320       s->buf_pos = (s->buf_pos + n);
321       if(s->buf_pos > BUF_SIZE){
322         /* copy to start of buffer */
323         /* areas shouldn't overlap, but just in case, use memmove */
324         memmove(s->buf,s->buf+BUF_SIZE,s->buf_pos-BUF_SIZE);
325       }
326       s->buf_pos = s->buf_pos % BUF_SIZE;
327     }
328   else
329     {
330       /* Couldn't read anything from the buffer */
331       /* Assume EOF has been reached, so start again */
332       s->do_fill_buff = True;
333     }
334 }
335
336
337 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
338 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
339
340 static Bool
341 set_cursor_1 (m_state *state, Bool on)
342 {
343   Bool changed = (state->cursor_on != on);
344   state->cursor_on = on;
345
346   if (changed && state->cursor_x >= 0 && state->cursor_y >= 0)
347     {
348       m_cell *cell = &state->cells[state->grid_width * state->cursor_y
349                                    + state->cursor_x];
350       cell->glow = 0;
351       cell->changed = True;
352     }
353   return changed;
354 }
355
356
357 static void
358 set_cursor (m_state *state, Bool on)
359 {
360   if (set_cursor_1 (state, on))
361     {
362       if (state->cursor_timer)
363         XtRemoveTimeOut (state->cursor_timer);
364       state->cursor_timer = 0;
365       if (on)
366         cursor_on_timer (state, 0);
367     }
368 }
369
370 static void
371 cursor_off_timer (XtPointer closure, XtIntervalId *id)
372 {
373   m_state *state = (m_state *) closure;
374   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
375   set_cursor_1 (state, False);
376   state->cursor_timer = XtAppAddTimeOut (app, 333,
377                                          cursor_on_timer, closure);
378 }
379
380 static void
381 cursor_on_timer (XtPointer closure, XtIntervalId *id)
382 {
383   m_state *state = (m_state *) closure;
384   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
385   set_cursor_1 (state, True);
386   state->cursor_timer = XtAppAddTimeOut (app, 666,
387                                          cursor_off_timer, closure);
388 }
389
390
391 static void
392 init_spinners (m_state *state)
393 {
394   int i = state->nspinners;
395   int x, y;
396   m_cell *cell;
397
398   for (y = 0; y < state->grid_height; y++)
399     for (x = 0; x < state->grid_width; x++)
400       {
401         cell = &state->cells[state->grid_width * y + x];
402         cell->spinner = 0;
403       }
404
405   while (--i > 0)
406     {
407       x = random() % state->grid_width;
408       y = random() % state->grid_height;
409       cell = &state->cells[state->grid_width * y + x];
410       cell->spinner = 1;
411     }
412 }
413
414
415 static void
416 clear_spinners (m_state *state)
417 {
418   int i;
419   for (i = 0; i < state->grid_width * state->grid_height; i++)
420     if (state->cells[i].spinner)
421       {
422         state->cells[i].spinner = 0;
423         state->cells[i].changed = 1;
424       }
425 }
426
427
428 static void set_mode (m_state *, m_mode);
429
430
431 static void
432 init_trace (m_state *state)
433 {
434   char *s = get_string_resource (state->dpy, "tracePhone", "TracePhone");
435   const char *s2;
436   signed char *s3;
437   if (!s)
438     goto FAIL;
439
440   state->tracing = (signed char *) malloc (strlen (s) + 1);
441   s3 = state->tracing;
442
443   for (s2 = s; *s2; s2++)
444     if (*s2 >= '0' && *s2 <= '9')
445       *s3++ = -*s2;
446   *s3 = 0;
447
448   if (s3 == state->tracing)
449     goto FAIL;
450
451   state->glyph_map = decimal_encoding;
452   state->nglyphs = countof(decimal_encoding);
453
454   return;
455
456  FAIL:
457   fprintf (stderr, "%s: bad phone number: \"%s\".\n",
458            progname, s ? s : "(null)");
459
460   if (s) free (s);
461   if (state->tracing) free (state->tracing);
462   state->tracing = 0;
463   set_mode (state, MATRIX);
464 }
465
466
467 static void
468 init_drain (m_state *state)
469 {
470   int i;
471
472   set_cursor (state, False);
473   state->cursor_x = -1;
474   state->cursor_y = -1;
475
476   /* Fill the top row with empty top-feeders, to clear the screen. */
477   for (i = 0; i < state->grid_width; i++)
478     {
479       m_feeder *f = &state->feeders[i];
480       f->y = -1;
481       f->remaining = 0;
482       f->throttle = 0;
483     }
484
485   /* Turn off all the spinners, else they never go away. */
486   clear_spinners (state);
487 }
488
489 static Bool
490 screen_blank_p (m_state *state)
491 {
492   int i;
493   for (i = 0; i < state->grid_width * state->grid_height; i++)
494     if (state->cells[i].glyph)
495       return False;
496   return True;
497 }
498
499
500 static void
501 set_mode (m_state *state, m_mode mode)
502 {
503   if (mode == state->mode)
504     return;
505
506   state->mode = mode;
507   state->typing = 0;
508
509   switch (mode)
510     {
511     case MATRIX:
512       state->glyph_map = matrix_encoding;
513       state->nglyphs = countof(matrix_encoding);
514       flip_images (state, True);
515       init_spinners (state);
516       break;
517     case DNA:
518       state->glyph_map = dna_encoding;
519       state->nglyphs = countof(dna_encoding);
520       flip_images (state, False);
521       break;
522     case BINARY:
523       state->glyph_map = binary_encoding;
524       state->nglyphs = countof(binary_encoding);
525       flip_images (state, False);
526       break;
527     case HEX:
528       state->glyph_map = hex_encoding;
529       state->nglyphs = countof(hex_encoding);
530       flip_images (state, False);
531       break;
532     case ASCII:
533       state->glyph_map = ascii_encoding;
534       state->nglyphs = countof(ascii_encoding);
535       flip_images (state, False);
536       break;
537     case DEC:
538     case TRACE_A:
539     case TRACE_B:
540     case NMAP:
541     case KNOCK:
542       state->glyph_map = decimal_encoding;
543       state->nglyphs = countof(decimal_encoding);
544       flip_images (state, False);
545       break;
546     case TRACE_TEXT_A: 
547     case TRACE_TEXT_B:
548       flip_images (state, False);
549       init_trace (state);
550       break;
551     case DRAIN_TRACE_A:
552     case DRAIN_TRACE_B:
553     case DRAIN_KNOCK:
554     case DRAIN_NMAP:
555     case DRAIN_MATRIX:
556       init_drain (state);
557       break;
558     case TRACE_DONE:
559     case TRACE_FAIL:
560       break;
561     default:
562       abort();
563     }
564 }
565
566
567 static void *
568 xmatrix_init (Display *dpy, Window window)
569 {
570   XGCValues gcv;
571   char *insert, *mode;
572   int i;
573   m_state *state = (m_state *) calloc (sizeof(*state), 1);
574
575   state->dpy = dpy;
576   state->window = window;
577   state->delay = get_integer_resource (dpy, "delay", "Integer");
578
579   XGetWindowAttributes (dpy, window, &state->xgwa);
580
581   state->small_p = (state->xgwa.width < 300);
582   {
583     const char *s = get_string_resource (dpy, "matrixFont", "String");
584     if (!s || !*s || !strcasecmp(s, "large"))
585       state->small_p = False;
586     else if (!strcasecmp(s, "small"))
587       state->small_p = True;
588     else
589       fprintf (stderr, "%s: matrixFont should be 'small' or 'large' not '%s'\n",
590                progname, s);
591   }
592
593   load_images (dpy, state);
594
595   gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
596                                       "foreground", "Foreground");
597   gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
598                                       "background", "Background");
599   state->draw_gc = XCreateGC (state->dpy, state->window,
600                               GCForeground|GCBackground, &gcv);
601   gcv.foreground = gcv.background;
602   state->erase_gc = XCreateGC (state->dpy, state->window,
603                                GCForeground|GCBackground, &gcv);
604
605   state->scratch_gc = XCreateGC (state->dpy, state->window, 0, &gcv);
606
607   /* Allocate colors for SYSTEM FAILURE box */
608   {
609     XColor boxcolors[] = {
610       { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
611       { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
612       { 0, 0xE0E0, 0xF7F7, 0xE0E0, DoRed|DoGreen|DoBlue, 0 },
613       { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
614       { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
615     };
616   for (i = 0; i < countof(boxcolors); i++)
617     {
618       if (XAllocColor (state->dpy, state->xgwa.colormap, &boxcolors[i]))
619         state->colors[i] = boxcolors[i].pixel;
620       else
621         state->colors[i] = gcv.foreground;  /* default black */
622     }
623   }
624
625   state->char_width =  state->image_width  / CHAR_COLS;
626   state->char_height = state->image_height / CHAR_ROWS;
627
628   state->grid_width  = state->xgwa.width  / state->char_width;
629   state->grid_height = state->xgwa.height / state->char_height;
630   state->grid_width++;
631   state->grid_height++;
632   if (state->grid_width  < 5) state->grid_width  = 5;
633   if (state->grid_height < 5) state->grid_height = 5;
634
635   state->glyph_map = matrix_encoding;
636   state->nglyphs = countof(matrix_encoding);
637
638   state->cells = (m_cell *)
639     calloc (sizeof(m_cell), state->grid_width * state->grid_height);
640   state->background = (m_cell *)
641     calloc (sizeof(m_cell), state->grid_width * state->grid_height);
642   state->feeders = (m_feeder *) calloc (sizeof(m_feeder), state->grid_width);
643
644   state->density = get_integer_resource (dpy, "density", "Integer");
645
646   insert = get_string_resource(dpy, "insert", "Insert");
647   if (insert && !strcmp(insert, "top"))
648     {
649       state->insert_top_p = True;
650       state->insert_bottom_p = False;
651     }
652   else if (insert && !strcmp(insert, "bottom"))
653     {
654       state->insert_top_p = False;
655       state->insert_bottom_p = True;
656     }
657   else if (insert && !strcmp(insert, "both"))
658     {
659       state->insert_top_p = True;
660       state->insert_bottom_p = True;
661     }
662   else
663     {
664       if (insert && *insert)
665         fprintf (stderr,
666                  "%s: `insert' must be `top', `bottom', or `both', not `%s'\n",
667                  progname, insert);
668       state->insert_top_p = False;
669       state->insert_bottom_p = True;
670     }
671
672   state->nspinners = get_integer_resource (dpy, "spinners", "Integer");
673
674   if (insert)
675     free (insert);
676
677   state->knock_knock_p = get_boolean_resource (dpy, "knockKnock", "KnockKnock");
678
679   state->use_pipe_p = get_boolean_resource (dpy, "usePipe", "Boolean");
680   state->buf_pos = 1;
681   state->buf[0] = ' '; /* spacer byte in buffer (space) */
682   state->buf_done = 0;
683   state->do_fill_buff = True;
684   state->start_reveal_back_p = False;
685   state->back_text_full_p = False;
686   state->back_y = 0;
687   state->back_pos = 0;
688
689   state->mode = -1;
690   state->def_mode = MATRIX;
691   mode = get_string_resource (dpy, "mode", "Mode");
692   if (mode && !strcasecmp(mode, "trace"))
693     set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
694   else if (mode && !strcasecmp(mode, "crack"))
695     set_mode (state, DRAIN_NMAP);
696   else if (mode && !strcasecmp(mode, "dna")){
697     set_mode (state, DNA);
698     state->def_mode = DNA;
699   }
700   else if (mode && (!strcasecmp(mode, "bin") ||
701                     !strcasecmp(mode, "binary"))){
702     set_mode (state, BINARY);
703     state->def_mode = BINARY;
704   }
705   else if (mode && (!strcasecmp(mode, "hex") ||
706                     !strcasecmp(mode, "hexadecimal"))){
707     set_mode (state, HEX);
708     state->def_mode = HEX;
709   }
710   else if (mode && (!strcasecmp(mode, "dec") ||
711                     !strcasecmp(mode, "decimal"))){
712     set_mode (state, DEC);
713     state->def_mode = DEC;
714   }
715   else if (mode && (!strcasecmp(mode, "asc") ||
716                     !strcasecmp(mode, "ascii"))){
717     set_mode (state, ASCII);
718     state->def_mode = ASCII;
719   }
720   else if (mode && !strcasecmp(mode, "pipe"))
721     {
722       set_mode (state, ASCII);
723       state->def_mode = ASCII;
724       state->use_pipe_p = True;
725       state->tc = textclient_open (dpy);
726     }
727   else if (!mode || !*mode || !strcasecmp(mode, "matrix"))
728     set_mode (state, MATRIX);
729   else
730     {
731       fprintf (stderr, "%s: `mode' must be ",progname);
732       fprintf (stderr, "matrix, trace, dna, binary, ascii, hex, or pipe: ");
733       fprintf (stderr, "not `%s'\n", mode);
734       set_mode (state, MATRIX);
735     }
736
737   if (state->mode == MATRIX && get_boolean_resource (dpy, "trace", "Boolean"))
738     set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
739
740   state->cursor_x = -1;
741   state->cursor_y = -1;
742
743   return state;
744 }
745
746
747 static void
748 insert_glyph (m_state *state, int glyph, int x, int y)
749 {
750   Bool bottom_feeder_p = (y >= 0);
751   m_cell *from, *to;
752   if (y >= state->grid_height)
753     return;
754
755   if (bottom_feeder_p)
756     {
757       to = &state->cells[state->grid_width * y + x];
758     }
759   else
760     {
761       for (y = state->grid_height-1; y > 0; y--)
762         {
763           from = &state->cells[state->grid_width * (y-1) + x];
764           to   = &state->cells[state->grid_width * y     + x];
765           to->glyph   = from->glyph;
766           to->glow    = from->glow;
767           to->changed = 1;
768         }
769       to = &state->cells[x];
770     }
771
772   to->glyph = glyph;
773   to->changed = 1;
774
775   if (!to->glyph)
776     ;
777   else if (bottom_feeder_p)
778     to->glow = 1 + (random() % (state->tracing ? 4 : 2));
779   else
780     to->glow = 0;
781 }
782
783
784 static void
785 place_back_char (m_state *state, char textc, int x, int y){
786   if((x>=0) && (y>=0) &&
787      (x < state->grid_width) && (y < state->grid_height)){
788     m_cell *celltmp = &state->background[state->grid_width * y + x];
789     celltmp -> glyph = char_map[(unsigned char)textc] + 1;
790     if(!celltmp->glyph || (celltmp->glyph == 3)){
791       celltmp -> glyph = char_map[32] + 1; 
792     }
793     celltmp -> changed = 1;
794   } 
795 }
796
797 static void
798 place_back_text (m_state *state, char *text, int x, int y){
799   int i;
800   for(i=0; i<strlen(text); i++){
801     place_back_char(state, text[i], x+i, y);
802   }
803 }
804
805 static void
806 place_back_pipe (m_state *state, char textc){
807   Bool new_line = False;
808   /* gringer pipe insert */
809   state->back_line[state->back_pos] = textc;
810   if(textc == '\n'){
811     state->back_line[state->back_pos] = '\0';
812     new_line = True;
813   }
814   else if ((state->back_pos > (state->grid_width - 4)) || 
815            (state->back_pos >= BUF_SIZE)){ /* off by 1? */
816     state->back_line[++state->back_pos] = '\0';
817     new_line = True;
818   }
819   else{
820     state->back_pos++;
821   }
822   if(new_line){
823     int startx = (state->grid_width >> 1) - 
824       (strlen(state->back_line) >> 1);
825     place_back_text(state, state->back_line, 
826                     startx, state->back_y);
827     state->back_pos = 0;
828     state->back_y++;
829     if(state->back_y >= (state->grid_height - 1)){
830       state->back_y = 1;
831       state->back_text_full_p = True;
832       state->start_reveal_back_p = True;
833     }
834   }
835 }
836
837 static void
838 feed_matrix (m_state *state)
839 {
840   int x;
841
842   switch (state->mode)
843     {
844     case TRACE_A:
845         {
846           int L = strlen((char *) state->tracing);
847           int count = 0;
848           int i;
849
850           for (i = 0; i < strlen((char *) state->tracing); i++)
851             if (state->tracing[i] > 0) 
852               count++;
853
854           if (count >= L)
855             {
856               set_mode (state, TRACE_DONE);
857               state->typing_delay = 1000000;
858               return;
859             }
860           else
861             {
862               i = 5 + (30 / (count+1)); /* how fast numbers are discovered */
863
864               if ((random() % i) == 0)
865                 {
866                   i = random() % L;
867                   if (state->tracing[i] < 0)
868                     state->tracing[i] = -state->tracing[i];
869                 }
870             }
871         }
872       break;
873
874     case TRACE_B:
875       if ((random() % 40) == 0)
876         {
877           set_mode (state, TRACE_FAIL);
878           return;
879         }
880       break;
881
882     case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII: 
883     case DRAIN_TRACE_A:
884     case DRAIN_TRACE_B:
885     case DRAIN_KNOCK:
886     case DRAIN_NMAP:
887     case DRAIN_MATRIX:
888       break;
889
890     default:
891       abort();
892     }
893
894   /*get input*/
895   if((state->use_pipe_p) && (!state->back_text_full_p)){
896     place_back_pipe(state, state->buf[state->buf_done]);
897     state->buf_done = (state->buf_done + 1) % BUF_SIZE;
898     if(state->buf_done == (state->buf_pos - 1)){
899       state->do_fill_buff = True;
900     }
901     }
902   if(state->buf_done == (state->buf_pos + 1)){
903     state->do_fill_buff = False;
904   }
905   else{
906     state->do_fill_buff = True;
907     fill_input(state);
908   }
909
910   /* Update according to current feeders. */
911   for (x = 0; x < state->grid_width; x++)
912     {
913       m_feeder *f = &state->feeders[x];
914
915       if (f->throttle)          /* this is a delay tick, synced to frame. */
916         {
917           f->throttle--;
918         }
919       else if (f->remaining > 0)        /* how many items are in the pipe */
920         {
921           int g;
922           long rval;
923           if((state->use_pipe_p) && (!state->back_text_full_p)){
924             rval = (int) state->buf[f->pipe_loc];
925             if(++f->pipe_loc > (BUF_SIZE-1)){
926               f->pipe_loc = 0;
927               /*fill_input(state);*/
928             }
929             rval = (rval % state->nglyphs);
930           }
931           else{
932             rval = (random() % state->nglyphs);
933           }
934           g = state->glyph_map[rval] + 1;
935           insert_glyph (state, g, x, f->y);
936           f->remaining--;
937           if (f->y >= 0)  /* bottom_feeder_p */
938             f->y++;
939         }
940       else                              /* if pipe is empty, insert spaces */
941         {
942           insert_glyph (state, 0, x, f->y);
943           if (f->y >= 0)  /* bottom_feeder_p */
944             f->y++;
945         }
946
947       if ((random() % 10) == 0)         /* randomly change throttle speed */
948         {
949           f->throttle = ((random() % 5) + (random() % 5));
950         }
951     }
952 }
953
954
955 static void
956 redraw_cells (m_state *state, Bool active)
957 {
958   int x, y;
959   int count = 0;
960   Bool use_back_p = False;
961
962   for (y = 0; y < state->grid_height; y++)
963     for (x = 0; x < state->grid_width; x++)
964       {
965         m_cell *cell = &state->cells[state->grid_width * y + x];
966         m_cell *back = &state->background[state->grid_width * y + x];
967         Bool cursor_p = (state->cursor_on &&
968                          x == state->cursor_x && 
969                          y == state->cursor_y);
970
971         if (cell->glyph)
972           count++;
973         else {
974           if((state->start_reveal_back_p) && 
975              (back->glyph) && !(state->mode == TRACE_A || 
976                                 state->mode == TRACE_B ||
977                                 state->mode == TRACE_DONE)){
978             use_back_p = True;
979             cell = back;
980           }
981         }
982
983         /* In trace-mode, the state of each cell is random unless we have
984            a match for this digit. */
985         if (active && (state->mode == TRACE_A || 
986                        state->mode == TRACE_B ||
987                        state->mode == TRACE_DONE))
988           {
989             int xx = x % strlen((char *) state->tracing);
990             Bool dead_p = state->tracing[xx] > 0;
991
992             if (y == 0 && x == xx && !use_back_p)
993               cell->glyph = (dead_p
994                              ? state->glyph_map[state->tracing[xx]-'0'] + 1
995                              : 0);
996             else if (y == 0 && !use_back_p)
997               cell->glyph = 0;
998             else if (!use_back_p)
999               cell->glyph = (dead_p ? 0 :
1000                              (state->glyph_map[(random()%state->nglyphs)]
1001                               + 1));
1002             if (!use_back_p)
1003             cell->changed = 1;
1004           }
1005
1006         if (!cell->changed)
1007           continue;
1008
1009
1010         if (cell->glyph == 0 && !cursor_p && !use_back_p)
1011           XFillRectangle (state->dpy, state->window, state->erase_gc,
1012                           x * state->char_width,
1013                           y * state->char_height,
1014                           state->char_width,
1015                           state->char_height);
1016         else
1017           {
1018             int g = (cursor_p ? CURSOR_GLYPH : cell->glyph);
1019             int cx = (g - 1) % CHAR_COLS;
1020             int cy = (g - 1) / CHAR_COLS;
1021             int map = ((cell->glow != 0 || cell->spinner) ? GLOW_MAP :
1022                        PLAIN_MAP);
1023
1024             XCopyArea (state->dpy, state->images[map],
1025                        state->window, state->draw_gc,
1026                        cx * state->char_width,
1027                        cy * state->char_height,
1028                        state->char_width,
1029                        state->char_height,
1030                        x * state->char_width,
1031                        y * state->char_height);
1032           }
1033         if (!use_back_p)
1034         cell->changed = 0;
1035
1036         if (cell->glow > 0 && state->mode != NMAP && !use_back_p)
1037           {
1038             cell->glow--;
1039             cell->changed = 1;
1040           }
1041         else if (cell->glow < 0)
1042           abort();
1043
1044         if (cell->spinner && active && !use_back_p)
1045           {
1046             cell->glyph = (state->glyph_map[(random()%state->nglyphs)] + 1);
1047             cell->changed = 1;
1048           }
1049       }
1050 }
1051
1052
1053 static int
1054 densitizer (m_state *state)
1055 {
1056   /* Horrid kludge that converts percentages (density of screen coverage)
1057      to the parameter that actually controls this.  I got this mapping
1058      empirically, on a 1024x768 screen.  Sue me. */
1059   if      (state->density < 10) return 85;
1060   else if (state->density < 15) return 60;
1061   else if (state->density < 20) return 45;
1062   else if (state->density < 25) return 25;
1063   else if (state->density < 30) return 20;
1064   else if (state->density < 35) return 15;
1065   else if (state->density < 45) return 10;
1066   else if (state->density < 50) return 8;
1067   else if (state->density < 55) return 7;
1068   else if (state->density < 65) return 5;
1069   else if (state->density < 80) return 3;
1070   else if (state->density < 90) return 2;
1071   else return 1;
1072 }
1073
1074
1075 static void
1076 hack_text (m_state *state)
1077 {
1078   if (!state->typing)
1079     {
1080       set_cursor (state, False);
1081       state->cursor_x = 0;
1082       state->cursor_y = 0;
1083       state->typing_scroll_p = False;
1084       state->typing_bold_p = False;
1085       state->typing_cursor_p = True;
1086       state->typing_stutter_p = False;
1087       state->typing_char_delay = 10000;
1088       state->typing_line_delay = 1500000;
1089
1090       switch (state->mode)
1091         {
1092         case TRACE_TEXT_A:
1093         case TRACE_TEXT_B:
1094           clear_spinners (state);
1095           if (state->mode == TRACE_TEXT_A)
1096             {
1097               if (state->grid_width >= 52)
1098                 state->typing =
1099                   ("Call trans opt: received. 2-19-98 13:24:18 REC:Log>\n"
1100                    "Trace program: running\n");
1101               else
1102                 state->typing =
1103                   ("Call trans opt: received.\n2-19-98 13:24:18 REC:Log>\n"
1104                    "Trace program: running\n");
1105             }
1106           else
1107             {
1108               if (state->grid_width >= 52)
1109                 state->typing =
1110                   ("Call trans opt: received. 9-18-99 14:32:21 REC:Log>\n"
1111                    "WARNING: carrier anomaly\n"
1112                    "Trace program: running\n");
1113               else
1114                 state->typing =
1115                   ("Call trans opt: received.\n9-18-99 14:32:21 REC:Log>\n"
1116                    "WARNING: carrier anomaly\n"
1117                    "Trace program: running\n");
1118             }
1119           break;
1120
1121         case TRACE_FAIL:
1122           {
1123             const char *s = "SYSTEM FAILURE\n";
1124             int i;
1125             float cx = (state->grid_width - strlen(s) - 1) / 2 - 0.5;
1126             float cy = (state->grid_height / 2) - 1.3;
1127
1128             if (cy < 0) cy = 0;
1129             if (cx < 0) cx = 0;
1130
1131             XFillRectangle (state->dpy, state->window, state->erase_gc,
1132                             cx * state->char_width,
1133                             cy * state->char_height,
1134                             strlen(s) * state->char_width,
1135                             state->char_height * 1.6);
1136
1137             for (i = -2; i < 3; i++)
1138               {
1139                 XGCValues gcv;
1140                 gcv.foreground = state->colors[i + 2];
1141                 XChangeGC (state->dpy, state->scratch_gc, GCForeground, &gcv);
1142                 XDrawRectangle (state->dpy, state->window, state->scratch_gc,
1143                                 cx * state->char_width - i,
1144                                 cy * state->char_height - i,
1145                                 strlen(s) * state->char_width + (2 * i),
1146                                 (state->char_height * 1.6) + (2 * i));
1147               }
1148
1149             /* If we don't clear these, part of the box may get overwritten */
1150             for (i = 0; i < state->grid_height * state->grid_width; i++)
1151               {
1152                 m_cell *cell = &state->cells[i];
1153                 cell->changed = 0;
1154               }
1155
1156             state->cursor_x = (state->grid_width - strlen(s) - 1) / 2;
1157             state->cursor_y = (state->grid_height / 2) - 1;
1158             if (state->cursor_x < 0) state->cursor_x = 0;
1159             if (state->cursor_y < 0) state->cursor_y = 0;
1160
1161             state->typing = s;
1162             state->typing_char_delay = 0;
1163             state->typing_cursor_p = False;
1164           }
1165           break;
1166
1167         case KNOCK:
1168           {
1169             clear_spinners (state);
1170             state->typing = ("\001Wake up, Neo...\n"
1171                              "\001The Matrix has you...\n"
1172                              "\001Follow the white rabbit.\n"
1173                              "\n"
1174                              "Knock, knock, Neo.\n");
1175
1176             state->cursor_x = 4;
1177             state->cursor_y = 2;
1178             state->typing_char_delay = 0;
1179             state->typing_line_delay = 2000000;
1180           }
1181           break;
1182
1183         case NMAP:
1184           {
1185             /* Note that what Trinity is using here is moderately accurate:
1186                She runs nmap (http://www.insecure.org/nmap/) then breaks in
1187                with a (hypothetical) program called "sshnuke" that exploits
1188                the (very real) SSHv1 CRC32 compensation attack detector bug
1189                (http://staff.washington.edu/dittrich/misc/ssh-analysis.txt).
1190
1191                The command syntax of the power grid control software looks a
1192                lot like Cisco IOS to me.  (IOS is a descendant of VMS.)
1193             */
1194
1195             clear_spinners (state);
1196             state->typing =
1197 # ifdef __GNUC__
1198             __extension__  /* don't warn about "string length is greater than
1199                               the length ISO C89 compilers are required to
1200                               support"... */
1201
1202 # endif
1203               ("# "
1204                "\010\010\010\010"
1205                "\001nmap -v -sS -O 10.2.2.2\n"
1206                "Starting nmap V. 2.54BETA25\n"
1207                "\010\010\010\010\010\010\010\010\010\010"
1208                "Insufficient responses for TCP sequencing (3), OS detection"
1209                " may be less accurate\n"
1210                "Interesting ports on 10.2.2.2:\n"
1211                "(The 1539 ports scanned but not shown below are in state:"
1212                " closed)\n"
1213                "Port       state       service\n"
1214                "22/tcp     open        ssh\n"
1215                "\n"
1216                "No exact OS matches for host\n"
1217                "\n"
1218                "Nmap run completed -- 1 IP address (1 host up) scanned\n"
1219                "# "
1220
1221                "\010\010\010\010"
1222                "\001sshnuke 10.2.2.2 -rootpw=\"Z1ON0101\"\n"
1223                "Connecting to 10.2.2.2:ssh ... "
1224                "\010\010"
1225                "successful.\n"
1226                "Attempting to exploit SSHv1 CRC32 ... "
1227                "\010\010\010\010"
1228                "successful.\n"
1229                "Resetting root password to \"Z1ON0101\".\n"
1230                "\010\010"
1231                "System open: Access Level <9>\n"
1232
1233                "# "
1234                "\010\010"
1235
1236                "\001ssh 10.2.2.2 -l root\n"
1237                "\010\010"
1238                "root@10.2.2.2's password: "
1239                "\010\010\n"
1240                "\010\010\n"
1241                "RRF-CONTROL> "
1242
1243                "\010\010"
1244                "\001disable grid nodes 21 - 48\n"
1245                "\n"
1246                "\002Warning: Disabling nodes 21-48 will disconnect sector 11"
1247                " (28 nodes)\n"
1248                "\n"
1249                "\002         ARE YOU SURE? (y/n) "
1250
1251                "\010\010"
1252                "\001y\n"
1253                "\n"
1254                "\n"
1255                "\010\002Grid Node 21 offline...\n"
1256                "\010\002Grid Node 22 offline...\n"
1257                "\010\002Grid Node 23 offline...\n"
1258                "\010\002Grid Node 24 offline...\n"
1259                "\010\002Grid Node 25 offline...\n"
1260                "\010\002Grid Node 26 offline...\n"
1261                "\010\002Grid Node 27 offline...\n"
1262                "\010\002Grid Node 28 offline...\n"
1263                "\010\002Grid Node 29 offline...\n"
1264                "\010\002Grid Node 30 offline...\n"
1265                "\010\002Grid Node 31 offline...\n"
1266                "\010\002Grid Node 32 offline...\n"
1267                "\010\002Grid Node 33 offline...\n"
1268                "\010\002Grid Node 34 offline...\n"
1269                "\010\002Grid Node 35 offline...\n"
1270                "\010\002Grid Node 36 offline...\n"
1271                "\010\002Grid Node 37 offline...\n"
1272                "\010\002Grid Node 38 offline...\n"
1273                "\010\002Grid Node 39 offline...\n"
1274                "\010\002Grid Node 40 offline...\n"
1275                "\010\002Grid Node 41 offline...\n"
1276                "\010\002Grid Node 42 offline...\n"
1277                "\010\002Grid Node 43 offline...\n"
1278                "\010\002Grid Node 44 offline...\n"
1279                "\010\002Grid Node 45 offline...\n"
1280                "\010\002Grid Node 46 offline...\n"
1281                "\010\002Grid Node 47 offline...\n"
1282                "\010\002Grid Node 48 offline...\n"
1283                "\010\010"
1284                "\nRRF-CONTROL> "
1285                "\010\010\010\010\010\010\010\010"
1286                );
1287
1288             state->cursor_x = 0;
1289             state->cursor_y = state->grid_height - 3;
1290             state->typing_scroll_p = True;
1291             state->typing_char_delay = 0;
1292             state->typing_line_delay = 20000;
1293           }
1294           break;
1295
1296         default:
1297           abort();
1298           break;
1299         }
1300
1301       state->typing_left_margin = state->cursor_x;
1302       state->typing_delay = state->typing_char_delay;
1303       if (state->typing_cursor_p)
1304         set_cursor (state, True);
1305
1306 # ifdef USE_IPHONE
1307   /* Stupid iPhone X bezel.
1308      #### This is the worst of all possible ways to do this!  But how else?
1309    */
1310   if (state->xgwa.width == 2436 || state->xgwa.height == 2436)
1311     switch (state->mode) 
1312       {
1313       case TRACE_TEXT_A:
1314       case TRACE_TEXT_B:
1315       case KNOCK:
1316       case NMAP:
1317         {
1318           int off = 5 * (state->small_p ? 2 : 1);
1319           if (state->xgwa.width > state->xgwa.height)
1320             {
1321               state->typing_left_margin += off;
1322               state->cursor_x += off;
1323             }
1324           else
1325             {
1326               state->cursor_y += off;
1327             }
1328         }
1329       default: break;
1330       }
1331 # endif
1332     }
1333   else
1334     {
1335       Bool scrolled_p = False;
1336       unsigned char c, c1;
1337       int x = state->cursor_x;
1338       int y = state->cursor_y;
1339
1340     AGAIN:
1341       c  = ((unsigned char *) state->typing)[0];
1342       c1 = c ? ((unsigned char *) state->typing)[1] : 0;
1343
1344       state->typing_delay = (!c || c1 == '\n'
1345                              ? state->typing_line_delay
1346                              : state->typing_char_delay);
1347       if (! c)
1348         {
1349           state->typing_delay = 0;
1350           state->typing = 0;
1351           return;
1352         }
1353
1354       if (state->typing_scroll_p &&
1355           (c == '\n' || 
1356            x >= state->grid_width - 1))
1357         {
1358           set_cursor (state, False);
1359           x = 0;
1360           y++;
1361
1362           if (y >= state->grid_height-1)
1363             {
1364               int xx, yy;
1365               for (yy = 0; yy < state->grid_height-2; yy++)
1366                 for (xx = 0; xx < state->grid_width; xx++)
1367                   {
1368                     int ii = yy     * state->grid_width + xx;
1369                     int jj = (yy+1) * state->grid_width + xx;
1370                     state->cells[ii] = state->cells[jj];
1371                     state->cells[ii].changed = 1;
1372                   }
1373               /* clear bottom row */
1374               for (xx = 0; xx < state->grid_width; xx++)
1375                 {
1376                   int ii = yy * state->grid_width + xx;
1377                   state->cells[ii].glyph   = 0;
1378                   state->cells[ii].changed = 1;
1379                 }
1380               y--;  /* move back up to bottom line */
1381               scrolled_p = True;
1382             }
1383         }
1384
1385       if (c == '\n')
1386         {
1387           if (!state->typing_scroll_p)
1388             {
1389               int i, j;
1390               set_cursor (state, False);
1391               x = state->typing_left_margin;
1392
1393               /* clear the line */
1394               i = state->grid_width * y;
1395               j = i + state->grid_width;
1396               for (; i < j; i++)
1397                 {
1398                   state->cells[i].glyph   = 0;
1399                   state->cells[i].changed = 1;
1400                 }
1401             }
1402           state->typing_bold_p = False;
1403           state->typing_stutter_p = False;
1404           scrolled_p = True;
1405         }
1406
1407       else if (c == '\010')
1408         state->typing_delay += 500000;
1409
1410       else if (c == '\001')
1411         {
1412           state->typing_stutter_p = True;
1413           state->typing_bold_p = False;
1414         }
1415       else if (c == '\002')
1416         state->typing_bold_p = True;
1417
1418       else if (x < state->grid_width-1)
1419         {
1420           m_cell *cell = &state->cells[state->grid_width * y + x];
1421           cell->glyph = char_map[c] + 1;
1422           if (c == ' ' || c == '\t') cell->glyph = 0;
1423           cell->changed = 1;
1424           cell->glow = (state->typing_bold_p ? 127 : 0);
1425         }
1426
1427       if (c >= ' ')
1428         x++;
1429
1430       if (x >= state->grid_width-1)
1431         x = state->grid_width-1;
1432
1433       state->typing++;
1434
1435       if (state->typing_stutter_p)
1436         {
1437           if (state->typing_delay == 0)
1438             state->typing_delay = 20000;
1439           if (random() % 3)
1440             state->typing_delay += (0xFFFFFF & ((random() % 200000) + 1));
1441         }
1442
1443       /* If there's no delay after this character, just keep going. */
1444       if (state->typing_delay == 0)
1445         goto AGAIN;
1446
1447       if (scrolled_p || x != state->cursor_x || y != state->cursor_y)
1448         {
1449           set_cursor (state, False);
1450           state->cursor_x = x;
1451           state->cursor_y = y;
1452           if (state->typing_cursor_p)
1453             set_cursor (state, True);
1454         }
1455     }
1456 }
1457
1458
1459 static void
1460 hack_matrix (m_state *state)
1461 {
1462   int x;
1463
1464   switch (state->mode)
1465     {
1466     case TRACE_DONE: case TRACE_FAIL:
1467       return;
1468     case TRACE_A: case TRACE_B:
1469     case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1470       break;
1471     default:
1472       abort(); break;
1473     }
1474
1475   /* Glow some characters. */
1476   if (!state->insert_bottom_p)
1477     {
1478       int i = random() % (state->grid_width / 2);
1479       while (--i > 0)
1480         {
1481           int yy = random() % state->grid_height;
1482           int xx = random() % state->grid_width;
1483           m_cell *cell = &state->cells[state->grid_width * yy + xx];
1484           if (cell->glyph && cell->glow == 0)
1485             {
1486               cell->glow = random() % 10;
1487               cell->changed = 1;
1488             }
1489         }
1490     }
1491
1492   /* Change some of the feeders. */
1493   for (x = 0; x < state->grid_width; x++)
1494     {
1495       m_feeder *f = &state->feeders[x];
1496       Bool bottom_feeder_p;
1497
1498       if (f->remaining > 0)     /* never change if pipe isn't empty */
1499         continue;
1500
1501       if ((random() % densitizer(state)) != 0) /* then change N% of the time */
1502         continue;
1503
1504       f->remaining = 3 + (random() % state->grid_height);
1505       f->throttle = ((random() % 5) + (random() % 5));
1506
1507       if ((random() % 4) != 0)
1508         f->remaining = 0;
1509
1510       if (state->mode == TRACE_A || state->mode == TRACE_B)
1511         bottom_feeder_p = True;
1512       else if (state->insert_top_p && state->insert_bottom_p)
1513         bottom_feeder_p = (random() & 1);
1514       else
1515         bottom_feeder_p = state->insert_bottom_p;
1516
1517       if (bottom_feeder_p)
1518         f->y = random() % (state->grid_height / 2);
1519       else
1520         f->y = -1;
1521     }
1522
1523   if (state->mode == MATRIX && (! (random() % 500)))
1524     init_spinners (state);
1525 }
1526
1527
1528 static unsigned long
1529 xmatrix_draw (Display *dpy, Window window, void *closure)
1530 {
1531   m_state *state = (m_state *) closure;
1532
1533   if (state->typing_delay > 0)
1534     {
1535       state->typing_delay -= state->delay;
1536       if (state->typing_delay < 0)
1537         state->typing_delay = 0;
1538       redraw_cells (state, False);
1539       return state->delay;
1540     }
1541
1542   switch (state->mode)
1543     {
1544     case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1545     case TRACE_A: case TRACE_B:
1546       feed_matrix (state);
1547       hack_matrix (state);
1548       break;
1549
1550     case DRAIN_TRACE_A:
1551     case DRAIN_TRACE_B:
1552     case DRAIN_KNOCK:
1553     case DRAIN_NMAP:
1554     case DRAIN_MATRIX:
1555       feed_matrix (state);
1556       if (screen_blank_p (state))
1557         {
1558           state->typing_delay = 500000;
1559           if(state->start_reveal_back_p){
1560             m_cell *back, *to;
1561             int x,y;
1562             state->typing_delay = 5000000;
1563             state->start_reveal_back_p = False;
1564             state->back_text_full_p = False;
1565             /* for loop to move background to foreground */
1566             for (y = 0; y < state->grid_height; y++){
1567               for (x = 0; x < state->grid_width; x++){
1568                 to = &state->cells[state->grid_width * y + x];
1569                 back = &state->background[state->grid_width * y + x];
1570                 to->glyph = back->glyph;
1571                 to->changed = back->changed;
1572                 back->glyph = 0;
1573                 back->changed = 0;
1574               }
1575             }
1576           }
1577           switch (state->mode)
1578             {
1579             case DRAIN_TRACE_A: set_mode (state, TRACE_TEXT_A); break;
1580             case DRAIN_TRACE_B: set_mode (state, TRACE_TEXT_B); break;
1581             case DRAIN_KNOCK:   set_mode (state, KNOCK);        break;
1582             case DRAIN_NMAP:    set_mode (state, NMAP);         break;
1583             case DRAIN_MATRIX:  set_mode (state, state->def_mode); break;
1584             default:            abort();                        break;
1585             }
1586         }
1587       break;
1588
1589     case TRACE_DONE:
1590       set_mode (state, state->def_mode);
1591       break;
1592
1593     case TRACE_TEXT_A: 
1594     case TRACE_TEXT_B: 
1595     case TRACE_FAIL: 
1596     case KNOCK: 
1597     case NMAP:
1598       hack_text (state);
1599
1600       if (! state->typing)  /* done typing */
1601         {
1602           set_cursor (state, False);
1603           switch (state->mode)
1604             {
1605             case TRACE_TEXT_A: set_mode (state, TRACE_A); break;
1606             case TRACE_TEXT_B: set_mode (state, TRACE_B); break;
1607             case TRACE_FAIL:   set_mode (state, state->def_mode);  break;
1608             case KNOCK:        set_mode (state, state->def_mode);  break;
1609             case NMAP:         set_mode (state, state->def_mode);  break;
1610             default:           abort();                   break;
1611             }
1612         }
1613       break;
1614
1615     default:
1616       abort();
1617     }
1618   if (state->start_reveal_back_p){
1619     set_mode (state, DRAIN_MATRIX);
1620   }
1621   if (state->mode == MATRIX &&
1622       state->knock_knock_p &&
1623       (! (random() % 10000)))
1624     {
1625       if (! (random() % 5))
1626         set_mode (state, DRAIN_NMAP);
1627       else
1628         set_mode (state, DRAIN_KNOCK);
1629     }
1630
1631   redraw_cells (state, True);
1632
1633 #if 0
1634   {
1635     static int i = 0;
1636     static int ndens = 0;
1637     static int tdens = 0;
1638     i++;
1639     if (i > 50)
1640       {
1641         int dens = (100.0 *
1642                     (((double)count) /
1643                      ((double) (state->grid_width * state->grid_height))));
1644         tdens += dens;
1645         ndens++;
1646         printf ("density: %d%% (%d%%)\n", dens, (tdens / ndens));
1647         i = 0;
1648       }
1649   }
1650 #endif
1651
1652   return state->delay;
1653 }
1654
1655
1656 static void
1657 xmatrix_reshape (Display *dpy, Window window, void *closure, 
1658                  unsigned int w, unsigned int h)
1659 {
1660   m_state *state = (m_state *) closure;
1661   int ow = state->grid_width;
1662   int oh = state->grid_height;
1663   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
1664   state->grid_width  = state->xgwa.width  / state->char_width;
1665   state->grid_height = state->xgwa.height / state->char_height;
1666   state->grid_width++;
1667   state->grid_height++;
1668   if (state->grid_width  < 5) state->grid_width  = 5;
1669   if (state->grid_height < 5) state->grid_height = 5;
1670
1671   if (ow != state->grid_width ||
1672       oh != state->grid_height)
1673     {
1674       m_cell *ncells = (m_cell *)
1675         calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1676       m_cell *nbackground = (m_cell *)
1677         calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1678       m_feeder *nfeeders = (m_feeder *)
1679         calloc (sizeof(m_feeder), state->grid_width);
1680       int x, y, i;
1681
1682       /* fprintf(stderr, "resize: %d x %d  ==>  %d x %d\n",
1683          ow, oh, state->grid_width, state->grid_height); */
1684
1685       for (y = 0; y < oh; y++)
1686         for (x = 0; x < ow; x++)
1687           if (x < ow && x < state->grid_width &&
1688               y < oh && y < state->grid_height){
1689             ncells[y * state->grid_width + x] =
1690               state->cells[y * ow + x];
1691             nbackground[y * state->grid_width + x] =
1692               state->background[y * ow + x];
1693           }
1694       free (state->cells);
1695       free (state->background);
1696       state->cells = ncells;
1697       state->background = nbackground;
1698
1699       x = (ow < state->grid_width ? ow : state->grid_width);
1700       for (i = 0; i < x; i++)
1701         nfeeders[i] = state->feeders[i];
1702       free (state->feeders);
1703       state->feeders = nfeeders;
1704     }
1705   if (state->tc)
1706     textclient_reshape (state->tc,
1707                         state->xgwa.width,
1708                         state->xgwa.height,
1709                         state->grid_width  - 2,
1710                         state->grid_height - 1,
1711                         0);
1712 }
1713
1714 static Bool
1715 xmatrix_event (Display *dpy, Window window, void *closure, XEvent *event)
1716 {
1717   m_state *state = (m_state *) closure;
1718
1719  if (event->xany.type == KeyPress)
1720    {
1721      KeySym keysym;
1722      char c = 0;
1723      XLookupString (&event->xkey, &c, 1, &keysym, 0);
1724      switch (c)
1725        {
1726        case '0':
1727          /*set_mode (state, DRAIN_MATRIX);*/
1728          state->back_y = 1;
1729          state->back_text_full_p = True;
1730          state->start_reveal_back_p = True;
1731          return True;
1732
1733        case '+': case '=': case '>': case '.':
1734          state->density += 10;
1735          if (state->density > 100)
1736            state->density = 100;
1737          else
1738            return True;
1739          break;
1740
1741        case '-': case '_': case '<': case ',':
1742          state->density -= 10;
1743          if (state->density < 0)
1744            state->density = 0;
1745          else
1746            return True;
1747          break;
1748
1749        case '[': case '(': case '{':
1750          state->insert_top_p    = True;
1751          state->insert_bottom_p = False;
1752          return True;
1753
1754        case ']': case ')': case '}':
1755          state->insert_top_p    = False;
1756          state->insert_bottom_p = True;
1757          return True;
1758
1759        case '\\': case '|':
1760          state->insert_top_p    = True;
1761          state->insert_bottom_p = True;
1762          return True;
1763
1764        case 't':
1765          set_mode (state, DRAIN_TRACE_A);
1766          return True;
1767
1768        case 'T':
1769          set_mode (state, DRAIN_TRACE_B);
1770          return True;
1771
1772        case 'k':
1773          set_mode (state, DRAIN_KNOCK);
1774          return True;
1775
1776        case 'c':
1777          set_mode (state, DRAIN_NMAP);
1778          return True;
1779
1780        default:
1781          break;
1782        }
1783    }
1784
1785  if (screenhack_event_helper (dpy, window, event))
1786    {
1787      set_mode (state, DRAIN_MATRIX);
1788      return True;
1789    }
1790
1791   return False;
1792 }
1793
1794 static void
1795 xmatrix_free (Display *dpy, Window window, void *closure)
1796 {
1797   m_state *state = (m_state *) closure;
1798   if (state->tc)
1799     textclient_close (state->tc);
1800   if (state->cursor_timer)
1801     XtRemoveTimeOut (state->cursor_timer);
1802
1803   /* #### there's more to free here */
1804
1805   free (state);
1806 }
1807
1808 static const char *xmatrix_defaults [] = {
1809   ".background:            black",
1810   ".foreground:            #00AA00",
1811   ".lowrez:                true",  /* Small font is unreadable at 5120x2880 */
1812   "*fpsSolid:              true",
1813   "*matrixFont:            large",
1814   "*delay:                 10000",
1815   "*insert:                both",
1816   "*mode:                  Matrix",
1817   "*tracePhone:            (312) 555-0690",
1818   "*spinners:              5",
1819   "*density:               75",
1820   "*trace:                 True",
1821   "*knockKnock:            True",
1822   "*usePipe:               False",
1823   "*usePty:                False",
1824   "*program:               xscreensaver-text --latin1",
1825   "*geometry:              960x720",
1826   0
1827 };
1828
1829 static XrmOptionDescRec xmatrix_options [] = {
1830   { "-small",           ".matrixFont",          XrmoptionNoArg, "Small" },
1831   { "-large",           ".matrixFont",          XrmoptionNoArg, "Large" },
1832   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
1833   { "-insert",          ".insert",              XrmoptionSepArg, 0 },
1834   { "-top",             ".insert",              XrmoptionNoArg, "top" },
1835   { "-bottom",          ".insert",              XrmoptionNoArg, "bottom" },
1836   { "-both",            ".insert",              XrmoptionNoArg, "both" },
1837   { "-density",         ".density",             XrmoptionSepArg, 0 },
1838   { "-trace",           ".trace",               XrmoptionNoArg, "True" },
1839   { "-no-trace",        ".trace",               XrmoptionNoArg, "False" },
1840   { "-crack",           ".mode",                XrmoptionNoArg, "crack"},
1841   { "-phone",           ".tracePhone",          XrmoptionSepArg, 0 },
1842   { "-mode",            ".mode",                XrmoptionSepArg, 0 },
1843   { "-dna",             ".mode",                XrmoptionNoArg, "DNA" },
1844   { "-binary",          ".mode",                XrmoptionNoArg, "binary" },
1845   { "-hexadecimal",     ".mode",                XrmoptionNoArg, "hexadecimal"},
1846   { "-decimal",         ".mode",                XrmoptionNoArg, "decimal"},
1847   { "-knock-knock",     ".knockKnock",          XrmoptionNoArg, "True" },
1848   { "-no-knock-knock",  ".knockKnock",          XrmoptionNoArg, "False" },
1849   { "-ascii",           ".mode",                XrmoptionNoArg, "ascii"},
1850   { "-pipe",            ".usePipe",             XrmoptionNoArg, "True" },
1851   { "-no-pipe",         ".usePipe",             XrmoptionNoArg, "False" },
1852   { "-program",         ".program",             XrmoptionSepArg, 0 },
1853   { 0, 0, 0, 0 }
1854 };
1855
1856 XSCREENSAVER_MODULE ("XMatrix", xmatrix)