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