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