adccb0c66dd091a696bcd9a2e903c5fca6fa81a5
[xscreensaver] / hacks / xmatrix.c
1 /* xscreensaver, Copyright (c) 1999-2015 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_JWXYZ
63 # define HAVE_XPM
64 #else
65 # define DO_XBM     /* only do mono bitmaps under real X11 */
66 #endif
67
68 #ifndef HAVE_JWXYZ
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   const char *s2;
465   signed char *s3;
466   if (!s)
467     goto FAIL;
468
469   state->tracing = (signed char *) malloc (strlen (s) + 1);
470   s3 = 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 == state->tracing)
478     goto FAIL;
479
480   state->glyph_map = decimal_encoding;
481   state->nglyphs = countof(decimal_encoding);
482
483   return;
484
485  FAIL:
486   fprintf (stderr, "%s: bad phone number: \"%s\".\n",
487            progname, s ? s : "(null)");
488
489   if (s) free (s);
490   if (state->tracing) free (state->tracing);
491   state->tracing = 0;
492   set_mode (state, MATRIX);
493 }
494
495
496 static void
497 init_drain (m_state *state)
498 {
499   int i;
500
501   set_cursor (state, False);
502   state->cursor_x = -1;
503   state->cursor_y = -1;
504
505   /* Fill the top row with empty top-feeders, to clear the screen. */
506   for (i = 0; i < state->grid_width; i++)
507     {
508       m_feeder *f = &state->feeders[i];
509       f->y = -1;
510       f->remaining = 0;
511       f->throttle = 0;
512     }
513
514   /* Turn off all the spinners, else they never go away. */
515   clear_spinners (state);
516 }
517
518 static Bool
519 screen_blank_p (m_state *state)
520 {
521   int i;
522   for (i = 0; i < state->grid_width * state->grid_height; i++)
523     if (state->cells[i].glyph)
524       return False;
525   return True;
526 }
527
528
529 static void
530 set_mode (m_state *state, m_mode mode)
531 {
532   if (mode == state->mode)
533     return;
534
535   state->mode = mode;
536   state->typing = 0;
537
538   switch (mode)
539     {
540     case MATRIX:
541       state->glyph_map = matrix_encoding;
542       state->nglyphs = countof(matrix_encoding);
543       flip_images (state, True);
544       init_spinners (state);
545       break;
546     case DNA:
547       state->glyph_map = dna_encoding;
548       state->nglyphs = countof(dna_encoding);
549       flip_images (state, False);
550       break;
551     case BINARY:
552       state->glyph_map = binary_encoding;
553       state->nglyphs = countof(binary_encoding);
554       flip_images (state, False);
555       break;
556     case HEX:
557       state->glyph_map = hex_encoding;
558       state->nglyphs = countof(hex_encoding);
559       flip_images (state, False);
560       break;
561     case ASCII:
562       state->glyph_map = ascii_encoding;
563       state->nglyphs = countof(ascii_encoding);
564       flip_images (state, False);
565       break;
566     case DEC:
567     case TRACE_A:
568     case TRACE_B:
569     case NMAP:
570     case KNOCK:
571       state->glyph_map = decimal_encoding;
572       state->nglyphs = countof(decimal_encoding);
573       flip_images (state, False);
574       break;
575     case TRACE_TEXT_A: 
576     case TRACE_TEXT_B:
577       flip_images (state, False);
578       init_trace (state);
579       break;
580     case DRAIN_TRACE_A:
581     case DRAIN_TRACE_B:
582     case DRAIN_KNOCK:
583     case DRAIN_NMAP:
584     case DRAIN_MATRIX:
585       init_drain (state);
586       break;
587     case TRACE_DONE:
588     case TRACE_FAIL:
589       break;
590     default:
591       abort();
592     }
593 }
594
595
596 static void *
597 xmatrix_init (Display *dpy, Window window)
598 {
599   XGCValues gcv;
600   char *insert, *mode;
601   int i;
602   m_state *state = (m_state *) calloc (sizeof(*state), 1);
603
604   state->dpy = dpy;
605   state->window = window;
606   state->delay = get_integer_resource (dpy, "delay", "Integer");
607
608   XGetWindowAttributes (dpy, window, &state->xgwa);
609
610   state->small_p = (state->xgwa.width < 300);
611   {
612     const char *s = get_string_resource (dpy, "matrixFont", "String");
613     if (!s || !*s || !strcasecmp(s, "large"))
614       state->small_p = False;
615     else if (!strcasecmp(s, "small"))
616       state->small_p = True;
617     else
618       fprintf (stderr, "%s: matrixFont should be 'small' or 'large' not '%s'\n",
619                progname, s);
620   }
621
622   load_images (dpy, state);
623
624   gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
625                                       "foreground", "Foreground");
626   gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
627                                       "background", "Background");
628   state->draw_gc = XCreateGC (state->dpy, state->window,
629                               GCForeground|GCBackground, &gcv);
630   gcv.foreground = gcv.background;
631   state->erase_gc = XCreateGC (state->dpy, state->window,
632                                GCForeground|GCBackground, &gcv);
633
634   state->scratch_gc = XCreateGC (state->dpy, state->window, 0, &gcv);
635
636   /* Allocate colors for SYSTEM FAILURE box */
637   {
638     XColor boxcolors[] = {
639       { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
640       { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
641       { 0, 0xE0E0, 0xF7F7, 0xE0E0, DoRed|DoGreen|DoBlue, 0 },
642       { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
643       { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
644     };
645   for (i = 0; i < countof(boxcolors); i++)
646     {
647       if (XAllocColor (state->dpy, state->xgwa.colormap, &boxcolors[i]))
648         state->colors[i] = boxcolors[i].pixel;
649       else
650         state->colors[i] = gcv.foreground;  /* default black */
651     }
652   }
653
654   state->char_width =  state->image_width  / CHAR_COLS;
655   state->char_height = state->image_height / CHAR_ROWS;
656
657   state->grid_width  = state->xgwa.width  / state->char_width;
658   state->grid_height = state->xgwa.height / state->char_height;
659   state->grid_width++;
660   state->grid_height++;
661   if (state->grid_width  < 5) state->grid_width  = 5;
662   if (state->grid_height < 5) state->grid_height = 5;
663
664   state->glyph_map = matrix_encoding;
665   state->nglyphs = countof(matrix_encoding);
666
667   state->cells = (m_cell *)
668     calloc (sizeof(m_cell), state->grid_width * state->grid_height);
669   state->background = (m_cell *)
670     calloc (sizeof(m_cell), state->grid_width * state->grid_height);
671   state->feeders = (m_feeder *) calloc (sizeof(m_feeder), state->grid_width);
672
673   state->density = get_integer_resource (dpy, "density", "Integer");
674
675   insert = get_string_resource(dpy, "insert", "Insert");
676   if (insert && !strcmp(insert, "top"))
677     {
678       state->insert_top_p = True;
679       state->insert_bottom_p = False;
680     }
681   else if (insert && !strcmp(insert, "bottom"))
682     {
683       state->insert_top_p = False;
684       state->insert_bottom_p = True;
685     }
686   else if (insert && !strcmp(insert, "both"))
687     {
688       state->insert_top_p = True;
689       state->insert_bottom_p = True;
690     }
691   else
692     {
693       if (insert && *insert)
694         fprintf (stderr,
695                  "%s: `insert' must be `top', `bottom', or `both', not `%s'\n",
696                  progname, insert);
697       state->insert_top_p = False;
698       state->insert_bottom_p = True;
699     }
700
701   state->nspinners = get_integer_resource (dpy, "spinners", "Integer");
702
703   if (insert)
704     free (insert);
705
706   state->knock_knock_p = get_boolean_resource (dpy, "knockKnock", "KnockKnock");
707
708   state->use_pipe_p = get_boolean_resource (dpy, "usePipe", "Boolean");
709   state->buf_pos = 1;
710   state->buf[0] = ' '; /* spacer byte in buffer (space) */
711   state->buf_done = 0;
712   state->do_fill_buff = True;
713   state->start_reveal_back_p = False;
714   state->back_text_full_p = False;
715   state->back_y = 0;
716   state->back_pos = 0;
717
718   state->mode = -1;
719   state->def_mode = MATRIX;
720   mode = get_string_resource (dpy, "mode", "Mode");
721   if (mode && !strcasecmp(mode, "trace"))
722     set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
723   else if (mode && !strcasecmp(mode, "crack"))
724     set_mode (state, DRAIN_NMAP);
725   else if (mode && !strcasecmp(mode, "dna")){
726     set_mode (state, DNA);
727     state->def_mode = DNA;
728   }
729   else if (mode && (!strcasecmp(mode, "bin") ||
730                     !strcasecmp(mode, "binary"))){
731     set_mode (state, BINARY);
732     state->def_mode = BINARY;
733   }
734   else if (mode && (!strcasecmp(mode, "hex") ||
735                     !strcasecmp(mode, "hexadecimal"))){
736     set_mode (state, HEX);
737     state->def_mode = HEX;
738   }
739   else if (mode && (!strcasecmp(mode, "dec") ||
740                     !strcasecmp(mode, "decimal"))){
741     set_mode (state, DEC);
742     state->def_mode = DEC;
743   }
744   else if (mode && (!strcasecmp(mode, "asc") ||
745                     !strcasecmp(mode, "ascii"))){
746     set_mode (state, ASCII);
747     state->def_mode = ASCII;
748   }
749   else if (mode && !strcasecmp(mode, "pipe"))
750     {
751       set_mode (state, ASCII);
752       state->def_mode = ASCII;
753       state->use_pipe_p = True;
754       state->tc = textclient_open (dpy);
755     }
756   else if (!mode || !*mode || !strcasecmp(mode, "matrix"))
757     set_mode (state, MATRIX);
758   else
759     {
760       fprintf (stderr, "%s: `mode' must be ",progname);
761       fprintf (stderr, "matrix, trace, dna, binary, ascii, hex, or pipe: ");
762       fprintf (stderr, "not `%s'\n", mode);
763       set_mode (state, MATRIX);
764     }
765
766   if (state->mode == MATRIX && get_boolean_resource (dpy, "trace", "Boolean"))
767     set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
768
769   state->cursor_x = -1;
770   state->cursor_y = -1;
771
772   return state;
773 }
774
775
776 static void
777 insert_glyph (m_state *state, int glyph, int x, int y)
778 {
779   Bool bottom_feeder_p = (y >= 0);
780   m_cell *from, *to;
781   if (y >= state->grid_height)
782     return;
783
784   if (bottom_feeder_p)
785     {
786       to = &state->cells[state->grid_width * y + x];
787     }
788   else
789     {
790       for (y = state->grid_height-1; y > 0; y--)
791         {
792           from = &state->cells[state->grid_width * (y-1) + x];
793           to   = &state->cells[state->grid_width * y     + x];
794           to->glyph   = from->glyph;
795           to->glow    = from->glow;
796           to->changed = 1;
797         }
798       to = &state->cells[x];
799     }
800
801   to->glyph = glyph;
802   to->changed = 1;
803
804   if (!to->glyph)
805     ;
806   else if (bottom_feeder_p)
807     to->glow = 1 + (random() % (state->tracing ? 4 : 2));
808   else
809     to->glow = 0;
810 }
811
812
813 static void
814 place_back_char (m_state *state, char textc, int x, int y){
815   if((x>=0) && (y>=0) &&
816      (x < state->grid_width) && (y < state->grid_height)){
817     m_cell *celltmp = &state->background[state->grid_width * y + x];
818     celltmp -> glyph = char_map[(unsigned char)textc] + 1;
819     if(!celltmp->glyph || (celltmp->glyph == 3)){
820       celltmp -> glyph = char_map[32] + 1; 
821     }
822     celltmp -> changed = 1;
823   } 
824 }
825
826 static void
827 place_back_text (m_state *state, char *text, int x, int y){
828   int i;
829   for(i=0; i<strlen(text); i++){
830     place_back_char(state, text[i], x+i, y);
831   }
832 }
833
834 static void
835 place_back_pipe (m_state *state, char textc){
836   Bool new_line = False;
837   /* gringer pipe insert */
838   state->back_line[state->back_pos] = textc;
839   if(textc == '\n'){
840     state->back_line[state->back_pos] = '\0';
841     new_line = True;
842   }
843   else if ((state->back_pos > (state->grid_width - 4)) || 
844            (state->back_pos >= BUF_SIZE)){ /* off by 1? */
845     state->back_line[++state->back_pos] = '\0';
846     new_line = True;
847   }
848   else{
849     state->back_pos++;
850   }
851   if(new_line){
852     int startx = (state->grid_width >> 1) - 
853       (strlen(state->back_line) >> 1);
854     place_back_text(state, state->back_line, 
855                     startx, state->back_y);
856     state->back_pos = 0;
857     state->back_y++;
858     if(state->back_y >= (state->grid_height - 1)){
859       state->back_y = 1;
860       state->back_text_full_p = True;
861       state->start_reveal_back_p = True;
862     }
863   }
864 }
865
866 static void
867 feed_matrix (m_state *state)
868 {
869   int x;
870
871   switch (state->mode)
872     {
873     case TRACE_A:
874         {
875           int L = strlen((char *) state->tracing);
876           int count = 0;
877           int i;
878
879           for (i = 0; i < strlen((char *) state->tracing); i++)
880             if (state->tracing[i] > 0) 
881               count++;
882
883           if (count >= L)
884             {
885               set_mode (state, TRACE_DONE);
886               state->typing_delay = 1000000;
887               return;
888             }
889           else
890             {
891               i = 5 + (30 / (count+1)); /* how fast numbers are discovered */
892
893               if ((random() % i) == 0)
894                 {
895                   i = random() % L;
896                   if (state->tracing[i] < 0)
897                     state->tracing[i] = -state->tracing[i];
898                 }
899             }
900         }
901       break;
902
903     case TRACE_B:
904       if ((random() % 40) == 0)
905         {
906           set_mode (state, TRACE_FAIL);
907           return;
908         }
909       break;
910
911     case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII: 
912     case DRAIN_TRACE_A:
913     case DRAIN_TRACE_B:
914     case DRAIN_KNOCK:
915     case DRAIN_NMAP:
916     case DRAIN_MATRIX:
917       break;
918
919     default:
920       abort();
921     }
922
923   /*get input*/
924   if((state->use_pipe_p) && (!state->back_text_full_p)){
925     place_back_pipe(state, state->buf[state->buf_done]);
926     state->buf_done = (state->buf_done + 1) % BUF_SIZE;
927     if(state->buf_done == (state->buf_pos - 1)){
928       state->do_fill_buff = True;
929     }
930     }
931   if(state->buf_done == (state->buf_pos + 1)){
932     state->do_fill_buff = False;
933   }
934   else{
935     state->do_fill_buff = True;
936     fill_input(state);
937   }
938
939   /* Update according to current feeders. */
940   for (x = 0; x < state->grid_width; x++)
941     {
942       m_feeder *f = &state->feeders[x];
943
944       if (f->throttle)          /* this is a delay tick, synced to frame. */
945         {
946           f->throttle--;
947         }
948       else if (f->remaining > 0)        /* how many items are in the pipe */
949         {
950           int g;
951           long rval;
952           if((state->use_pipe_p) && (!state->back_text_full_p)){
953             rval = (int) state->buf[f->pipe_loc];
954             if(++f->pipe_loc > (BUF_SIZE-1)){
955               f->pipe_loc = 0;
956               /*fill_input(state);*/
957             }
958             rval = (rval % state->nglyphs);
959           }
960           else{
961             rval = (random() % state->nglyphs);
962           }
963           g = state->glyph_map[rval] + 1;
964           insert_glyph (state, g, x, f->y);
965           f->remaining--;
966           if (f->y >= 0)  /* bottom_feeder_p */
967             f->y++;
968         }
969       else                              /* if pipe is empty, insert spaces */
970         {
971           insert_glyph (state, 0, x, f->y);
972           if (f->y >= 0)  /* bottom_feeder_p */
973             f->y++;
974         }
975
976       if ((random() % 10) == 0)         /* randomly change throttle speed */
977         {
978           f->throttle = ((random() % 5) + (random() % 5));
979         }
980     }
981 }
982
983
984 static void
985 redraw_cells (m_state *state, Bool active)
986 {
987   int x, y;
988   int count = 0;
989   Bool use_back_p = False;
990
991   for (y = 0; y < state->grid_height; y++)
992     for (x = 0; x < state->grid_width; x++)
993       {
994         m_cell *cell = &state->cells[state->grid_width * y + x];
995         m_cell *back = &state->background[state->grid_width * y + x];
996         Bool cursor_p = (state->cursor_on &&
997                          x == state->cursor_x && 
998                          y == state->cursor_y);
999
1000         if (cell->glyph)
1001           count++;
1002         else {
1003           if((state->start_reveal_back_p) && 
1004              (back->glyph) && !(state->mode == TRACE_A || 
1005                                 state->mode == TRACE_B ||
1006                                 state->mode == TRACE_DONE)){
1007             use_back_p = True;
1008             cell = back;
1009           }
1010         }
1011
1012         /* In trace-mode, the state of each cell is random unless we have
1013            a match for this digit. */
1014         if (active && (state->mode == TRACE_A || 
1015                        state->mode == TRACE_B ||
1016                        state->mode == TRACE_DONE))
1017           {
1018             int xx = x % strlen((char *) state->tracing);
1019             Bool dead_p = state->tracing[xx] > 0;
1020
1021             if (y == 0 && x == xx && !use_back_p)
1022               cell->glyph = (dead_p
1023                              ? state->glyph_map[state->tracing[xx]-'0'] + 1
1024                              : 0);
1025             else if (y == 0 && !use_back_p)
1026               cell->glyph = 0;
1027             else if (!use_back_p)
1028               cell->glyph = (dead_p ? 0 :
1029                              (state->glyph_map[(random()%state->nglyphs)]
1030                               + 1));
1031             if (!use_back_p)
1032             cell->changed = 1;
1033           }
1034
1035         if (!cell->changed)
1036           continue;
1037
1038
1039         if (cell->glyph == 0 && !cursor_p && !use_back_p)
1040           XFillRectangle (state->dpy, state->window, state->erase_gc,
1041                           x * state->char_width,
1042                           y * state->char_height,
1043                           state->char_width,
1044                           state->char_height);
1045         else
1046           {
1047             int g = (cursor_p ? CURSOR_GLYPH : cell->glyph);
1048             int cx = (g - 1) % CHAR_COLS;
1049             int cy = (g - 1) / CHAR_COLS;
1050             int map = ((cell->glow != 0 || cell->spinner) ? GLOW_MAP :
1051                        PLAIN_MAP);
1052
1053             XCopyArea (state->dpy, state->images[map],
1054                        state->window, state->draw_gc,
1055                        cx * state->char_width,
1056                        cy * state->char_height,
1057                        state->char_width,
1058                        state->char_height,
1059                        x * state->char_width,
1060                        y * state->char_height);
1061           }
1062         if (!use_back_p)
1063         cell->changed = 0;
1064
1065         if (cell->glow > 0 && state->mode != NMAP && !use_back_p)
1066           {
1067             cell->glow--;
1068             cell->changed = 1;
1069           }
1070         else if (cell->glow < 0)
1071           abort();
1072
1073         if (cell->spinner && active && !use_back_p)
1074           {
1075             cell->glyph = (state->glyph_map[(random()%state->nglyphs)] + 1);
1076             cell->changed = 1;
1077           }
1078       }
1079 }
1080
1081
1082 static int
1083 densitizer (m_state *state)
1084 {
1085   /* Horrid kludge that converts percentages (density of screen coverage)
1086      to the parameter that actually controls this.  I got this mapping
1087      empirically, on a 1024x768 screen.  Sue me. */
1088   if      (state->density < 10) return 85;
1089   else if (state->density < 15) return 60;
1090   else if (state->density < 20) return 45;
1091   else if (state->density < 25) return 25;
1092   else if (state->density < 30) return 20;
1093   else if (state->density < 35) return 15;
1094   else if (state->density < 45) return 10;
1095   else if (state->density < 50) return 8;
1096   else if (state->density < 55) return 7;
1097   else if (state->density < 65) return 5;
1098   else if (state->density < 80) return 3;
1099   else if (state->density < 90) return 2;
1100   else return 1;
1101 }
1102
1103
1104 static void
1105 hack_text (m_state *state)
1106 {
1107   if (!state->typing)
1108     {
1109       set_cursor (state, False);
1110       state->cursor_x = 0;
1111       state->cursor_y = 0;
1112       state->typing_scroll_p = False;
1113       state->typing_bold_p = False;
1114       state->typing_cursor_p = True;
1115       state->typing_stutter_p = False;
1116       state->typing_char_delay = 10000;
1117       state->typing_line_delay = 1500000;
1118
1119       switch (state->mode)
1120         {
1121         case TRACE_TEXT_A:
1122         case TRACE_TEXT_B:
1123           clear_spinners (state);
1124           if (state->mode == TRACE_TEXT_A)
1125             {
1126               if (state->grid_width >= 52)
1127                 state->typing =
1128                   ("Call trans opt: received. 2-19-98 13:24:18 REC:Log>\n"
1129                    "Trace program: running\n");
1130               else
1131                 state->typing =
1132                   ("Call trans opt: received.\n2-19-98 13:24:18 REC:Log>\n"
1133                    "Trace program: running\n");
1134             }
1135           else
1136             {
1137               if (state->grid_width >= 52)
1138                 state->typing =
1139                   ("Call trans opt: received. 9-18-99 14:32:21 REC:Log>\n"
1140                    "WARNING: carrier anomaly\n"
1141                    "Trace program: running\n");
1142               else
1143                 state->typing =
1144                   ("Call trans opt: received.\n9-18-99 14:32:21 REC:Log>\n"
1145                    "WARNING: carrier anomaly\n"
1146                    "Trace program: running\n");
1147             }
1148           break;
1149
1150         case TRACE_FAIL:
1151           {
1152             const char *s = "SYSTEM FAILURE\n";
1153             int i;
1154             float cx = (state->grid_width - strlen(s) - 1) / 2 - 0.5;
1155             float cy = (state->grid_height / 2) - 1.3;
1156
1157             if (cy < 0) cy = 0;
1158             if (cx < 0) cx = 0;
1159
1160             XFillRectangle (state->dpy, state->window, state->erase_gc,
1161                             cx * state->char_width,
1162                             cy * state->char_height,
1163                             strlen(s) * state->char_width,
1164                             state->char_height * 1.6);
1165
1166             for (i = -2; i < 3; i++)
1167               {
1168                 XGCValues gcv;
1169                 gcv.foreground = state->colors[i + 2];
1170                 XChangeGC (state->dpy, state->scratch_gc, GCForeground, &gcv);
1171                 XDrawRectangle (state->dpy, state->window, state->scratch_gc,
1172                                 cx * state->char_width - i,
1173                                 cy * state->char_height - i,
1174                                 strlen(s) * state->char_width + (2 * i),
1175                                 (state->char_height * 1.6) + (2 * i));
1176               }
1177
1178             /* If we don't clear these, part of the box may get overwritten */
1179             for (i = 0; i < state->grid_height * state->grid_width; i++)
1180               {
1181                 m_cell *cell = &state->cells[i];
1182                 cell->changed = 0;
1183               }
1184
1185             state->cursor_x = (state->grid_width - strlen(s) - 1) / 2;
1186             state->cursor_y = (state->grid_height / 2) - 1;
1187             if (state->cursor_x < 0) state->cursor_x = 0;
1188             if (state->cursor_y < 0) state->cursor_y = 0;
1189
1190             state->typing = s;
1191             state->typing_char_delay = 0;
1192             state->typing_cursor_p = False;
1193           }
1194           break;
1195
1196         case KNOCK:
1197           {
1198             clear_spinners (state);
1199             state->typing = ("\001Wake up, Neo...\n"
1200                              "\001The Matrix has you...\n"
1201                              "\001Follow the white rabbit.\n"
1202                              "\n"
1203                              "Knock, knock, Neo.\n");
1204
1205             state->cursor_x = 4;
1206             state->cursor_y = 2;
1207             state->typing_char_delay = 0;
1208             state->typing_line_delay = 2000000;
1209           }
1210           break;
1211
1212         case NMAP:
1213           {
1214             /* Note that what Trinity is using here is moderately accurate:
1215                She runs nmap (http://www.insecure.org/nmap/) then breaks in
1216                with a (hypothetical) program called "sshnuke" that exploits
1217                the (very real) SSHv1 CRC32 compensation attack detector bug
1218                (http://staff.washington.edu/dittrich/misc/ssh-analysis.txt).
1219
1220                The command syntax of the power grid control software looks a
1221                lot like Cisco IOS to me.  (IOS is a descendant of VMS.)
1222             */
1223
1224             clear_spinners (state);
1225             state->typing =
1226 # ifdef __GNUC__
1227             __extension__  /* don't warn about "string length is greater than
1228                               the length ISO C89 compilers are required to
1229                               support"... */
1230
1231 # endif
1232               ("# "
1233                "\010\010\010\010"
1234                "\001nmap -v -sS -O 10.2.2.2\n"
1235                "Starting nmap V. 2.54BETA25\n"
1236                "\010\010\010\010\010\010\010\010\010\010"
1237                "Insufficient responses for TCP sequencing (3), OS detection"
1238                " may be less accurate\n"
1239                "Interesting ports on 10.2.2.2:\n"
1240                "(The 1539 ports scanned but not shown below are in state:"
1241                " closed)\n"
1242                "Port       state       service\n"
1243                "22/tcp     open        ssh\n"
1244                "\n"
1245                "No exact OS matches for host\n"
1246                "\n"
1247                "Nmap run completed -- 1 IP address (1 host up) scanned\n"
1248                "# "
1249
1250                "\010\010\010\010"
1251                "\001sshnuke 10.2.2.2 -rootpw=\"Z1ON0101\"\n"
1252                "Connecting to 10.2.2.2:ssh ... "
1253                "\010\010"
1254                "successful.\n"
1255                "Attempting to exploit SSHv1 CRC32 ... "
1256                "\010\010\010\010"
1257                "successful.\n"
1258                "Resetting root password to \"Z1ON0101\".\n"
1259                "\010\010"
1260                "System open: Access Level <9>\n"
1261
1262                "# "
1263                "\010\010"
1264
1265                "\001ssh 10.2.2.2 -l root\n"
1266                "\010\010"
1267                "root@10.2.2.2's password: "
1268                "\010\010\n"
1269                "\010\010\n"
1270                "RRF-CONTROL> "
1271
1272                "\010\010"
1273                "\001disable grid nodes 21 - 48\n"
1274                "\n"
1275                "\002Warning: Disabling nodes 21-48 will disconnect sector 11"
1276                " (28 nodes)\n"
1277                "\n"
1278                "\002         ARE YOU SURE? (y/n) "
1279
1280                "\010\010"
1281                "\001y\n"
1282                "\n"
1283                "\n"
1284                "\010\002Grid Node 21 offline...\n"
1285                "\010\002Grid Node 22 offline...\n"
1286                "\010\002Grid Node 23 offline...\n"
1287                "\010\002Grid Node 24 offline...\n"
1288                "\010\002Grid Node 25 offline...\n"
1289                "\010\002Grid Node 26 offline...\n"
1290                "\010\002Grid Node 27 offline...\n"
1291                "\010\002Grid Node 28 offline...\n"
1292                "\010\002Grid Node 29 offline...\n"
1293                "\010\002Grid Node 30 offline...\n"
1294                "\010\002Grid Node 31 offline...\n"
1295                "\010\002Grid Node 32 offline...\n"
1296                "\010\002Grid Node 33 offline...\n"
1297                "\010\002Grid Node 34 offline...\n"
1298                "\010\002Grid Node 35 offline...\n"
1299                "\010\002Grid Node 36 offline...\n"
1300                "\010\002Grid Node 37 offline...\n"
1301                "\010\002Grid Node 38 offline...\n"
1302                "\010\002Grid Node 39 offline...\n"
1303                "\010\002Grid Node 40 offline...\n"
1304                "\010\002Grid Node 41 offline...\n"
1305                "\010\002Grid Node 42 offline...\n"
1306                "\010\002Grid Node 43 offline...\n"
1307                "\010\002Grid Node 44 offline...\n"
1308                "\010\002Grid Node 45 offline...\n"
1309                "\010\002Grid Node 46 offline...\n"
1310                "\010\002Grid Node 47 offline...\n"
1311                "\010\002Grid Node 48 offline...\n"
1312                "\010\010"
1313                "\nRRF-CONTROL> "
1314                "\010\010\010\010\010\010\010\010"
1315                );
1316
1317             state->cursor_x = 0;
1318             state->cursor_y = state->grid_height - 3;
1319             state->typing_scroll_p = True;
1320             state->typing_char_delay = 0;
1321             state->typing_line_delay = 20000;
1322           }
1323           break;
1324
1325         default:
1326           abort();
1327           break;
1328         }
1329
1330       state->typing_left_margin = state->cursor_x;
1331       state->typing_delay = state->typing_char_delay;
1332       if (state->typing_cursor_p)
1333         set_cursor (state, True);
1334     }
1335   else
1336     {
1337       Bool scrolled_p = False;
1338       unsigned char c, c1;
1339       int x = state->cursor_x;
1340       int y = state->cursor_y;
1341
1342     AGAIN:
1343       c  = ((unsigned char *) state->typing)[0];
1344       c1 = c ? ((unsigned char *) state->typing)[1] : 0;
1345
1346       state->typing_delay = (!c || c1 == '\n'
1347                              ? state->typing_line_delay
1348                              : state->typing_char_delay);
1349       if (! c)
1350         {
1351           state->typing_delay = 0;
1352           state->typing = 0;
1353           return;
1354         }
1355
1356       if (state->typing_scroll_p &&
1357           (c == '\n' || 
1358            x >= state->grid_width - 1))
1359         {
1360           set_cursor (state, False);
1361           x = 0;
1362           y++;
1363
1364           if (y >= state->grid_height-1)
1365             {
1366               int xx, yy;
1367               for (yy = 0; yy < state->grid_height-2; yy++)
1368                 for (xx = 0; xx < state->grid_width; xx++)
1369                   {
1370                     int ii = yy     * state->grid_width + xx;
1371                     int jj = (yy+1) * state->grid_width + xx;
1372                     state->cells[ii] = state->cells[jj];
1373                     state->cells[ii].changed = 1;
1374                   }
1375               /* clear bottom row */
1376               for (xx = 0; xx < state->grid_width; xx++)
1377                 {
1378                   int ii = yy * state->grid_width + xx;
1379                   state->cells[ii].glyph   = 0;
1380                   state->cells[ii].changed = 1;
1381                 }
1382               y--;  /* move back up to bottom line */
1383               scrolled_p = True;
1384             }
1385         }
1386
1387       if (c == '\n')
1388         {
1389           if (!state->typing_scroll_p)
1390             {
1391               int i, j;
1392               set_cursor (state, False);
1393               x = state->typing_left_margin;
1394
1395               /* clear the line */
1396               i = state->grid_width * y;
1397               j = i + state->grid_width;
1398               for (; i < j; i++)
1399                 {
1400                   state->cells[i].glyph   = 0;
1401                   state->cells[i].changed = 1;
1402                 }
1403             }
1404           state->typing_bold_p = False;
1405           state->typing_stutter_p = False;
1406           scrolled_p = True;
1407         }
1408
1409       else if (c == '\010')
1410         state->typing_delay += 500000;
1411
1412       else if (c == '\001')
1413         {
1414           state->typing_stutter_p = True;
1415           state->typing_bold_p = False;
1416         }
1417       else if (c == '\002')
1418         state->typing_bold_p = True;
1419
1420       else if (x < state->grid_width-1)
1421         {
1422           m_cell *cell = &state->cells[state->grid_width * y + x];
1423           cell->glyph = char_map[c] + 1;
1424           if (c == ' ' || c == '\t') cell->glyph = 0;
1425           cell->changed = 1;
1426           cell->glow = (state->typing_bold_p ? 127 : 0);
1427         }
1428
1429       if (c >= ' ')
1430         x++;
1431
1432       if (x >= state->grid_width-1)
1433         x = state->grid_width-1;
1434
1435       state->typing++;
1436
1437       if (state->typing_stutter_p)
1438         {
1439           if (state->typing_delay == 0)
1440             state->typing_delay = 20000;
1441           if (random() % 3)
1442             state->typing_delay += (0xFFFFFF & ((random() % 200000) + 1));
1443         }
1444
1445       /* If there's no delay after this character, just keep going. */
1446       if (state->typing_delay == 0)
1447         goto AGAIN;
1448
1449       if (scrolled_p || x != state->cursor_x || y != state->cursor_y)
1450         {
1451           set_cursor (state, False);
1452           state->cursor_x = x;
1453           state->cursor_y = y;
1454           if (state->typing_cursor_p)
1455             set_cursor (state, True);
1456         }
1457     }
1458 }
1459
1460
1461 static void
1462 hack_matrix (m_state *state)
1463 {
1464   int x;
1465
1466   switch (state->mode)
1467     {
1468     case TRACE_DONE: case TRACE_FAIL:
1469       return;
1470     case TRACE_A: case TRACE_B:
1471     case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1472       break;
1473     default:
1474       abort(); break;
1475     }
1476
1477   /* Glow some characters. */
1478   if (!state->insert_bottom_p)
1479     {
1480       int i = random() % (state->grid_width / 2);
1481       while (--i > 0)
1482         {
1483           int yy = random() % state->grid_height;
1484           int xx = random() % state->grid_width;
1485           m_cell *cell = &state->cells[state->grid_width * yy + xx];
1486           if (cell->glyph && cell->glow == 0)
1487             {
1488               cell->glow = random() % 10;
1489               cell->changed = 1;
1490             }
1491         }
1492     }
1493
1494   /* Change some of the feeders. */
1495   for (x = 0; x < state->grid_width; x++)
1496     {
1497       m_feeder *f = &state->feeders[x];
1498       Bool bottom_feeder_p;
1499
1500       if (f->remaining > 0)     /* never change if pipe isn't empty */
1501         continue;
1502
1503       if ((random() % densitizer(state)) != 0) /* then change N% of the time */
1504         continue;
1505
1506       f->remaining = 3 + (random() % state->grid_height);
1507       f->throttle = ((random() % 5) + (random() % 5));
1508
1509       if ((random() % 4) != 0)
1510         f->remaining = 0;
1511
1512       if (state->mode == TRACE_A || state->mode == TRACE_B)
1513         bottom_feeder_p = True;
1514       else if (state->insert_top_p && state->insert_bottom_p)
1515         bottom_feeder_p = (random() & 1);
1516       else
1517         bottom_feeder_p = state->insert_bottom_p;
1518
1519       if (bottom_feeder_p)
1520         f->y = random() % (state->grid_height / 2);
1521       else
1522         f->y = -1;
1523     }
1524
1525   if (state->mode == MATRIX && (! (random() % 500)))
1526     init_spinners (state);
1527 }
1528
1529
1530 static unsigned long
1531 xmatrix_draw (Display *dpy, Window window, void *closure)
1532 {
1533   m_state *state = (m_state *) closure;
1534
1535   if (state->typing_delay > 0)
1536     {
1537       state->typing_delay -= state->delay;
1538       if (state->typing_delay < 0)
1539         state->typing_delay = 0;
1540       redraw_cells (state, False);
1541       return state->delay;
1542     }
1543
1544   switch (state->mode)
1545     {
1546     case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1547     case TRACE_A: case TRACE_B:
1548       feed_matrix (state);
1549       hack_matrix (state);
1550       break;
1551
1552     case DRAIN_TRACE_A:
1553     case DRAIN_TRACE_B:
1554     case DRAIN_KNOCK:
1555     case DRAIN_NMAP:
1556     case DRAIN_MATRIX:
1557       feed_matrix (state);
1558       if (screen_blank_p (state))
1559         {
1560           state->typing_delay = 500000;
1561           if(state->start_reveal_back_p){
1562             m_cell *back, *to;
1563             int x,y;
1564             state->typing_delay = 5000000;
1565             state->start_reveal_back_p = False;
1566             state->back_text_full_p = False;
1567             /* for loop to move background to foreground */
1568             for (y = 0; y < state->grid_height; y++){
1569               for (x = 0; x < state->grid_width; x++){
1570                 to = &state->cells[state->grid_width * y + x];
1571                 back = &state->background[state->grid_width * y + x];
1572                 to->glyph = back->glyph;
1573                 to->changed = back->changed;
1574                 back->glyph = 0;
1575                 back->changed = 0;
1576               }
1577             }
1578           }
1579           switch (state->mode)
1580             {
1581             case DRAIN_TRACE_A: set_mode (state, TRACE_TEXT_A); break;
1582             case DRAIN_TRACE_B: set_mode (state, TRACE_TEXT_B); break;
1583             case DRAIN_KNOCK:   set_mode (state, KNOCK);        break;
1584             case DRAIN_NMAP:    set_mode (state, NMAP);         break;
1585             case DRAIN_MATRIX:  set_mode (state, state->def_mode); break;
1586             default:            abort();                        break;
1587             }
1588         }
1589       break;
1590
1591     case TRACE_DONE:
1592       set_mode (state, state->def_mode);
1593       break;
1594
1595     case TRACE_TEXT_A: 
1596     case TRACE_TEXT_B: 
1597     case TRACE_FAIL: 
1598     case KNOCK: 
1599     case NMAP:
1600       hack_text (state);
1601
1602       if (! state->typing)  /* done typing */
1603         {
1604           set_cursor (state, False);
1605           switch (state->mode)
1606             {
1607             case TRACE_TEXT_A: set_mode (state, TRACE_A); break;
1608             case TRACE_TEXT_B: set_mode (state, TRACE_B); break;
1609             case TRACE_FAIL:   set_mode (state, state->def_mode);  break;
1610             case KNOCK:        set_mode (state, state->def_mode);  break;
1611             case NMAP:         set_mode (state, state->def_mode);  break;
1612             default:           abort();                   break;
1613             }
1614         }
1615       break;
1616
1617     default:
1618       abort();
1619     }
1620   if (state->start_reveal_back_p){
1621     set_mode (state, DRAIN_MATRIX);
1622   }
1623   if (state->mode == MATRIX &&
1624       state->knock_knock_p &&
1625       (! (random() % 10000)))
1626     {
1627       if (! (random() % 5))
1628         set_mode (state, DRAIN_NMAP);
1629       else
1630         set_mode (state, DRAIN_KNOCK);
1631     }
1632
1633   redraw_cells (state, True);
1634
1635 #if 0
1636   {
1637     static int i = 0;
1638     static int ndens = 0;
1639     static int tdens = 0;
1640     i++;
1641     if (i > 50)
1642       {
1643         int dens = (100.0 *
1644                     (((double)count) /
1645                      ((double) (state->grid_width * state->grid_height))));
1646         tdens += dens;
1647         ndens++;
1648         printf ("density: %d%% (%d%%)\n", dens, (tdens / ndens));
1649         i = 0;
1650       }
1651   }
1652 #endif
1653
1654   return state->delay;
1655 }
1656
1657
1658 static void
1659 xmatrix_reshape (Display *dpy, Window window, void *closure, 
1660                  unsigned int w, unsigned int h)
1661 {
1662   m_state *state = (m_state *) closure;
1663   int ow = state->grid_width;
1664   int oh = state->grid_height;
1665   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
1666   state->grid_width  = state->xgwa.width  / state->char_width;
1667   state->grid_height = state->xgwa.height / state->char_height;
1668   state->grid_width++;
1669   state->grid_height++;
1670   if (state->grid_width  < 5) state->grid_width  = 5;
1671   if (state->grid_height < 5) state->grid_height = 5;
1672
1673   if (ow != state->grid_width ||
1674       oh != state->grid_height)
1675     {
1676       m_cell *ncells = (m_cell *)
1677         calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1678       m_cell *nbackground = (m_cell *)
1679         calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1680       m_feeder *nfeeders = (m_feeder *)
1681         calloc (sizeof(m_feeder), state->grid_width);
1682       int x, y, i;
1683
1684       /* fprintf(stderr, "resize: %d x %d  ==>  %d x %d\n",
1685          ow, oh, state->grid_width, state->grid_height); */
1686
1687       for (y = 0; y < oh; y++)
1688         for (x = 0; x < ow; x++)
1689           if (x < ow && x < state->grid_width &&
1690               y < oh && y < state->grid_height){
1691             ncells[y * state->grid_width + x] =
1692               state->cells[y * ow + x];
1693             nbackground[y * state->grid_width + x] =
1694               state->background[y * ow + x];
1695           }
1696       free (state->cells);
1697       free (state->background);
1698       state->cells = ncells;
1699       state->background = nbackground;
1700
1701       x = (ow < state->grid_width ? ow : state->grid_width);
1702       for (i = 0; i < x; i++)
1703         nfeeders[i] = state->feeders[i];
1704       free (state->feeders);
1705       state->feeders = nfeeders;
1706     }
1707   if (state->tc)
1708     textclient_reshape (state->tc,
1709                         state->xgwa.width,
1710                         state->xgwa.height,
1711                         state->grid_width  - 2,
1712                         state->grid_height - 1,
1713                         0);
1714 }
1715
1716 static Bool
1717 xmatrix_event (Display *dpy, Window window, void *closure, XEvent *event)
1718 {
1719   m_state *state = (m_state *) closure;
1720
1721  if (event->xany.type == KeyPress)
1722    {
1723      KeySym keysym;
1724      char c = 0;
1725      XLookupString (&event->xkey, &c, 1, &keysym, 0);
1726      switch (c)
1727        {
1728        case '0':
1729          /*set_mode (state, DRAIN_MATRIX);*/
1730          state->back_y = 1;
1731          state->back_text_full_p = True;
1732          state->start_reveal_back_p = True;
1733          return True;
1734
1735        case '+': case '=': case '>': case '.':
1736          state->density += 10;
1737          if (state->density > 100)
1738            state->density = 100;
1739          else
1740            return True;
1741          break;
1742
1743        case '-': case '_': case '<': case ',':
1744          state->density -= 10;
1745          if (state->density < 0)
1746            state->density = 0;
1747          else
1748            return True;
1749          break;
1750
1751        case '[': case '(': case '{':
1752          state->insert_top_p    = True;
1753          state->insert_bottom_p = False;
1754          return True;
1755
1756        case ']': case ')': case '}':
1757          state->insert_top_p    = False;
1758          state->insert_bottom_p = True;
1759          return True;
1760
1761        case '\\': case '|':
1762          state->insert_top_p    = True;
1763          state->insert_bottom_p = True;
1764          return True;
1765
1766        case 't':
1767          set_mode (state, DRAIN_TRACE_A);
1768          return True;
1769
1770        case 'T':
1771          set_mode (state, DRAIN_TRACE_B);
1772          return True;
1773
1774        case 'k':
1775          set_mode (state, DRAIN_KNOCK);
1776          return True;
1777
1778        case 'c':
1779          set_mode (state, DRAIN_NMAP);
1780          return True;
1781
1782        default:
1783          break;
1784        }
1785    }
1786
1787  if (screenhack_event_helper (dpy, window, event))
1788    {
1789      set_mode (state, DRAIN_MATRIX);
1790      return True;
1791    }
1792
1793   return False;
1794 }
1795
1796 static void
1797 xmatrix_free (Display *dpy, Window window, void *closure)
1798 {
1799   m_state *state = (m_state *) closure;
1800   if (state->tc)
1801     textclient_close (state->tc);
1802   if (state->cursor_timer)
1803     XtRemoveTimeOut (state->cursor_timer);
1804
1805   /* #### there's more to free here */
1806
1807   free (state);
1808 }
1809
1810 static const char *xmatrix_defaults [] = {
1811   ".background:            black",
1812   ".foreground:            #00AA00",
1813   "*fpsSolid:              true",
1814   "*matrixFont:            large",
1815   "*delay:                 10000",
1816   "*insert:                both",
1817   "*mode:                  Matrix",
1818   "*tracePhone:            (312) 555-0690",
1819   "*spinners:              5",
1820   "*density:               75",
1821   "*trace:                 True",
1822   "*knockKnock:            True",
1823   "*usePipe:               False",
1824   "*usePty:                False",
1825   "*program:               xscreensaver-text --latin1",
1826   "*geometry:              960x720",
1827   0
1828 };
1829
1830 static XrmOptionDescRec xmatrix_options [] = {
1831   { "-small",           ".matrixFont",          XrmoptionNoArg, "Small" },
1832   { "-large",           ".matrixFont",          XrmoptionNoArg, "Large" },
1833   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
1834   { "-insert",          ".insert",              XrmoptionSepArg, 0 },
1835   { "-top",             ".insert",              XrmoptionNoArg, "top" },
1836   { "-bottom",          ".insert",              XrmoptionNoArg, "bottom" },
1837   { "-both",            ".insert",              XrmoptionNoArg, "both" },
1838   { "-density",         ".density",             XrmoptionSepArg, 0 },
1839   { "-trace",           ".trace",               XrmoptionNoArg, "True" },
1840   { "-no-trace",        ".trace",               XrmoptionNoArg, "False" },
1841   { "-crack",           ".mode",                XrmoptionNoArg, "crack"},
1842   { "-phone",           ".tracePhone",          XrmoptionSepArg, 0 },
1843   { "-mode",            ".mode",                XrmoptionSepArg, 0 },
1844   { "-dna",             ".mode",                XrmoptionNoArg, "DNA" },
1845   { "-binary",          ".mode",                XrmoptionNoArg, "binary" },
1846   { "-hexadecimal",     ".mode",                XrmoptionNoArg, "hexadecimal"},
1847   { "-decimal",         ".mode",                XrmoptionNoArg, "decimal"},
1848   { "-knock-knock",     ".knockKnock",          XrmoptionNoArg, "True" },
1849   { "-no-knock-knock",  ".knockKnock",          XrmoptionNoArg, "False" },
1850   { "-ascii",           ".mode",                XrmoptionNoArg, "ascii"},
1851   { "-pipe",            ".usePipe",             XrmoptionNoArg, "True" },
1852   { "-no-pipe",         ".usePipe",             XrmoptionNoArg, "False" },
1853   { "-program",         ".program",             XrmoptionSepArg, 0 },
1854   { 0, 0, 0, 0 }
1855 };
1856
1857 XSCREENSAVER_MODULE ("XMatrix", xmatrix)