1 /* xscreensaver, Copyright (c) 1999, 2001 Jamie Zawinski <jwz@jwz.org>
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
11 * Matrix -- simulate the text scrolls from the movie "The Matrix".
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
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.
23 * ==========================================================
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.
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...
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.
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.
48 * ==========================================================
56 #include "screenhack.h"
57 #include "xpm-pixmap.h"
64 # define DO_XBM /* only do mono bitmaps under real X11 */
67 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
68 # include "images/matrix1.xpm"
69 # include "images/matrix2.xpm"
70 # include "images/matrix1b.xpm"
71 # include "images/matrix2b.xpm"
75 # include "images/matrix1.xbm"
76 # include "images/matrix2.xbm"
77 # include "images/matrix1b.xbm"
78 # include "images/matrix2b.xbm"
82 # include <X11/Xatom.h>
83 # include <X11/Intrinsic.h>
87 # include <sys/ioctl.h>
94 #endif /* HAVE_FORKPTY */
104 unsigned int glyph : 9; /* note: 9 bit characters! */
105 unsigned int changed : 1;
106 unsigned int spinner : 1;
116 #define countof(x) (sizeof(x)/sizeof(*(x)))
118 static const int matrix_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
119 192, 193, 194, 195, 196, 197, 198, 199,
120 200, 201, 202, 203, 204, 205, 206, 207 };
121 static const int decimal_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
122 static const int hex_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
123 33, 34, 35, 36, 37, 38 };
124 static const int binary_encoding[] = { 16, 17 };
125 static const int dna_encoding[] = { 33, 35, 39, 52 };
126 static const int ascii_encoding[] = {
127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
128 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
130 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
131 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
132 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
133 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
134 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
135 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
136 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
138 static const unsigned char char_map[256] = {
139 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0 */
140 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 16 */
141 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 32 */
142 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 48 */
143 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 64 */
144 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 80 */
145 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 96 */
146 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 112 */
147 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 128 */
148 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 144 */
149 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, /* 160 */
150 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* 176 */
151 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 192 */
152 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, /* 208 */
153 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, /* 224 */
154 176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191 /* 240 */
157 #define CURSOR_GLYPH 97
159 /* larger numbers should mean more variability between columns */
162 typedef enum { DRAIN_TRACE_A,
163 TRACE_TEXT_A, /* Call trans opt: received. */
164 TRACE_A, /* (31_) 5__-0_9_ */
168 TRACE_TEXT_B, /* Call trans opt: received. */
170 TRACE_FAIL, /* System Failure */
173 KNOCK, /* Wake up, Neo... */
176 NMAP, /* Starting nmap V. 2.54BETA25 */
189 XWindowAttributes xgwa;
190 GC draw_gc, erase_gc, scratch_gc;
191 int grid_width, grid_height;
192 int char_width, char_height;
198 Bool insert_top_p, insert_bottom_p;
205 Bool input_available_p;
206 Time subproc_relaunch_delay;
207 char buf [BUF_SIZE*2+1]; /* twice because this is a ring buffer */
213 signed char *tracing;
217 Bool typing_scroll_p;
218 Bool typing_cursor_p;
220 Bool typing_stutter_p;
221 int typing_left_margin;
222 int typing_char_delay;
223 int typing_line_delay;
227 int cursor_x, cursor_y;
228 XtIntervalId cursor_timer;
230 Pixmap images[CHAR_MAPS];
231 int image_width, image_height;
232 Bool images_flipped_p;
235 const int *glyph_map;
237 unsigned long colors[5];
243 load_images_1 (Display *dpy, m_state *state, int which)
245 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
246 if (!get_boolean_resource (dpy, "mono", "Boolean") &&
247 state->xgwa.depth > 1)
250 (which == 1 ? (state->small_p ? matrix1b_xpm : matrix1_xpm) :
251 (state->small_p ? matrix2b_xpm : matrix2_xpm));
253 state->images[which] =
254 xpm_data_to_pixmap (state->dpy, state->window, bits,
255 &state->image_width, &state->image_height, 0);
258 #endif /* !HAVE_XPM && !HAVE_GDK_PIXBUF */
261 unsigned long fg, bg;
262 state->image_width = (state->small_p ? matrix1b_width :matrix1_width);
263 state->image_height = (state->small_p ? matrix1b_height:matrix1_height);
264 fg = get_pixel_resource(state->dpy, state->xgwa.colormap,
265 "foreground", "Foreground");
266 bg = get_pixel_resource(state->dpy, state->xgwa.colormap,
267 "background", "Background");
268 state->images[which] =
269 XCreatePixmapFromBitmapData (state->dpy, state->window, (char *)
270 (which == 1 ? (state->small_p ? matrix1b_bits :matrix1_bits) :
271 (state->small_p ? matrix2b_bits :matrix2_bits)),
272 state->image_width, state->image_height,
273 bg, fg, state->xgwa.depth);
282 load_images (Display *dpy, m_state *state)
284 load_images_1 (dpy, state, 1);
285 load_images_1 (dpy, state, 2);
290 flip_images_1 (m_state *state, int which)
292 XImage *im = XGetImage (state->dpy, state->images[which], 0, 0,
293 state->image_width, state->image_height,
294 ~0L, (state->xgwa.depth > 1 ? ZPixmap : XYPixmap));
296 int ww = state->char_width;
297 unsigned long *row = (unsigned long *) malloc (sizeof(*row) * ww);
299 for (y = 0; y < state->image_height; y++)
301 for (x = 0; x < CHAR_COLS; x++)
303 for (xx = 0; xx < ww; xx++)
304 row[xx] = XGetPixel (im, (x * ww) + xx, y);
305 for (xx = 0; xx < ww; xx++)
306 XPutPixel (im, (x * ww) + xx, y, row[ww - xx - 1]);
310 XPutImage (state->dpy, state->images[which], state->draw_gc, im, 0, 0, 0, 0,
311 state->image_width, state->image_height);
317 flip_images (m_state *state, Bool flipped_p)
319 if (flipped_p != state->images_flipped_p)
321 state->images_flipped_p = flipped_p;
322 flip_images_1 (state, 1);
323 flip_images_1 (state, 2);
328 subproc_cb (XtPointer closure, int *source, XtInputId *id)
330 m_state *state = (m_state *) closure;
331 state->input_available_p = True;
335 launch_text_generator (m_state *state)
337 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
338 char *oprogram = get_string_resource (state->dpy, "program", "Program");
339 char *program = (char *) malloc (strlen (oprogram) + 10);
340 strcpy (program, "( ");
341 strcat (program, oprogram);
342 strcat (program, " ) 2>&1");
344 if ((state->pipe = popen (program, "r")))
347 XtAppAddInput (app, fileno (state->pipe),
348 (XtPointer) (XtInputReadMask | XtInputExceptMask),
349 subproc_cb, (XtPointer) state);
359 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
361 m_state *state = (m_state *) closure;
362 launch_text_generator (state);
365 /* When the subprocess has generated some output, this reads as much as it
366 can into s->buf at s->buf_tail.
370 fill_input (m_state *s)
372 XtAppContext app = XtDisplayToApplicationContext (s->dpy);
375 if (! s->pipe) return;
376 if (! s->input_available_p) return;
377 s->input_available_p = False;
379 n = read (fileno (s->pipe),
380 (void *) (s->buf+s->buf_pos),
385 /* if one read wasn't enough to fill the whole buffer, then
386 read again. This might need some work if it's too intensive. */
387 s->do_fill_buff = False;
389 s->buf_pos = (s->buf_pos + n);
390 if(s->buf_pos > BUF_SIZE){
391 /* just in case areas overlap */
392 memmove(s->buf,s->buf+BUF_SIZE,s->buf_pos-BUF_SIZE);
394 s->buf_pos = s->buf_pos % BUF_SIZE;
395 /* s->input_available_p = True;*/
399 s->do_fill_buff = True;
400 XtRemoveInput (s->pipe_id);
405 /* Set up a timer to re-launch the subproc in a bit. */
406 XtAppAddTimeOut (app, s->subproc_relaunch_delay,
407 relaunch_generator_timer,
414 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
415 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
418 set_cursor_1 (m_state *state, Bool on)
420 Bool changed = (state->cursor_on != on);
421 state->cursor_on = on;
423 if (changed && state->cursor_x >= 0 && state->cursor_y >= 0)
425 m_cell *cell = &state->cells[state->grid_width * state->cursor_y
428 cell->changed = True;
435 set_cursor (m_state *state, Bool on)
437 if (set_cursor_1 (state, on))
439 if (state->cursor_timer)
440 XtRemoveTimeOut (state->cursor_timer);
441 state->cursor_timer = 0;
443 cursor_on_timer (state, 0);
448 cursor_off_timer (XtPointer closure, XtIntervalId *id)
450 m_state *state = (m_state *) closure;
451 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
452 set_cursor_1 (state, False);
453 state->cursor_timer = XtAppAddTimeOut (app, 333,
454 cursor_on_timer, closure);
458 cursor_on_timer (XtPointer closure, XtIntervalId *id)
460 m_state *state = (m_state *) closure;
461 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
462 set_cursor_1 (state, True);
463 state->cursor_timer = XtAppAddTimeOut (app, 666,
464 cursor_off_timer, closure);
469 init_spinners (m_state *state)
471 int i = state->nspinners;
475 for (y = 0; y < state->grid_height; y++)
476 for (x = 0; x < state->grid_width; x++)
478 cell = &state->cells[state->grid_width * y + x];
484 x = random() % state->grid_width;
485 y = random() % state->grid_height;
486 cell = &state->cells[state->grid_width * y + x];
492 static void set_mode (m_state *, m_mode);
496 init_trace (m_state *state)
498 char *s = get_string_resource (state->dpy, "tracePhone", "TracePhone");
504 state->tracing = (signed char *) malloc (strlen (s) + 1);
505 s3 = (char *) state->tracing;
507 for (s2 = s; *s2; s2++)
508 if (*s2 >= '0' && *s2 <= '9')
512 if (s3 == (char *) state->tracing)
515 for (i = 0; i < strlen((char *) state->tracing); i++)
516 state->tracing[i] = -state->tracing[i];
518 state->glyph_map = decimal_encoding;
519 state->nglyphs = countof(decimal_encoding);
524 fprintf (stderr, "%s: bad phone number: \"%s\".\n",
525 progname, s ? s : "(null)");
528 if (state->tracing) free (state->tracing);
530 set_mode (state, MATRIX);
535 init_drain (m_state *state)
539 set_cursor (state, False);
540 state->cursor_x = -1;
541 state->cursor_y = -1;
543 /* Fill the top row with empty top-feeders, to clear the screen. */
544 for (i = 0; i < state->grid_width; i++)
546 m_feeder *f = &state->feeders[i];
550 f->pipe_loc = BUF_SIZE-1;
553 /* Turn off all the spinners, else they never go away. */
554 for (i = 0; i < state->grid_width * state->grid_height; i++)
555 if (state->cells[i].spinner)
557 state->cells[i].spinner = 0;
558 state->cells[i].changed = 1;
563 screen_blank_p (m_state *state)
566 for (i = 0; i < state->grid_width * state->grid_height; i++)
567 if (state->cells[i].glyph)
574 set_mode (m_state *state, m_mode mode)
576 if (mode == state->mode)
585 state->glyph_map = matrix_encoding;
586 state->nglyphs = countof(matrix_encoding);
587 flip_images (state, True);
588 init_spinners (state);
591 state->glyph_map = dna_encoding;
592 state->nglyphs = countof(dna_encoding);
593 flip_images (state, False);
596 state->glyph_map = binary_encoding;
597 state->nglyphs = countof(binary_encoding);
598 flip_images (state, False);
601 state->glyph_map = hex_encoding;
602 state->nglyphs = countof(hex_encoding);
603 flip_images (state, False);
606 state->glyph_map = ascii_encoding;
607 state->nglyphs = countof(ascii_encoding);
608 flip_images (state, False);
615 state->glyph_map = decimal_encoding;
616 state->nglyphs = countof(decimal_encoding);
617 flip_images (state, False);
621 flip_images (state, False);
641 xmatrix_init (Display *dpy, Window window)
646 m_state *state = (m_state *) calloc (sizeof(*state), 1);
649 state->window = window;
650 state->delay = get_integer_resource (dpy, "delay", "Integer");
652 XGetWindowAttributes (dpy, window, &state->xgwa);
654 state->small_p = (state->xgwa.width < 300);
656 const char *s = get_string_resource (dpy, "matrixFont", "String");
657 if (!s || !*s || !strcasecmp(s, "large"))
658 state->small_p = False;
659 else if (!strcasecmp(s, "small"))
660 state->small_p = True;
662 fprintf (stderr, "%s: matrixFont should be 'small' or 'large' not '%s'\n",
666 load_images (dpy, state);
668 gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
669 "foreground", "Foreground");
670 gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
671 "background", "Background");
672 state->draw_gc = XCreateGC (state->dpy, state->window,
673 GCForeground|GCBackground, &gcv);
674 gcv.foreground = gcv.background;
675 state->erase_gc = XCreateGC (state->dpy, state->window,
676 GCForeground|GCBackground, &gcv);
678 state->scratch_gc = XCreateGC (state->dpy, state->window, 0, &gcv);
680 /* Allocate colors for SYSTEM FAILURE box */
682 XColor boxcolors[] = {
683 { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
684 { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
685 { 0, 0xE0E0, 0xF7F7, 0xE0E0, DoRed|DoGreen|DoBlue, 0 },
686 { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
687 { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
689 for (i = 0; i < countof(boxcolors); i++)
691 if (XAllocColor (state->dpy, state->xgwa.colormap, &boxcolors[i]))
692 state->colors[i] = boxcolors[i].pixel;
694 state->colors[i] = gcv.foreground; /* default black */
698 state->char_width = state->image_width / CHAR_COLS;
699 state->char_height = state->image_height / CHAR_ROWS;
701 state->grid_width = state->xgwa.width / state->char_width;
702 state->grid_height = state->xgwa.height / state->char_height;
704 state->grid_height++;
705 if (state->grid_width < 5) state->grid_width = 5;
706 if (state->grid_height < 5) state->grid_height = 5;
708 state->glyph_map = matrix_encoding;
709 state->nglyphs = countof(matrix_encoding);
711 state->cells = (m_cell *)
712 calloc (sizeof(m_cell), state->grid_width * state->grid_height);
713 state->feeders = (m_feeder *) calloc (sizeof(m_feeder), state->grid_width);
715 state->density = get_integer_resource (dpy, "density", "Integer");
717 insert = get_string_resource(dpy, "insert", "Insert");
718 if (insert && !strcmp(insert, "top"))
720 state->insert_top_p = True;
721 state->insert_bottom_p = False;
723 else if (insert && !strcmp(insert, "bottom"))
725 state->insert_top_p = False;
726 state->insert_bottom_p = True;
728 else if (insert && !strcmp(insert, "both"))
730 state->insert_top_p = True;
731 state->insert_bottom_p = True;
735 if (insert && *insert)
737 "%s: `insert' must be `top', `bottom', or `both', not `%s'\n",
739 state->insert_top_p = False;
740 state->insert_bottom_p = True;
743 state->nspinners = get_integer_resource (dpy, "spinners", "Integer");
748 state->knock_knock_p = get_boolean_resource (dpy, "knockKnock", "KnockKnock");
750 state->use_pipe_p = get_boolean_resource (dpy, "usePipe", "Boolean");
752 state->do_fill_buff = True;
754 launch_text_generator (state);
757 mode = get_string_resource (dpy, "mode", "Mode");
758 if (mode && !strcasecmp(mode, "trace"))
759 set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
760 else if (mode && !strcasecmp(mode, "crack"))
761 set_mode (state, DRAIN_NMAP);
762 else if (mode && !strcasecmp(mode, "dna"))
763 set_mode (state, DNA);
764 else if (mode && (!strcasecmp(mode, "bin") ||
765 !strcasecmp(mode, "binary")))
766 set_mode (state, BINARY);
767 else if (mode && (!strcasecmp(mode, "hex") ||
768 !strcasecmp(mode, "hexadecimal")))
769 set_mode (state, HEX);
770 else if (mode && (!strcasecmp(mode, "dec") ||
771 !strcasecmp(mode, "decimal")))
772 set_mode (state, DEC);
773 else if (mode && (!strcasecmp(mode, "asc") ||
774 !strcasecmp(mode, "ascii")))
775 set_mode (state, ASCII);
776 else if (!mode || !*mode || !strcasecmp(mode, "matrix"))
777 set_mode (state, MATRIX);
778 else if (!mode || !*mode || !strcasecmp(mode, "pipe"))
780 set_mode (state, ASCII);
781 state->use_pipe_p = True;
785 fprintf (stderr, "%s: `mode' must be ",progname);
786 fprintf (stderr, "matrix, trace, dna, binary, ascii, or hex: ");
787 fprintf (stderr, "not `%s'\n", mode);
788 set_mode (state, MATRIX);
791 if (state->mode == MATRIX && get_boolean_resource (dpy, "trace", "Boolean"))
792 set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
794 state->cursor_x = -1;
795 state->cursor_y = -1;
802 insert_glyph (m_state *state, int glyph, int x, int y)
804 Bool bottom_feeder_p = (y >= 0);
807 if (y >= state->grid_height)
812 to = &state->cells[state->grid_width * y + x];
816 for (y = state->grid_height-1; y > 0; y--)
818 from = &state->cells[state->grid_width * (y-1) + x];
819 to = &state->cells[state->grid_width * y + x];
820 to->glyph = from->glyph;
821 to->glow = from->glow;
824 to = &state->cells[x];
832 else if (bottom_feeder_p)
833 to->glow = 1 + (random() % (state->tracing ? 4 : 2));
840 feed_matrix (m_state *state)
848 int L = strlen((char *) state->tracing);
852 for (i = 0; i < strlen((char *) state->tracing); i++)
853 if (state->tracing[i] > 0)
858 set_mode (state, TRACE_DONE);
859 state->typing_delay = 1000000;
864 i = 5 + (30 / (count+1)); /* how fast numbers are discovered */
866 if ((random() % i) == 0)
869 if (state->tracing[i] < 0)
870 state->tracing[i] = -state->tracing[i];
877 if ((random() % 40) == 0)
879 set_mode (state, TRACE_FAIL);
884 case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
897 if(state->use_pipe_p){
898 state->buf_done = (state->buf_done + 1) % BUF_SIZE;
899 if(state->buf_done == 0){
900 state->do_fill_buff = True;
902 if(state->do_fill_buff){
907 /* Update according to current feeders. */
908 for (x = 0; x < state->grid_width; x++)
910 m_feeder *f = &state->feeders[x];
912 if (f->throttle) /* this is a delay tick, synced to frame. */
916 else if (f->remaining > 0) /* how many items are in the pipe */
920 if(state->use_pipe_p){
921 rval = (int) state->buf[f->pipe_loc];
922 if(++f->pipe_loc > (BUF_SIZE-1)){
924 /*fill_input(state);*/
926 rval = (rval % state->nglyphs);
929 rval = (random() % state->nglyphs);
931 g = state->glyph_map[rval] + 1;
932 insert_glyph (state, g, x, f->y);
934 if (f->y >= 0) /* bottom_feeder_p */
937 else /* if pipe is empty, insert spaces */
939 insert_glyph (state, 0, x, f->y);
940 if (f->y >= 0) /* bottom_feeder_p */
944 if ((random() % 10) == 0) /* randomly change throttle speed */
946 f->throttle = ((random() % 5) + (random() % 5));
953 redraw_cells (m_state *state, Bool active)
958 for (y = 0; y < state->grid_height; y++)
959 for (x = 0; x < state->grid_width; x++)
961 m_cell *cell = &state->cells[state->grid_width * y + x];
962 Bool cursor_p = (state->cursor_on &&
963 x == state->cursor_x &&
964 y == state->cursor_y);
969 /* In trace-mode, the state of each cell is random unless we have
970 a match for this digit. */
971 if (active && (state->mode == TRACE_A ||
972 state->mode == TRACE_B ||
973 state->mode == TRACE_DONE))
975 int xx = x % strlen((char *) state->tracing);
976 Bool dead_p = state->tracing[xx] > 0;
978 if (y == 0 && x == xx)
979 cell->glyph = (dead_p
980 ? state->glyph_map[state->tracing[xx]-'0'] + 1
985 cell->glyph = (dead_p ? 0 :
986 (state->glyph_map[(random()%state->nglyphs)]
995 if (cell->glyph == 0 && !cursor_p)
996 XFillRectangle (state->dpy, state->window, state->erase_gc,
997 x * state->char_width,
998 y * state->char_height,
1000 state->char_height);
1003 int g = (cursor_p ? CURSOR_GLYPH : cell->glyph);
1004 int cx = (g - 1) % CHAR_COLS;
1005 int cy = (g - 1) / CHAR_COLS;
1006 int map = ((cell->glow != 0 || cell->spinner) ? GLOW_MAP :
1009 XCopyArea (state->dpy, state->images[map],
1010 state->window, state->draw_gc,
1011 cx * state->char_width,
1012 cy * state->char_height,
1015 x * state->char_width,
1016 y * state->char_height);
1021 if (cell->glow > 0 && state->mode != NMAP)
1026 else if (cell->glow < 0)
1029 if (cell->spinner && active)
1031 cell->glyph = (state->glyph_map[(random()%state->nglyphs)] + 1);
1039 densitizer (m_state *state)
1041 /* Horrid kludge that converts percentages (density of screen coverage)
1042 to the parameter that actually controls this. I got this mapping
1043 empirically, on a 1024x768 screen. Sue me. */
1044 if (state->density < 10) return 85;
1045 else if (state->density < 15) return 60;
1046 else if (state->density < 20) return 45;
1047 else if (state->density < 25) return 25;
1048 else if (state->density < 30) return 20;
1049 else if (state->density < 35) return 15;
1050 else if (state->density < 45) return 10;
1051 else if (state->density < 50) return 8;
1052 else if (state->density < 55) return 7;
1053 else if (state->density < 65) return 5;
1054 else if (state->density < 80) return 3;
1055 else if (state->density < 90) return 2;
1061 hack_text (m_state *state)
1065 set_cursor (state, False);
1066 state->cursor_x = 0;
1067 state->cursor_y = 0;
1068 state->typing_scroll_p = False;
1069 state->typing_bold_p = False;
1070 state->typing_cursor_p = True;
1071 state->typing_stutter_p = False;
1072 state->typing_char_delay = 10000;
1073 state->typing_line_delay = 1500000;
1075 switch (state->mode)
1079 if (state->mode == TRACE_TEXT_A)
1081 if (state->grid_width >= 52)
1083 ("Call trans opt: received. 2-19-98 13:24:18 REC:Log>\n"
1084 "Trace program: running\n");
1087 ("Call trans opt: received.\n2-19-98 13:24:18 REC:Log>\n"
1088 "Trace program: running\n");
1092 if (state->grid_width >= 52)
1094 ("Call trans opt: received. 9-18-99 14:32:21 REC:Log>\n"
1095 "WARNING: carrier anomaly\n"
1096 "Trace program: running\n");
1099 ("Call trans opt: received.\n9-18-99 14:32:21 REC:Log>\n"
1100 "WARNING: carrier anomaly\n"
1101 "Trace program: running\n");
1107 const char *s = "SYSTEM FAILURE\n";
1109 float cx = (state->grid_width - strlen(s) - 1) / 2 - 0.5;
1110 float cy = (state->grid_height / 2) - 1.3;
1115 XFillRectangle (state->dpy, state->window, state->erase_gc,
1116 cx * state->char_width,
1117 cy * state->char_height,
1118 strlen(s) * state->char_width,
1119 state->char_height * 1.6);
1121 for (i = -2; i < 3; i++)
1124 gcv.foreground = state->colors[i + 2];
1125 XChangeGC (state->dpy, state->scratch_gc, GCForeground, &gcv);
1126 XDrawRectangle (state->dpy, state->window, state->scratch_gc,
1127 cx * state->char_width - i,
1128 cy * state->char_height - i,
1129 strlen(s) * state->char_width + (2 * i),
1130 (state->char_height * 1.6) + (2 * i));
1133 /* If we don't clear these, part of the box may get overwritten */
1134 for (i = 0; i < state->grid_height * state->grid_width; i++)
1136 m_cell *cell = &state->cells[i];
1140 state->cursor_x = (state->grid_width - strlen(s) - 1) / 2;
1141 state->cursor_y = (state->grid_height / 2) - 1;
1142 if (state->cursor_x < 0) state->cursor_x = 0;
1143 if (state->cursor_y < 0) state->cursor_y = 0;
1146 state->typing_char_delay = 0;
1147 state->typing_cursor_p = False;
1153 state->typing = ("\001Wake up, Neo...\n"
1154 "\001The Matrix has you...\n"
1155 "\001Follow the white rabbit.\n"
1157 "Knock, knock, Neo.\n");
1159 state->cursor_x = 4;
1160 state->cursor_y = 2;
1161 state->typing_char_delay = 0;
1162 state->typing_line_delay = 2000000;
1168 /* Note that what Trinity is using here is moderately accurate:
1169 She runs nmap (http://www.insecure.org/nmap/) then breaks in
1170 with a (hypothetical) program called "sshnuke" that exploits
1171 the (very real) SSHv1 CRC32 compensation attack detector bug
1172 (http://staff.washington.edu/dittrich/misc/ssh-analysis.txt).
1174 The command syntax of the power grid control software looks a
1175 lot like Cisco IOS to me. (IOS is a descendant of VMS.)
1180 __extension__ /* don't warn about "string length is greater than
1181 the length ISO C89 compilers are required to
1187 "\001nmap 10.2.2.2\n"
1188 "Starting nmap V. 2.54BETA25\n"
1189 "\010\010\010\010\010\010\010\010\010\010"
1190 "Insufficient responses for TCP sequencing (3), OS detection"
1191 " may be less accurate\n"
1192 "Interesting ports on 10.2.2.2:\n"
1193 "(The 1538 ports scanned but not shown below are in state:"
1195 "Port state service\n"
1198 "No exact OS matches for host\n"
1200 "Nmap run completed -- 1 IP address (1 host up) scanned\n"
1204 "\001sshnuke 10.2.2.2 -rootpw=\"Z1ON0101\"\n"
1205 "Connecting to 10.2.2.2:ssh ... "
1208 "Attempting to exploit SSHv1 CRC32 ... "
1211 "Resetting root password to \"Z1ON0101\".\n"
1213 "System open: Access Level <9>\n"
1218 "\001ssh 10.2.2.2 -l root\n"
1220 "root@10.2.2.2's password: "
1226 "\001disable grid nodes 21 - 48\n"
1228 "\002Warning: Disabling nodes 21-48 will disconnect sector 11"
1231 "\002 ARE YOU SURE? (y/n) "
1237 "\010\002Grid Node 21 offline...\n"
1238 "\010\002Grid Node 22 offline...\n"
1239 "\010\002Grid Node 23 offline...\n"
1240 "\010\002Grid Node 24 offline...\n"
1241 "\010\002Grid Node 25 offline...\n"
1242 "\010\002Grid Node 26 offline...\n"
1243 "\010\002Grid Node 27 offline...\n"
1244 "\010\002Grid Node 28 offline...\n"
1245 "\010\002Grid Node 29 offline...\n"
1246 "\010\002Grid Node 30 offline...\n"
1247 "\010\002Grid Node 31 offline...\n"
1248 "\010\002Grid Node 32 offline...\n"
1249 "\010\002Grid Node 33 offline...\n"
1250 "\010\002Grid Node 34 offline...\n"
1251 "\010\002Grid Node 35 offline...\n"
1252 "\010\002Grid Node 36 offline...\n"
1253 "\010\002Grid Node 37 offline...\n"
1254 "\010\002Grid Node 38 offline...\n"
1255 "\010\002Grid Node 39 offline...\n"
1256 "\010\002Grid Node 40 offline...\n"
1257 "\010\002Grid Node 41 offline...\n"
1258 "\010\002Grid Node 42 offline...\n"
1259 "\010\002Grid Node 43 offline...\n"
1260 "\010\002Grid Node 44 offline...\n"
1261 "\010\002Grid Node 45 offline...\n"
1262 "\010\002Grid Node 46 offline...\n"
1263 "\010\002Grid Node 47 offline...\n"
1264 "\010\002Grid Node 48 offline...\n"
1267 "\010\010\010\010\010\010\010\010"
1270 state->cursor_x = 0;
1271 state->cursor_y = state->grid_height - 3;
1272 state->typing_scroll_p = True;
1273 state->typing_char_delay = 0;
1274 state->typing_line_delay = 20000;
1283 state->typing_left_margin = state->cursor_x;
1284 state->typing_delay = state->typing_char_delay;
1285 if (state->typing_cursor_p)
1286 set_cursor (state, True);
1290 Bool scrolled_p = False;
1291 unsigned char c, c1;
1292 int x = state->cursor_x;
1293 int y = state->cursor_y;
1296 c = ((unsigned char *) state->typing)[0];
1297 c1 = ((unsigned char *) state->typing)[1];
1299 state->typing_delay = (!c || c1 == '\n'
1300 ? state->typing_line_delay
1301 : state->typing_char_delay);
1304 state->typing_delay = 0;
1309 if (state->typing_scroll_p &&
1311 x >= state->grid_width - 1))
1313 set_cursor (state, False);
1317 if (y >= state->grid_height-1)
1320 for (yy = 0; yy < state->grid_height-2; yy++)
1321 for (xx = 0; xx < state->grid_width; xx++)
1323 int ii = yy * state->grid_width + xx;
1324 int jj = (yy+1) * state->grid_width + xx;
1325 state->cells[ii] = state->cells[jj];
1326 state->cells[ii].changed = 1;
1328 /* clear bottom row */
1329 for (xx = 0; xx < state->grid_width; xx++)
1331 int ii = yy * state->grid_width + xx;
1332 state->cells[ii].glyph = 0;
1333 state->cells[ii].changed = 1;
1335 y--; /* move back up to bottom line */
1342 if (!state->typing_scroll_p)
1345 set_cursor (state, False);
1346 x = state->typing_left_margin;
1348 /* clear the line */
1349 i = state->grid_width * y;
1350 j = i + state->grid_width;
1353 state->cells[i].glyph = 0;
1354 state->cells[i].changed = 1;
1357 state->typing_bold_p = False;
1358 state->typing_stutter_p = False;
1362 else if (c == '\010')
1363 state->typing_delay += 500000;
1365 else if (c == '\001')
1367 state->typing_stutter_p = True;
1368 state->typing_bold_p = False;
1370 else if (c == '\002')
1371 state->typing_bold_p = True;
1373 else if (x < state->grid_width-1)
1375 m_cell *cell = &state->cells[state->grid_width * y + x];
1376 cell->glyph = char_map[c] + 1;
1377 if (c == ' ' || c == '\t') cell->glyph = 0;
1379 cell->glow = (state->typing_bold_p ? 127 : 0);
1385 if (x >= state->grid_width-1)
1386 x = state->grid_width-1;
1390 if (state->typing_stutter_p)
1392 if (state->typing_delay == 0)
1393 state->typing_delay = 20000;
1395 state->typing_delay += (0xFFFFFF & ((random() % 200000) + 1));
1398 /* If there's no delay after this character, just keep going. */
1399 if (state->typing_delay == 0)
1402 if (scrolled_p || x != state->cursor_x || y != state->cursor_y)
1404 set_cursor (state, False);
1405 state->cursor_x = x;
1406 state->cursor_y = y;
1407 if (state->typing_cursor_p)
1408 set_cursor (state, True);
1415 hack_matrix (m_state *state)
1419 switch (state->mode)
1421 case TRACE_DONE: case TRACE_FAIL:
1423 case TRACE_A: case TRACE_B:
1424 case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1430 /* Glow some characters. */
1431 if (!state->insert_bottom_p)
1433 int i = random() % (state->grid_width / 2);
1436 int yy = random() % state->grid_height;
1437 int xx = random() % state->grid_width;
1438 m_cell *cell = &state->cells[state->grid_width * yy + xx];
1439 if (cell->glyph && cell->glow == 0)
1441 cell->glow = random() % 10;
1447 /* Change some of the feeders. */
1448 for (x = 0; x < state->grid_width; x++)
1450 m_feeder *f = &state->feeders[x];
1451 Bool bottom_feeder_p;
1453 if (f->remaining > 0) /* never change if pipe isn't empty */
1456 if ((random() % densitizer(state)) != 0) /* then change N% of the time */
1459 f->remaining = 3 + (random() % state->grid_height);
1460 f->throttle = ((random() % 5) + (random() % 5));
1462 if ((random() % 4) != 0)
1465 if (state->mode == TRACE_A || state->mode == TRACE_B)
1466 bottom_feeder_p = True;
1467 if (state->insert_top_p && state->insert_bottom_p)
1468 bottom_feeder_p = (random() & 1);
1470 bottom_feeder_p = state->insert_bottom_p;
1472 if (bottom_feeder_p)
1473 f->y = random() % (state->grid_height / 2);
1478 if (state->mode == MATRIX && (! (random() % 500)))
1479 init_spinners (state);
1483 static unsigned long
1484 xmatrix_draw (Display *dpy, Window window, void *closure)
1486 m_state *state = (m_state *) closure;
1488 if (state->typing_delay > 0)
1490 state->typing_delay -= state->delay;
1491 if (state->typing_delay < 0)
1492 state->typing_delay = 0;
1493 redraw_cells (state, False);
1494 return state->delay;
1497 switch (state->mode)
1499 case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1500 case TRACE_A: case TRACE_B:
1501 feed_matrix (state);
1502 hack_matrix (state);
1510 feed_matrix (state);
1511 if (screen_blank_p (state))
1513 state->typing_delay = 500000;
1514 switch (state->mode)
1516 case DRAIN_TRACE_A: set_mode (state, TRACE_TEXT_A); break;
1517 case DRAIN_TRACE_B: set_mode (state, TRACE_TEXT_B); break;
1518 case DRAIN_KNOCK: set_mode (state, KNOCK); break;
1519 case DRAIN_NMAP: set_mode (state, NMAP); break;
1520 case DRAIN_MATRIX: set_mode (state, MATRIX); break;
1521 default: abort(); break;
1527 set_mode (state, MATRIX);
1537 if (! state->typing) /* done typing */
1539 set_cursor (state, False);
1540 switch (state->mode)
1542 case TRACE_TEXT_A: set_mode (state, TRACE_A); break;
1543 case TRACE_TEXT_B: set_mode (state, TRACE_B); break;
1544 case TRACE_FAIL: set_mode (state, MATRIX); break;
1545 case KNOCK: set_mode (state, MATRIX); break;
1546 case NMAP: set_mode (state, MATRIX); break;
1547 default: abort(); break;
1556 if (state->mode == MATRIX &&
1557 state->knock_knock_p &&
1558 (! (random() % 10000)))
1560 if (! (random() % 5))
1561 set_mode (state, DRAIN_NMAP);
1563 set_mode (state, DRAIN_KNOCK);
1566 redraw_cells (state, True);
1571 static int ndens = 0;
1572 static int tdens = 0;
1578 ((double) (state->grid_width * state->grid_height))));
1581 printf ("density: %d%% (%d%%)\n", dens, (tdens / ndens));
1587 return state->delay;
1592 xmatrix_reshape (Display *dpy, Window window, void *closure,
1593 unsigned int w, unsigned int h)
1595 m_state *state = (m_state *) closure;
1596 int ow = state->grid_width;
1597 int oh = state->grid_height;
1598 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
1599 state->grid_width = state->xgwa.width / state->char_width;
1600 state->grid_height = state->xgwa.height / state->char_height;
1601 state->grid_width++;
1602 state->grid_height++;
1603 if (state->grid_width < 5) state->grid_width = 5;
1604 if (state->grid_height < 5) state->grid_height = 5;
1606 if (ow != state->grid_width ||
1607 oh != state->grid_height)
1609 m_cell *ncells = (m_cell *)
1610 calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1611 m_feeder *nfeeders = (m_feeder *)
1612 calloc (sizeof(m_feeder), state->grid_width);
1615 /* fprintf(stderr, "resize: %d x %d ==> %d x %d\n",
1616 ow, oh, state->grid_width, state->grid_height); */
1618 for (y = 0; y < oh; y++)
1619 for (x = 0; x < ow; x++)
1620 if (x < ow && x < state->grid_width &&
1621 y < oh && y < state->grid_height)
1622 ncells[y * state->grid_width + x] =
1623 state->cells[y * ow + x];
1624 free (state->cells);
1625 state->cells = ncells;
1627 x = (ow < state->grid_width ? ow : state->grid_width);
1628 for (i = 0; i < x; i++)
1629 nfeeders[i] = state->feeders[i];
1630 free (state->feeders);
1631 state->feeders = nfeeders;
1636 xmatrix_event (Display *dpy, Window window, void *closure, XEvent *event)
1638 m_state *state = (m_state *) closure;
1640 if (event->xany.type == KeyPress)
1644 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1648 set_mode (state, DRAIN_MATRIX);
1651 case '+': case '=': case '>': case '.':
1652 state->density += 10;
1653 if (state->density > 100)
1654 state->density = 100;
1659 case '-': case '_': case '<': case ',':
1660 state->density -= 10;
1661 if (state->density < 0)
1667 case '[': case '(': case '{':
1668 state->insert_top_p = True;
1669 state->insert_bottom_p = False;
1672 case ']': case ')': case '}':
1673 state->insert_top_p = False;
1674 state->insert_bottom_p = True;
1677 case '\\': case '|':
1678 state->insert_top_p = True;
1679 state->insert_bottom_p = True;
1683 set_mode (state, DRAIN_TRACE_A);
1687 set_mode (state, DRAIN_TRACE_B);
1691 set_mode (state, DRAIN_KNOCK);
1695 set_mode (state, DRAIN_NMAP);
1707 xmatrix_free (Display *dpy, Window window, void *closure)
1709 m_state *state = (m_state *) closure;
1711 if (state->cursor_timer)
1712 XtRemoveTimeOut (state->cursor_timer);
1714 /* #### there's more to free here */
1721 static const char *xmatrix_defaults [] = {
1722 ".background: black",
1723 ".foreground: #00AA00",
1724 "*matrixFont: large",
1728 "*tracePhone: (312) 555-0690",
1732 "*knockKnock: True",
1734 "*program: xscreensaver-text",
1735 "*geometry: 800x600",
1739 static XrmOptionDescRec xmatrix_options [] = {
1740 { "-small", ".matrixFont", XrmoptionNoArg, "Small" },
1741 { "-large", ".matrixFont", XrmoptionNoArg, "Large" },
1742 { "-delay", ".delay", XrmoptionSepArg, 0 },
1743 { "-insert", ".insert", XrmoptionSepArg, 0 },
1744 { "-top", ".insert", XrmoptionNoArg, "top" },
1745 { "-bottom", ".insert", XrmoptionNoArg, "bottom" },
1746 { "-both", ".insert", XrmoptionNoArg, "both" },
1747 { "-density", ".density", XrmoptionSepArg, 0 },
1748 { "-trace", ".trace", XrmoptionNoArg, "True" },
1749 { "-no-trace", ".trace", XrmoptionNoArg, "False" },
1750 { "-crack", ".mode", XrmoptionNoArg, "crack"},
1751 { "-phone", ".tracePhone", XrmoptionSepArg, 0 },
1752 { "-mode", ".mode", XrmoptionSepArg, 0 },
1753 { "-dna", ".mode", XrmoptionNoArg, "DNA" },
1754 { "-binary", ".mode", XrmoptionNoArg, "binary" },
1755 { "-hexadecimal", ".mode", XrmoptionNoArg, "hexadecimal"},
1756 { "-decimal", ".mode", XrmoptionNoArg, "decimal"},
1757 { "-knock-knock", ".knockKnock", XrmoptionNoArg, "True" },
1758 { "-no-knock-knock", ".knockKnock", XrmoptionNoArg, "False" },
1759 { "-ascii", ".mode", XrmoptionNoArg, "ascii"},
1760 { "-pipe", ".usePipe", XrmoptionNoArg, "True" },
1761 { "-no-pipe", ".usePipe", XrmoptionNoArg, "False" },
1762 { "-program", ".program", XrmoptionSepArg, 0 },
1766 XSCREENSAVER_MODULE ("XMatrix", xmatrix)