1 /* xscreensaver, Copyright (c) 1999-2018 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. "GLMatrix" does that.
48 * ==========================================================
56 #include "screenhack.h"
57 #include "textclient.h"
58 #include "ximage-loader.h"
63 # include <X11/Intrinsic.h>
66 #include "images/gen/matrix1_png.h"
67 #include "images/gen/matrix2_png.h"
68 #include "images/gen/matrix1b_png.h"
69 #include "images/gen/matrix2b_png.h"
79 unsigned int glyph : 9; /* note: 9 bit characters! */
80 unsigned int changed : 1;
81 unsigned int spinner : 1;
91 #define countof(x) (sizeof(x)/sizeof(*(x)))
93 static const int matrix_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
94 192, 193, 194, 195, 196, 197, 198, 199,
95 200, 201, 202, 203, 204, 205, 206, 207 };
96 static const int decimal_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
97 static const int hex_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
98 33, 34, 35, 36, 37, 38 };
99 static const int binary_encoding[] = { 16, 17 };
100 static const int dna_encoding[] = { 33, 35, 39, 52 };
101 static const int ascii_encoding[] = {
102 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
103 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
104 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
105 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
106 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
107 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
108 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
109 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
110 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
111 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
113 static const unsigned char char_map[256] = {
114 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0 */
115 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 16 */
116 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 32 */
117 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 48 */
118 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 64 */
119 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 80 */
120 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 96 */
121 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 112 */
122 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 128 */
123 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 144 */
124 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, /* 160 */
125 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* 176 */
126 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 192 */
127 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, /* 208 */
128 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, /* 224 */
129 176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191 /* 240 */
132 #define CURSOR_GLYPH 97
134 /* larger numbers should mean more variability between columns */
137 typedef enum { DRAIN_TRACE_A,
138 TRACE_TEXT_A, /* Call trans opt: received. */
139 TRACE_A, /* (31_) 5__-0_9_ */
143 TRACE_TEXT_B, /* Call trans opt: received. */
145 TRACE_FAIL, /* System Failure */
148 KNOCK, /* Wake up, Neo... */
151 NMAP, /* Starting nmap V. 2.54BETA25 */
164 XWindowAttributes xgwa;
165 GC draw_gc, erase_gc, scratch_gc;
166 int grid_width, grid_height;
167 int char_width, char_height;
174 Bool insert_top_p, insert_bottom_p;
177 m_mode def_mode; /* Mode to return to after trace etc. */
180 char buf [BUF_SIZE*2+1]; /* ring buffer */
184 Bool start_reveal_back_p; /* start reveal process for pipe */
185 Bool back_text_full_p; /* is the pipe buffer (background) full ? */
186 char back_line [BUF_SIZE*2+1]; /* line buffer for background */
187 int back_pos; /* background line buffer position */
190 signed char *tracing;
194 Bool typing_scroll_p;
195 Bool typing_cursor_p;
197 Bool typing_stutter_p;
198 int typing_left_margin;
199 int typing_char_delay;
200 int typing_line_delay;
204 int cursor_x, cursor_y;
205 XtIntervalId cursor_timer;
207 Pixmap images[CHAR_MAPS];
208 int image_width, image_height;
209 Bool images_flipped_p;
212 const int *glyph_map;
214 unsigned long colors[5];
220 load_images_1 (Display *dpy, m_state *state, int which)
222 const unsigned char *png = 0;
223 unsigned long size = 0;
227 png = matrix1b_png, size = sizeof(matrix1b_png);
229 png = matrix1_png, size = sizeof(matrix1_png);
234 png = matrix2b_png, size = sizeof(matrix2b_png);
236 png = matrix2_png, size = sizeof(matrix2_png);
238 state->images[which] =
239 image_data_to_pixmap (state->dpy, state->window, png, size,
240 &state->image_width, &state->image_height, 0);
245 load_images (Display *dpy, m_state *state)
247 load_images_1 (dpy, state, 1);
248 load_images_1 (dpy, state, 2);
253 flip_images_1 (m_state *state, int which)
255 XImage *im = XGetImage (state->dpy, state->images[which], 0, 0,
256 state->image_width, state->image_height,
257 ~0L, (state->xgwa.depth > 1 ? ZPixmap : XYPixmap));
259 int ww = state->char_width;
260 unsigned long *row = (unsigned long *) malloc (sizeof(*row) * ww);
262 for (y = 0; y < state->image_height; y++)
264 for (x = 0; x < CHAR_COLS; x++)
266 for (xx = 0; xx < ww; xx++)
267 row[xx] = XGetPixel (im, (x * ww) + xx, y);
268 for (xx = 0; xx < ww; xx++)
269 XPutPixel (im, (x * ww) + xx, y, row[ww - xx - 1]);
273 XPutImage (state->dpy, state->images[which], state->draw_gc, im, 0, 0, 0, 0,
274 state->image_width, state->image_height);
280 flip_images (m_state *state, Bool flipped_p)
282 if (flipped_p != state->images_flipped_p)
284 state->images_flipped_p = flipped_p;
285 flip_images_1 (state, 1);
286 flip_images_1 (state, 2);
291 /* When the subprocess has generated some output, this reads as much as it
292 can into s->buf at s->buf_tail.
295 fill_input (m_state *s)
299 if(s->buf_done > s->buf_pos){
300 loadBytes = (s->buf_done - s->buf_pos) - 1;
303 loadBytes = ((BUF_SIZE - s->buf_pos) + s->buf_done) - 1;
311 int c = textclient_getc (s->tc);
312 n = (c > 0 ? 1 : -1);
313 s->buf [s->buf_pos] = (char) c;
319 s->do_fill_buff = False;
320 s->buf_pos = (s->buf_pos + n);
321 if(s->buf_pos > BUF_SIZE){
322 /* copy to start of buffer */
323 /* areas shouldn't overlap, but just in case, use memmove */
324 memmove(s->buf,s->buf+BUF_SIZE,s->buf_pos-BUF_SIZE);
326 s->buf_pos = s->buf_pos % BUF_SIZE;
330 /* Couldn't read anything from the buffer */
331 /* Assume EOF has been reached, so start again */
332 s->do_fill_buff = True;
337 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
338 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
341 set_cursor_1 (m_state *state, Bool on)
343 Bool changed = (state->cursor_on != on);
344 state->cursor_on = on;
346 if (changed && state->cursor_x >= 0 && state->cursor_y >= 0)
348 m_cell *cell = &state->cells[state->grid_width * state->cursor_y
351 cell->changed = True;
358 set_cursor (m_state *state, Bool on)
360 if (set_cursor_1 (state, on))
362 if (state->cursor_timer)
363 XtRemoveTimeOut (state->cursor_timer);
364 state->cursor_timer = 0;
366 cursor_on_timer (state, 0);
371 cursor_off_timer (XtPointer closure, XtIntervalId *id)
373 m_state *state = (m_state *) closure;
374 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
375 set_cursor_1 (state, False);
376 state->cursor_timer = XtAppAddTimeOut (app, 333,
377 cursor_on_timer, closure);
381 cursor_on_timer (XtPointer closure, XtIntervalId *id)
383 m_state *state = (m_state *) closure;
384 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
385 set_cursor_1 (state, True);
386 state->cursor_timer = XtAppAddTimeOut (app, 666,
387 cursor_off_timer, closure);
392 init_spinners (m_state *state)
394 int i = state->nspinners;
398 for (y = 0; y < state->grid_height; y++)
399 for (x = 0; x < state->grid_width; x++)
401 cell = &state->cells[state->grid_width * y + x];
407 x = random() % state->grid_width;
408 y = random() % state->grid_height;
409 cell = &state->cells[state->grid_width * y + x];
416 clear_spinners (m_state *state)
419 for (i = 0; i < state->grid_width * state->grid_height; i++)
420 if (state->cells[i].spinner)
422 state->cells[i].spinner = 0;
423 state->cells[i].changed = 1;
428 static void set_mode (m_state *, m_mode);
432 init_trace (m_state *state)
434 char *s = get_string_resource (state->dpy, "tracePhone", "TracePhone");
440 state->tracing = (signed char *) malloc (strlen (s) + 1);
443 for (s2 = s; *s2; s2++)
444 if (*s2 >= '0' && *s2 <= '9')
448 if (s3 == state->tracing)
451 state->glyph_map = decimal_encoding;
452 state->nglyphs = countof(decimal_encoding);
457 fprintf (stderr, "%s: bad phone number: \"%s\".\n",
458 progname, s ? s : "(null)");
461 if (state->tracing) free (state->tracing);
463 set_mode (state, MATRIX);
468 init_drain (m_state *state)
472 set_cursor (state, False);
473 state->cursor_x = -1;
474 state->cursor_y = -1;
476 /* Fill the top row with empty top-feeders, to clear the screen. */
477 for (i = 0; i < state->grid_width; i++)
479 m_feeder *f = &state->feeders[i];
485 /* Turn off all the spinners, else they never go away. */
486 clear_spinners (state);
490 screen_blank_p (m_state *state)
493 for (i = 0; i < state->grid_width * state->grid_height; i++)
494 if (state->cells[i].glyph)
501 set_mode (m_state *state, m_mode mode)
503 if (mode == state->mode)
512 state->glyph_map = matrix_encoding;
513 state->nglyphs = countof(matrix_encoding);
514 flip_images (state, True);
515 init_spinners (state);
518 state->glyph_map = dna_encoding;
519 state->nglyphs = countof(dna_encoding);
520 flip_images (state, False);
523 state->glyph_map = binary_encoding;
524 state->nglyphs = countof(binary_encoding);
525 flip_images (state, False);
528 state->glyph_map = hex_encoding;
529 state->nglyphs = countof(hex_encoding);
530 flip_images (state, False);
533 state->glyph_map = ascii_encoding;
534 state->nglyphs = countof(ascii_encoding);
535 flip_images (state, False);
542 state->glyph_map = decimal_encoding;
543 state->nglyphs = countof(decimal_encoding);
544 flip_images (state, False);
548 flip_images (state, False);
568 xmatrix_init (Display *dpy, Window window)
573 m_state *state = (m_state *) calloc (sizeof(*state), 1);
576 state->window = window;
577 state->delay = get_integer_resource (dpy, "delay", "Integer");
579 XGetWindowAttributes (dpy, window, &state->xgwa);
581 state->small_p = (state->xgwa.width < 300);
583 const char *s = get_string_resource (dpy, "matrixFont", "String");
584 if (!s || !*s || !strcasecmp(s, "large"))
585 state->small_p = False;
586 else if (!strcasecmp(s, "small"))
587 state->small_p = True;
589 fprintf (stderr, "%s: matrixFont should be 'small' or 'large' not '%s'\n",
593 load_images (dpy, state);
595 gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
596 "foreground", "Foreground");
597 gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
598 "background", "Background");
599 state->draw_gc = XCreateGC (state->dpy, state->window,
600 GCForeground|GCBackground, &gcv);
601 gcv.foreground = gcv.background;
602 state->erase_gc = XCreateGC (state->dpy, state->window,
603 GCForeground|GCBackground, &gcv);
605 state->scratch_gc = XCreateGC (state->dpy, state->window, 0, &gcv);
607 /* Allocate colors for SYSTEM FAILURE box */
609 XColor boxcolors[] = {
610 { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
611 { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
612 { 0, 0xE0E0, 0xF7F7, 0xE0E0, DoRed|DoGreen|DoBlue, 0 },
613 { 0, 0x5A5A, 0xD2D2, 0x5A5A, DoRed|DoGreen|DoBlue, 0 },
614 { 0, 0x0808, 0x1E1E, 0x0808, DoRed|DoGreen|DoBlue, 0 },
616 for (i = 0; i < countof(boxcolors); i++)
618 if (XAllocColor (state->dpy, state->xgwa.colormap, &boxcolors[i]))
619 state->colors[i] = boxcolors[i].pixel;
621 state->colors[i] = gcv.foreground; /* default black */
625 state->char_width = state->image_width / CHAR_COLS;
626 state->char_height = state->image_height / CHAR_ROWS;
628 state->grid_width = state->xgwa.width / state->char_width;
629 state->grid_height = state->xgwa.height / state->char_height;
631 state->grid_height++;
632 if (state->grid_width < 5) state->grid_width = 5;
633 if (state->grid_height < 5) state->grid_height = 5;
635 state->glyph_map = matrix_encoding;
636 state->nglyphs = countof(matrix_encoding);
638 state->cells = (m_cell *)
639 calloc (sizeof(m_cell), state->grid_width * state->grid_height);
640 state->background = (m_cell *)
641 calloc (sizeof(m_cell), state->grid_width * state->grid_height);
642 state->feeders = (m_feeder *) calloc (sizeof(m_feeder), state->grid_width);
644 state->density = get_integer_resource (dpy, "density", "Integer");
646 insert = get_string_resource(dpy, "insert", "Insert");
647 if (insert && !strcmp(insert, "top"))
649 state->insert_top_p = True;
650 state->insert_bottom_p = False;
652 else if (insert && !strcmp(insert, "bottom"))
654 state->insert_top_p = False;
655 state->insert_bottom_p = True;
657 else if (insert && !strcmp(insert, "both"))
659 state->insert_top_p = True;
660 state->insert_bottom_p = True;
664 if (insert && *insert)
666 "%s: `insert' must be `top', `bottom', or `both', not `%s'\n",
668 state->insert_top_p = False;
669 state->insert_bottom_p = True;
672 state->nspinners = get_integer_resource (dpy, "spinners", "Integer");
677 state->knock_knock_p = get_boolean_resource (dpy, "knockKnock", "KnockKnock");
679 state->use_pipe_p = get_boolean_resource (dpy, "usePipe", "Boolean");
681 state->buf[0] = ' '; /* spacer byte in buffer (space) */
683 state->do_fill_buff = True;
684 state->start_reveal_back_p = False;
685 state->back_text_full_p = False;
690 state->def_mode = MATRIX;
691 mode = get_string_resource (dpy, "mode", "Mode");
692 if (mode && !strcasecmp(mode, "trace"))
693 set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
694 else if (mode && !strcasecmp(mode, "crack"))
695 set_mode (state, DRAIN_NMAP);
696 else if (mode && !strcasecmp(mode, "dna")){
697 set_mode (state, DNA);
698 state->def_mode = DNA;
700 else if (mode && (!strcasecmp(mode, "bin") ||
701 !strcasecmp(mode, "binary"))){
702 set_mode (state, BINARY);
703 state->def_mode = BINARY;
705 else if (mode && (!strcasecmp(mode, "hex") ||
706 !strcasecmp(mode, "hexadecimal"))){
707 set_mode (state, HEX);
708 state->def_mode = HEX;
710 else if (mode && (!strcasecmp(mode, "dec") ||
711 !strcasecmp(mode, "decimal"))){
712 set_mode (state, DEC);
713 state->def_mode = DEC;
715 else if (mode && (!strcasecmp(mode, "asc") ||
716 !strcasecmp(mode, "ascii"))){
717 set_mode (state, ASCII);
718 state->def_mode = ASCII;
720 else if (mode && !strcasecmp(mode, "pipe"))
722 set_mode (state, ASCII);
723 state->def_mode = ASCII;
724 state->use_pipe_p = True;
725 state->tc = textclient_open (dpy);
727 else if (!mode || !*mode || !strcasecmp(mode, "matrix"))
728 set_mode (state, MATRIX);
731 fprintf (stderr, "%s: `mode' must be ",progname);
732 fprintf (stderr, "matrix, trace, dna, binary, ascii, hex, or pipe: ");
733 fprintf (stderr, "not `%s'\n", mode);
734 set_mode (state, MATRIX);
737 if (state->mode == MATRIX && get_boolean_resource (dpy, "trace", "Boolean"))
738 set_mode (state, ((random() % 3) ? TRACE_TEXT_A : TRACE_TEXT_B));
740 state->cursor_x = -1;
741 state->cursor_y = -1;
748 insert_glyph (m_state *state, int glyph, int x, int y)
750 Bool bottom_feeder_p = (y >= 0);
752 if (y >= state->grid_height)
757 to = &state->cells[state->grid_width * y + x];
761 for (y = state->grid_height-1; y > 0; y--)
763 from = &state->cells[state->grid_width * (y-1) + x];
764 to = &state->cells[state->grid_width * y + x];
765 to->glyph = from->glyph;
766 to->glow = from->glow;
769 to = &state->cells[x];
777 else if (bottom_feeder_p)
778 to->glow = 1 + (random() % (state->tracing ? 4 : 2));
785 place_back_char (m_state *state, char textc, int x, int y){
786 if((x>=0) && (y>=0) &&
787 (x < state->grid_width) && (y < state->grid_height)){
788 m_cell *celltmp = &state->background[state->grid_width * y + x];
789 celltmp -> glyph = char_map[(unsigned char)textc] + 1;
790 if(!celltmp->glyph || (celltmp->glyph == 3)){
791 celltmp -> glyph = char_map[32] + 1;
793 celltmp -> changed = 1;
798 place_back_text (m_state *state, char *text, int x, int y){
800 for(i=0; i<strlen(text); i++){
801 place_back_char(state, text[i], x+i, y);
806 place_back_pipe (m_state *state, char textc){
807 Bool new_line = False;
808 /* gringer pipe insert */
809 state->back_line[state->back_pos] = textc;
811 state->back_line[state->back_pos] = '\0';
814 else if ((state->back_pos > (state->grid_width - 4)) ||
815 (state->back_pos >= BUF_SIZE)){ /* off by 1? */
816 state->back_line[++state->back_pos] = '\0';
823 int startx = (state->grid_width >> 1) -
824 (strlen(state->back_line) >> 1);
825 place_back_text(state, state->back_line,
826 startx, state->back_y);
829 if(state->back_y >= (state->grid_height - 1)){
831 state->back_text_full_p = True;
832 state->start_reveal_back_p = True;
838 feed_matrix (m_state *state)
846 int L = strlen((char *) state->tracing);
850 for (i = 0; i < strlen((char *) state->tracing); i++)
851 if (state->tracing[i] > 0)
856 set_mode (state, TRACE_DONE);
857 state->typing_delay = 1000000;
862 i = 5 + (30 / (count+1)); /* how fast numbers are discovered */
864 if ((random() % i) == 0)
867 if (state->tracing[i] < 0)
868 state->tracing[i] = -state->tracing[i];
875 if ((random() % 40) == 0)
877 set_mode (state, TRACE_FAIL);
882 case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
895 if((state->use_pipe_p) && (!state->back_text_full_p)){
896 place_back_pipe(state, state->buf[state->buf_done]);
897 state->buf_done = (state->buf_done + 1) % BUF_SIZE;
898 if(state->buf_done == (state->buf_pos - 1)){
899 state->do_fill_buff = True;
902 if(state->buf_done == (state->buf_pos + 1)){
903 state->do_fill_buff = False;
906 state->do_fill_buff = True;
910 /* Update according to current feeders. */
911 for (x = 0; x < state->grid_width; x++)
913 m_feeder *f = &state->feeders[x];
915 if (f->throttle) /* this is a delay tick, synced to frame. */
919 else if (f->remaining > 0) /* how many items are in the pipe */
923 if((state->use_pipe_p) && (!state->back_text_full_p)){
924 rval = (int) state->buf[f->pipe_loc];
925 if(++f->pipe_loc > (BUF_SIZE-1)){
927 /*fill_input(state);*/
929 rval = (rval % state->nglyphs);
932 rval = (random() % state->nglyphs);
934 g = state->glyph_map[rval] + 1;
935 insert_glyph (state, g, x, f->y);
937 if (f->y >= 0) /* bottom_feeder_p */
940 else /* if pipe is empty, insert spaces */
942 insert_glyph (state, 0, x, f->y);
943 if (f->y >= 0) /* bottom_feeder_p */
947 if ((random() % 10) == 0) /* randomly change throttle speed */
949 f->throttle = ((random() % 5) + (random() % 5));
956 redraw_cells (m_state *state, Bool active)
960 Bool use_back_p = False;
962 for (y = 0; y < state->grid_height; y++)
963 for (x = 0; x < state->grid_width; x++)
965 m_cell *cell = &state->cells[state->grid_width * y + x];
966 m_cell *back = &state->background[state->grid_width * y + x];
967 Bool cursor_p = (state->cursor_on &&
968 x == state->cursor_x &&
969 y == state->cursor_y);
974 if((state->start_reveal_back_p) &&
975 (back->glyph) && !(state->mode == TRACE_A ||
976 state->mode == TRACE_B ||
977 state->mode == TRACE_DONE)){
983 /* In trace-mode, the state of each cell is random unless we have
984 a match for this digit. */
985 if (active && (state->mode == TRACE_A ||
986 state->mode == TRACE_B ||
987 state->mode == TRACE_DONE))
989 int xx = x % strlen((char *) state->tracing);
990 Bool dead_p = state->tracing[xx] > 0;
992 if (y == 0 && x == xx && !use_back_p)
993 cell->glyph = (dead_p
994 ? state->glyph_map[state->tracing[xx]-'0'] + 1
996 else if (y == 0 && !use_back_p)
998 else if (!use_back_p)
999 cell->glyph = (dead_p ? 0 :
1000 (state->glyph_map[(random()%state->nglyphs)]
1010 if (cell->glyph == 0 && !cursor_p && !use_back_p)
1011 XFillRectangle (state->dpy, state->window, state->erase_gc,
1012 x * state->char_width,
1013 y * state->char_height,
1015 state->char_height);
1018 int g = (cursor_p ? CURSOR_GLYPH : cell->glyph);
1019 int cx = (g - 1) % CHAR_COLS;
1020 int cy = (g - 1) / CHAR_COLS;
1021 int map = ((cell->glow != 0 || cell->spinner) ? GLOW_MAP :
1024 XCopyArea (state->dpy, state->images[map],
1025 state->window, state->draw_gc,
1026 cx * state->char_width,
1027 cy * state->char_height,
1030 x * state->char_width,
1031 y * state->char_height);
1036 if (cell->glow > 0 && state->mode != NMAP && !use_back_p)
1041 else if (cell->glow < 0)
1044 if (cell->spinner && active && !use_back_p)
1046 cell->glyph = (state->glyph_map[(random()%state->nglyphs)] + 1);
1054 densitizer (m_state *state)
1056 /* Horrid kludge that converts percentages (density of screen coverage)
1057 to the parameter that actually controls this. I got this mapping
1058 empirically, on a 1024x768 screen. Sue me. */
1059 if (state->density < 10) return 85;
1060 else if (state->density < 15) return 60;
1061 else if (state->density < 20) return 45;
1062 else if (state->density < 25) return 25;
1063 else if (state->density < 30) return 20;
1064 else if (state->density < 35) return 15;
1065 else if (state->density < 45) return 10;
1066 else if (state->density < 50) return 8;
1067 else if (state->density < 55) return 7;
1068 else if (state->density < 65) return 5;
1069 else if (state->density < 80) return 3;
1070 else if (state->density < 90) return 2;
1076 hack_text (m_state *state)
1080 set_cursor (state, False);
1081 state->cursor_x = 0;
1082 state->cursor_y = 0;
1083 state->typing_scroll_p = False;
1084 state->typing_bold_p = False;
1085 state->typing_cursor_p = True;
1086 state->typing_stutter_p = False;
1087 state->typing_char_delay = 10000;
1088 state->typing_line_delay = 1500000;
1090 switch (state->mode)
1094 clear_spinners (state);
1095 if (state->mode == TRACE_TEXT_A)
1097 if (state->grid_width >= 52)
1099 ("Call trans opt: received. 2-19-98 13:24:18 REC:Log>\n"
1100 "Trace program: running\n");
1103 ("Call trans opt: received.\n2-19-98 13:24:18 REC:Log>\n"
1104 "Trace program: running\n");
1108 if (state->grid_width >= 52)
1110 ("Call trans opt: received. 9-18-99 14:32:21 REC:Log>\n"
1111 "WARNING: carrier anomaly\n"
1112 "Trace program: running\n");
1115 ("Call trans opt: received.\n9-18-99 14:32:21 REC:Log>\n"
1116 "WARNING: carrier anomaly\n"
1117 "Trace program: running\n");
1123 const char *s = "SYSTEM FAILURE\n";
1125 float cx = (state->grid_width - strlen(s) - 1) / 2 - 0.5;
1126 float cy = (state->grid_height / 2) - 1.3;
1131 XFillRectangle (state->dpy, state->window, state->erase_gc,
1132 cx * state->char_width,
1133 cy * state->char_height,
1134 strlen(s) * state->char_width,
1135 state->char_height * 1.6);
1137 for (i = -2; i < 3; i++)
1140 gcv.foreground = state->colors[i + 2];
1141 XChangeGC (state->dpy, state->scratch_gc, GCForeground, &gcv);
1142 XDrawRectangle (state->dpy, state->window, state->scratch_gc,
1143 cx * state->char_width - i,
1144 cy * state->char_height - i,
1145 strlen(s) * state->char_width + (2 * i),
1146 (state->char_height * 1.6) + (2 * i));
1149 /* If we don't clear these, part of the box may get overwritten */
1150 for (i = 0; i < state->grid_height * state->grid_width; i++)
1152 m_cell *cell = &state->cells[i];
1156 state->cursor_x = (state->grid_width - strlen(s) - 1) / 2;
1157 state->cursor_y = (state->grid_height / 2) - 1;
1158 if (state->cursor_x < 0) state->cursor_x = 0;
1159 if (state->cursor_y < 0) state->cursor_y = 0;
1162 state->typing_char_delay = 0;
1163 state->typing_cursor_p = False;
1169 clear_spinners (state);
1170 state->typing = ("\001Wake up, Neo...\n"
1171 "\001The Matrix has you...\n"
1172 "\001Follow the white rabbit.\n"
1174 "Knock, knock, Neo.\n");
1176 state->cursor_x = 4;
1177 state->cursor_y = 2;
1178 state->typing_char_delay = 0;
1179 state->typing_line_delay = 2000000;
1185 /* Note that what Trinity is using here is moderately accurate:
1186 She runs nmap (http://www.insecure.org/nmap/) then breaks in
1187 with a (hypothetical) program called "sshnuke" that exploits
1188 the (very real) SSHv1 CRC32 compensation attack detector bug
1189 (http://staff.washington.edu/dittrich/misc/ssh-analysis.txt).
1191 The command syntax of the power grid control software looks a
1192 lot like Cisco IOS to me. (IOS is a descendant of VMS.)
1195 clear_spinners (state);
1198 __extension__ /* don't warn about "string length is greater than
1199 the length ISO C89 compilers are required to
1205 "\001nmap -v -sS -O 10.2.2.2\n"
1206 "Starting nmap V. 2.54BETA25\n"
1207 "\010\010\010\010\010\010\010\010\010\010"
1208 "Insufficient responses for TCP sequencing (3), OS detection"
1209 " may be less accurate\n"
1210 "Interesting ports on 10.2.2.2:\n"
1211 "(The 1539 ports scanned but not shown below are in state:"
1213 "Port state service\n"
1216 "No exact OS matches for host\n"
1218 "Nmap run completed -- 1 IP address (1 host up) scanned\n"
1222 "\001sshnuke 10.2.2.2 -rootpw=\"Z1ON0101\"\n"
1223 "Connecting to 10.2.2.2:ssh ... "
1226 "Attempting to exploit SSHv1 CRC32 ... "
1229 "Resetting root password to \"Z1ON0101\".\n"
1231 "System open: Access Level <9>\n"
1236 "\001ssh 10.2.2.2 -l root\n"
1238 "root@10.2.2.2's password: "
1244 "\001disable grid nodes 21 - 48\n"
1246 "\002Warning: Disabling nodes 21-48 will disconnect sector 11"
1249 "\002 ARE YOU SURE? (y/n) "
1255 "\010\002Grid Node 21 offline...\n"
1256 "\010\002Grid Node 22 offline...\n"
1257 "\010\002Grid Node 23 offline...\n"
1258 "\010\002Grid Node 24 offline...\n"
1259 "\010\002Grid Node 25 offline...\n"
1260 "\010\002Grid Node 26 offline...\n"
1261 "\010\002Grid Node 27 offline...\n"
1262 "\010\002Grid Node 28 offline...\n"
1263 "\010\002Grid Node 29 offline...\n"
1264 "\010\002Grid Node 30 offline...\n"
1265 "\010\002Grid Node 31 offline...\n"
1266 "\010\002Grid Node 32 offline...\n"
1267 "\010\002Grid Node 33 offline...\n"
1268 "\010\002Grid Node 34 offline...\n"
1269 "\010\002Grid Node 35 offline...\n"
1270 "\010\002Grid Node 36 offline...\n"
1271 "\010\002Grid Node 37 offline...\n"
1272 "\010\002Grid Node 38 offline...\n"
1273 "\010\002Grid Node 39 offline...\n"
1274 "\010\002Grid Node 40 offline...\n"
1275 "\010\002Grid Node 41 offline...\n"
1276 "\010\002Grid Node 42 offline...\n"
1277 "\010\002Grid Node 43 offline...\n"
1278 "\010\002Grid Node 44 offline...\n"
1279 "\010\002Grid Node 45 offline...\n"
1280 "\010\002Grid Node 46 offline...\n"
1281 "\010\002Grid Node 47 offline...\n"
1282 "\010\002Grid Node 48 offline...\n"
1285 "\010\010\010\010\010\010\010\010"
1288 state->cursor_x = 0;
1289 state->cursor_y = state->grid_height - 3;
1290 state->typing_scroll_p = True;
1291 state->typing_char_delay = 0;
1292 state->typing_line_delay = 20000;
1301 state->typing_left_margin = state->cursor_x;
1302 state->typing_delay = state->typing_char_delay;
1303 if (state->typing_cursor_p)
1304 set_cursor (state, True);
1307 /* Stupid iPhone X bezel.
1308 #### This is the worst of all possible ways to do this! But how else?
1310 if (state->xgwa.width == 2436 || state->xgwa.height == 2436)
1311 switch (state->mode)
1318 int off = 5 * (state->small_p ? 2 : 1);
1319 if (state->xgwa.width > state->xgwa.height)
1321 state->typing_left_margin += off;
1322 state->cursor_x += off;
1326 state->cursor_y += off;
1335 Bool scrolled_p = False;
1336 unsigned char c, c1;
1337 int x = state->cursor_x;
1338 int y = state->cursor_y;
1341 c = ((unsigned char *) state->typing)[0];
1342 c1 = c ? ((unsigned char *) state->typing)[1] : 0;
1344 state->typing_delay = (!c || c1 == '\n'
1345 ? state->typing_line_delay
1346 : state->typing_char_delay);
1349 state->typing_delay = 0;
1354 if (state->typing_scroll_p &&
1356 x >= state->grid_width - 1))
1358 set_cursor (state, False);
1362 if (y >= state->grid_height-1)
1365 for (yy = 0; yy < state->grid_height-2; yy++)
1366 for (xx = 0; xx < state->grid_width; xx++)
1368 int ii = yy * state->grid_width + xx;
1369 int jj = (yy+1) * state->grid_width + xx;
1370 state->cells[ii] = state->cells[jj];
1371 state->cells[ii].changed = 1;
1373 /* clear bottom row */
1374 for (xx = 0; xx < state->grid_width; xx++)
1376 int ii = yy * state->grid_width + xx;
1377 state->cells[ii].glyph = 0;
1378 state->cells[ii].changed = 1;
1380 y--; /* move back up to bottom line */
1387 if (!state->typing_scroll_p)
1390 set_cursor (state, False);
1391 x = state->typing_left_margin;
1393 /* clear the line */
1394 i = state->grid_width * y;
1395 j = i + state->grid_width;
1398 state->cells[i].glyph = 0;
1399 state->cells[i].changed = 1;
1402 state->typing_bold_p = False;
1403 state->typing_stutter_p = False;
1407 else if (c == '\010')
1408 state->typing_delay += 500000;
1410 else if (c == '\001')
1412 state->typing_stutter_p = True;
1413 state->typing_bold_p = False;
1415 else if (c == '\002')
1416 state->typing_bold_p = True;
1418 else if (x < state->grid_width-1)
1420 m_cell *cell = &state->cells[state->grid_width * y + x];
1421 cell->glyph = char_map[c] + 1;
1422 if (c == ' ' || c == '\t') cell->glyph = 0;
1424 cell->glow = (state->typing_bold_p ? 127 : 0);
1430 if (x >= state->grid_width-1)
1431 x = state->grid_width-1;
1435 if (state->typing_stutter_p)
1437 if (state->typing_delay == 0)
1438 state->typing_delay = 20000;
1440 state->typing_delay += (0xFFFFFF & ((random() % 200000) + 1));
1443 /* If there's no delay after this character, just keep going. */
1444 if (state->typing_delay == 0)
1447 if (scrolled_p || x != state->cursor_x || y != state->cursor_y)
1449 set_cursor (state, False);
1450 state->cursor_x = x;
1451 state->cursor_y = y;
1452 if (state->typing_cursor_p)
1453 set_cursor (state, True);
1460 hack_matrix (m_state *state)
1464 switch (state->mode)
1466 case TRACE_DONE: case TRACE_FAIL:
1468 case TRACE_A: case TRACE_B:
1469 case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1475 /* Glow some characters. */
1476 if (!state->insert_bottom_p)
1478 int i = random() % (state->grid_width / 2);
1481 int yy = random() % state->grid_height;
1482 int xx = random() % state->grid_width;
1483 m_cell *cell = &state->cells[state->grid_width * yy + xx];
1484 if (cell->glyph && cell->glow == 0)
1486 cell->glow = random() % 10;
1492 /* Change some of the feeders. */
1493 for (x = 0; x < state->grid_width; x++)
1495 m_feeder *f = &state->feeders[x];
1496 Bool bottom_feeder_p;
1498 if (f->remaining > 0) /* never change if pipe isn't empty */
1501 if ((random() % densitizer(state)) != 0) /* then change N% of the time */
1504 f->remaining = 3 + (random() % state->grid_height);
1505 f->throttle = ((random() % 5) + (random() % 5));
1507 if ((random() % 4) != 0)
1510 if (state->mode == TRACE_A || state->mode == TRACE_B)
1511 bottom_feeder_p = True;
1512 else if (state->insert_top_p && state->insert_bottom_p)
1513 bottom_feeder_p = (random() & 1);
1515 bottom_feeder_p = state->insert_bottom_p;
1517 if (bottom_feeder_p)
1518 f->y = random() % (state->grid_height / 2);
1523 if (state->mode == MATRIX && (! (random() % 500)))
1524 init_spinners (state);
1528 static unsigned long
1529 xmatrix_draw (Display *dpy, Window window, void *closure)
1531 m_state *state = (m_state *) closure;
1533 if (state->typing_delay > 0)
1535 state->typing_delay -= state->delay;
1536 if (state->typing_delay < 0)
1537 state->typing_delay = 0;
1538 redraw_cells (state, False);
1539 return state->delay;
1542 switch (state->mode)
1544 case MATRIX: case DNA: case BINARY: case DEC: case HEX: case ASCII:
1545 case TRACE_A: case TRACE_B:
1546 feed_matrix (state);
1547 hack_matrix (state);
1555 feed_matrix (state);
1556 if (screen_blank_p (state))
1558 state->typing_delay = 500000;
1559 if(state->start_reveal_back_p){
1562 state->typing_delay = 5000000;
1563 state->start_reveal_back_p = False;
1564 state->back_text_full_p = False;
1565 /* for loop to move background to foreground */
1566 for (y = 0; y < state->grid_height; y++){
1567 for (x = 0; x < state->grid_width; x++){
1568 to = &state->cells[state->grid_width * y + x];
1569 back = &state->background[state->grid_width * y + x];
1570 to->glyph = back->glyph;
1571 to->changed = back->changed;
1577 switch (state->mode)
1579 case DRAIN_TRACE_A: set_mode (state, TRACE_TEXT_A); break;
1580 case DRAIN_TRACE_B: set_mode (state, TRACE_TEXT_B); break;
1581 case DRAIN_KNOCK: set_mode (state, KNOCK); break;
1582 case DRAIN_NMAP: set_mode (state, NMAP); break;
1583 case DRAIN_MATRIX: set_mode (state, state->def_mode); break;
1584 default: abort(); break;
1590 set_mode (state, state->def_mode);
1600 if (! state->typing) /* done typing */
1602 set_cursor (state, False);
1603 switch (state->mode)
1605 case TRACE_TEXT_A: set_mode (state, TRACE_A); break;
1606 case TRACE_TEXT_B: set_mode (state, TRACE_B); break;
1607 case TRACE_FAIL: set_mode (state, state->def_mode); break;
1608 case KNOCK: set_mode (state, state->def_mode); break;
1609 case NMAP: set_mode (state, state->def_mode); break;
1610 default: abort(); break;
1618 if (state->start_reveal_back_p){
1619 set_mode (state, DRAIN_MATRIX);
1621 if (state->mode == MATRIX &&
1622 state->knock_knock_p &&
1623 (! (random() % 10000)))
1625 if (! (random() % 5))
1626 set_mode (state, DRAIN_NMAP);
1628 set_mode (state, DRAIN_KNOCK);
1631 redraw_cells (state, True);
1636 static int ndens = 0;
1637 static int tdens = 0;
1643 ((double) (state->grid_width * state->grid_height))));
1646 printf ("density: %d%% (%d%%)\n", dens, (tdens / ndens));
1652 return state->delay;
1657 xmatrix_reshape (Display *dpy, Window window, void *closure,
1658 unsigned int w, unsigned int h)
1660 m_state *state = (m_state *) closure;
1661 int ow = state->grid_width;
1662 int oh = state->grid_height;
1663 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
1664 state->grid_width = state->xgwa.width / state->char_width;
1665 state->grid_height = state->xgwa.height / state->char_height;
1666 state->grid_width++;
1667 state->grid_height++;
1668 if (state->grid_width < 5) state->grid_width = 5;
1669 if (state->grid_height < 5) state->grid_height = 5;
1671 if (ow != state->grid_width ||
1672 oh != state->grid_height)
1674 m_cell *ncells = (m_cell *)
1675 calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1676 m_cell *nbackground = (m_cell *)
1677 calloc (sizeof(m_cell), state->grid_width * state->grid_height);
1678 m_feeder *nfeeders = (m_feeder *)
1679 calloc (sizeof(m_feeder), state->grid_width);
1682 /* fprintf(stderr, "resize: %d x %d ==> %d x %d\n",
1683 ow, oh, state->grid_width, state->grid_height); */
1685 for (y = 0; y < oh; y++)
1686 for (x = 0; x < ow; x++)
1687 if (x < ow && x < state->grid_width &&
1688 y < oh && y < state->grid_height){
1689 ncells[y * state->grid_width + x] =
1690 state->cells[y * ow + x];
1691 nbackground[y * state->grid_width + x] =
1692 state->background[y * ow + x];
1694 free (state->cells);
1695 free (state->background);
1696 state->cells = ncells;
1697 state->background = nbackground;
1699 x = (ow < state->grid_width ? ow : state->grid_width);
1700 for (i = 0; i < x; i++)
1701 nfeeders[i] = state->feeders[i];
1702 free (state->feeders);
1703 state->feeders = nfeeders;
1706 textclient_reshape (state->tc,
1709 state->grid_width - 2,
1710 state->grid_height - 1,
1715 xmatrix_event (Display *dpy, Window window, void *closure, XEvent *event)
1717 m_state *state = (m_state *) closure;
1719 if (event->xany.type == KeyPress)
1723 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1727 /*set_mode (state, DRAIN_MATRIX);*/
1729 state->back_text_full_p = True;
1730 state->start_reveal_back_p = True;
1733 case '+': case '=': case '>': case '.':
1734 state->density += 10;
1735 if (state->density > 100)
1736 state->density = 100;
1741 case '-': case '_': case '<': case ',':
1742 state->density -= 10;
1743 if (state->density < 0)
1749 case '[': case '(': case '{':
1750 state->insert_top_p = True;
1751 state->insert_bottom_p = False;
1754 case ']': case ')': case '}':
1755 state->insert_top_p = False;
1756 state->insert_bottom_p = True;
1759 case '\\': case '|':
1760 state->insert_top_p = True;
1761 state->insert_bottom_p = True;
1765 set_mode (state, DRAIN_TRACE_A);
1769 set_mode (state, DRAIN_TRACE_B);
1773 set_mode (state, DRAIN_KNOCK);
1777 set_mode (state, DRAIN_NMAP);
1785 if (screenhack_event_helper (dpy, window, event))
1787 set_mode (state, DRAIN_MATRIX);
1795 xmatrix_free (Display *dpy, Window window, void *closure)
1797 m_state *state = (m_state *) closure;
1799 textclient_close (state->tc);
1800 if (state->cursor_timer)
1801 XtRemoveTimeOut (state->cursor_timer);
1803 /* #### there's more to free here */
1808 static const char *xmatrix_defaults [] = {
1809 ".background: black",
1810 ".foreground: #00AA00",
1811 ".lowrez: true", /* Small font is unreadable at 5120x2880 */
1813 "*matrixFont: large",
1817 "*tracePhone: (312) 555-0690",
1821 "*knockKnock: True",
1824 "*program: xscreensaver-text --latin1",
1825 "*geometry: 960x720",
1829 static XrmOptionDescRec xmatrix_options [] = {
1830 { "-small", ".matrixFont", XrmoptionNoArg, "Small" },
1831 { "-large", ".matrixFont", XrmoptionNoArg, "Large" },
1832 { "-delay", ".delay", XrmoptionSepArg, 0 },
1833 { "-insert", ".insert", XrmoptionSepArg, 0 },
1834 { "-top", ".insert", XrmoptionNoArg, "top" },
1835 { "-bottom", ".insert", XrmoptionNoArg, "bottom" },
1836 { "-both", ".insert", XrmoptionNoArg, "both" },
1837 { "-density", ".density", XrmoptionSepArg, 0 },
1838 { "-trace", ".trace", XrmoptionNoArg, "True" },
1839 { "-no-trace", ".trace", XrmoptionNoArg, "False" },
1840 { "-crack", ".mode", XrmoptionNoArg, "crack"},
1841 { "-phone", ".tracePhone", XrmoptionSepArg, 0 },
1842 { "-mode", ".mode", XrmoptionSepArg, 0 },
1843 { "-dna", ".mode", XrmoptionNoArg, "DNA" },
1844 { "-binary", ".mode", XrmoptionNoArg, "binary" },
1845 { "-hexadecimal", ".mode", XrmoptionNoArg, "hexadecimal"},
1846 { "-decimal", ".mode", XrmoptionNoArg, "decimal"},
1847 { "-knock-knock", ".knockKnock", XrmoptionNoArg, "True" },
1848 { "-no-knock-knock", ".knockKnock", XrmoptionNoArg, "False" },
1849 { "-ascii", ".mode", XrmoptionNoArg, "ascii"},
1850 { "-pipe", ".usePipe", XrmoptionNoArg, "True" },
1851 { "-no-pipe", ".usePipe", XrmoptionNoArg, "False" },
1852 { "-program", ".program", XrmoptionSepArg, 0 },
1856 XSCREENSAVER_MODULE ("XMatrix", xmatrix)