http://ftp.x.org/contrib/applications/xscreensaver-3.23.tar.gz
[xscreensaver] / hacks / xmatrix.c
1 /* xscreensaver, Copyright (c) 1999 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 my
16  * memory of what the screens in the movie looked like, so my `xmatrix'
17  * does things differently.
18  *
19  *
20  *     ==========================================================
21  *
22  *         NOTE:
23  *
24  *         People just love to hack on this one.  I get sent
25  *         patches to this all the time saying, ``here, I made
26  *         it better!''  Mostly this hasn't been true.
27  *
28  *         If you've made changes to xmatrix, when you send me
29  *         your patch, please explain, in English, both *what*
30  *         your changes are, and *why* you think those changes
31  *         make this screensaver behave more like the displays
32  *         in the movie did.  I'd rather not have to read your
33  *         diffs to try and figure that out for myself...
34  *
35  *         In particular, note that the characters in the movie
36  *         were, in fact, low resolution and somewhat blurry/
37  *         washed out.  They also definitely scrolled a
38  *         character at a time, not a pixel at a time.
39  *
40  *     ==========================================================
41  *
42  * One thing I would like to add (to "-trace" mode) is an intro like at the
43  * beginning of the movie, where it printed
44  *
45  *        Call trans opt: received. 2-19-98 13:24:18 REC:Log>_
46  *
47  * then cleared, then
48  *
49  *        Trace program: running_
50  *
51  * then did the trace.
52  *
53  * I was also thinking of sometimes making the screen go blank and say
54  * "Knock, knock."  
55  *
56  * However, the problem with both of these ideas is, I made the number images
57  * by tooling around in GIMP until I got something that looked good (blurring
58  * and unblurring and enlarging and shrinking and blurring some more...) and I
59  * couldn't reproduce it if my life depended on it.  And to add anything other
60  * than roman digits/katakana, I'd need to matrixify a whole font...
61  */
62
63 #include "screenhack.h"
64 #include <stdio.h>
65 #include <X11/Xutil.h>
66
67 #ifdef HAVE_XPM
68 # include <X11/xpm.h>
69 # include "images/matrix.xpm"
70 # include "images/matrix2.xpm"
71 #endif
72
73 #include "images/matrix.xbm"
74 #include "images/matrix2.xbm"
75
76 #define CHAR_ROWS 27
77 #define CHAR_COLS 3
78 #define FADE_COL  0
79 #define PLAIN_COL 1
80 #define GLOW_COL  2
81
82 typedef struct {
83   unsigned int glyph   : 8;
84            int glow    : 8;
85   unsigned int changed : 1;
86   unsigned int spinner : 1;
87 } m_cell;
88
89 typedef struct {
90   int remaining;
91   int throttle;
92   int y;
93 } m_feeder;
94
95 typedef struct {
96   Display *dpy;
97   Window window;
98   XWindowAttributes xgwa;
99   GC draw_gc, erase_gc;
100   int grid_width, grid_height;
101   int char_width, char_height;
102   m_cell *cells;
103   m_feeder *feeders;
104   int nspinners;
105   Bool small_p;
106   Bool insert_top_p, insert_bottom_p;
107   Bool trace_p;
108   signed char *tracing;
109   int density;
110
111   Pixmap images;
112   int image_width, image_height;
113   int nglyphs;
114
115 } m_state;
116
117
118 static void
119 load_images (m_state *state)
120 {
121 #ifdef HAVE_XPM
122   if (!get_boolean_resource ("mono", "Boolean") &&
123       state->xgwa.depth > 1)
124     {
125       XpmAttributes xpmattrs;
126       int result;
127       xpmattrs.valuemask = 0;
128
129 # ifdef XpmCloseness
130       xpmattrs.valuemask |= XpmCloseness;
131       xpmattrs.closeness = 40000;
132 # endif
133 # ifdef XpmVisual
134       xpmattrs.valuemask |= XpmVisual;
135       xpmattrs.visual = state->xgwa.visual;
136 # endif
137 # ifdef XpmDepth
138       xpmattrs.valuemask |= XpmDepth;
139       xpmattrs.depth = state->xgwa.depth;
140 # endif
141 # ifdef XpmColormap
142       xpmattrs.valuemask |= XpmColormap;
143       xpmattrs.colormap = state->xgwa.colormap;
144 # endif
145
146       result = XpmCreatePixmapFromData (state->dpy, state->window,
147                                         (state->small_p
148                                          ? matrix2_xpm
149                                          : matrix_xpm),
150                                         &state->images, 0 /* mask */,
151                                         &xpmattrs);
152       if (!state->images || (result != XpmSuccess && result != XpmColorError))
153         state->images = 0;
154
155       state->image_width = xpmattrs.width;
156       state->image_height = xpmattrs.height;
157       state->nglyphs = CHAR_ROWS;
158     }
159   else
160 #endif /* !HAVE_XPM */
161     {
162       unsigned long fg, bg;
163       state->image_width =  (state->small_p ? matrix2_width  : matrix_width);
164       state->image_height = (state->small_p ? matrix2_height : matrix_height);
165       state->nglyphs = CHAR_ROWS;
166
167       fg = get_pixel_resource("foreground", "Foreground",
168                               state->dpy, state->xgwa.colormap);
169       bg = get_pixel_resource("background", "Background",
170                               state->dpy, state->xgwa.colormap);
171       state->images =
172         XCreatePixmapFromBitmapData (state->dpy, state->window,
173                                      (state->small_p
174                                       ? (char *) matrix2_bits
175                                       : (char *) matrix_bits),
176                                      state->image_width, state->image_height,
177                                      bg, fg, state->xgwa.depth);
178     }
179 }
180
181
182 static void
183 flip_images (m_state *state)
184 {
185   XImage *im = XGetImage (state->dpy, state->images, 0, 0,
186                           state->image_width, state->image_height,
187                           ~0L, (state->xgwa.depth > 1 ? ZPixmap : XYPixmap));
188   int x, y, i;
189   int w = state->image_width / CHAR_COLS;
190   unsigned long *row = (unsigned long *) malloc (sizeof(*row) * w);
191
192   for (y = 0; y < state->image_height; y++)
193     for (i = 0; i < CHAR_COLS; i++)
194       {
195         for (x = 0; x < w; x++)
196           row[x] = XGetPixel (im, (i * w) + x, y);
197         for (x = 0; x < w; x++)
198           XPutPixel (im, (i * w) + x, y, row[w - x - 1]);
199       }
200
201   XPutImage (state->dpy, state->images, state->draw_gc, im, 0, 0, 0, 0,
202              state->image_width, state->image_height);
203   XDestroyImage (im);
204   free (row);
205 }
206
207
208 static void
209 init_spinners (m_state *state)
210 {
211   int i = state->nspinners;
212   int x, y;
213   m_cell *cell;
214
215   for (y = 0; y < state->grid_height; y++)
216     for (x = 0; x < state->grid_width; x++)
217       {
218         cell = &state->cells[state->grid_width * y + x];
219         cell->spinner = 0;
220       }
221
222   while (--i > 0)
223     {
224       x = random() % state->grid_width;
225       y = random() % state->grid_height;
226       cell = &state->cells[state->grid_width * y + x];
227       cell->spinner = 1;
228     }
229 }
230
231
232 static void
233 init_trace (m_state *state)
234 {
235   char *s = get_string_resource ("tracePhone", "TracePhone");
236   char *s2, *s3;
237   int i;
238   if (!s)
239     goto FAIL;
240
241   state->tracing = (char *) malloc (strlen (s) + 1);
242   s3 = state->tracing;
243
244   for (s2 = s; *s2; s2++)
245     if (*s2 >= '0' && *s2 <= '9')
246       *s3++ = *s2;
247   *s3 = 0;
248
249   if (s3 == (char *) state->tracing)
250     goto FAIL;
251
252   for (i = 0; i < strlen(state->tracing); i++)
253     state->tracing[i] = -state->tracing[i];
254   state->nglyphs = 10;
255   flip_images (state);
256
257   return;
258
259  FAIL:
260   fprintf (stderr, "%s: bad phone number: \"%s\".\n",
261            progname, s ? s : "(null)");
262
263   if (s) free (s);
264   if (state->tracing) free (state->tracing);
265   state->tracing = 0;
266   state->trace_p = False;
267 }
268
269
270 static m_state *
271 init_matrix (Display *dpy, Window window)
272 {
273   XGCValues gcv;
274   char *insert;
275   m_state *state = (m_state *) calloc (sizeof(*state), 1);
276   state->dpy = dpy;
277   state->window = window;
278
279   XGetWindowAttributes (dpy, window, &state->xgwa);
280
281   state->small_p = get_boolean_resource ("small", "Boolean");
282   load_images (state);
283
284   gcv.foreground = get_pixel_resource("foreground", "Foreground",
285                                       state->dpy, state->xgwa.colormap);
286   gcv.background = get_pixel_resource("background", "Background",
287                                       state->dpy, state->xgwa.colormap);
288   state->draw_gc = XCreateGC (state->dpy, state->window,
289                               GCForeground|GCBackground, &gcv);
290   gcv.foreground = gcv.background;
291   state->erase_gc = XCreateGC (state->dpy, state->window,
292                                GCForeground|GCBackground, &gcv);
293
294   state->char_width =  state->image_width  / CHAR_COLS;
295   state->char_height = state->image_height / CHAR_ROWS;
296
297   state->grid_width  = state->xgwa.width  / state->char_width;
298   state->grid_height = state->xgwa.height / state->char_height;
299   state->grid_width++;
300   state->grid_height++;
301
302   state->cells = (m_cell *)
303     calloc (sizeof(m_cell), state->grid_width * state->grid_height);
304   state->feeders = (m_feeder *) calloc (sizeof(m_feeder), state->grid_width);
305
306   state->density = get_integer_resource ("density", "Integer");
307
308   insert = get_string_resource("insert", "Insert");
309   if (insert && !strcmp(insert, "top"))
310     {
311       state->insert_top_p = True;
312       state->insert_bottom_p = False;
313     }
314   else if (insert && !strcmp(insert, "bottom"))
315     {
316       state->insert_top_p = False;
317       state->insert_bottom_p = True;
318     }
319   else if (insert && !strcmp(insert, "both"))
320     {
321       state->insert_top_p = True;
322       state->insert_bottom_p = True;
323     }
324   else
325     {
326       if (insert && *insert)
327         fprintf (stderr,
328                  "%s: `insert' must be `top', `bottom', or `both', not `%s'\n",
329                  progname, insert);
330       state->insert_top_p = False;
331       state->insert_bottom_p = True;
332     }
333
334   state->nspinners = get_integer_resource ("spinners", "Integer");
335
336   if (insert)
337     free (insert);
338
339   state->trace_p = get_boolean_resource ("trace", "Trace");
340   if (state->trace_p)
341     init_trace (state);
342   else
343     init_spinners (state);
344
345   return state;
346 }
347
348
349 static void
350 insert_glyph (m_state *state, int glyph, int x, int y)
351 {
352   Bool bottom_feeder_p = (y >= 0);
353   m_cell *from, *to;
354
355   if (y >= state->grid_height)
356     return;
357
358   if (bottom_feeder_p)
359     {
360       to = &state->cells[state->grid_width * y + x];
361     }
362   else
363     {
364       for (y = state->grid_height-1; y > 0; y--)
365         {
366           from = &state->cells[state->grid_width * (y-1) + x];
367           to   = &state->cells[state->grid_width * y     + x];
368           to->glyph   = from->glyph;
369           to->glow    = from->glow;
370           to->changed = 1;
371         }
372       to = &state->cells[x];
373     }
374
375   to->glyph = glyph;
376   to->changed = 1;
377
378   if (!to->glyph)
379     ;
380   else if (bottom_feeder_p)
381     to->glow = 1 + (random() % 2);
382   else
383     to->glow = 0;
384 }
385
386
387 static void
388 feed_matrix (m_state *state)
389 {
390   int x;
391
392   /* Update according to current feeders. */
393   for (x = 0; x < state->grid_width; x++)
394     {
395       m_feeder *f = &state->feeders[x];
396
397       if (f->throttle)          /* this is a delay tick, synced to frame. */
398         {
399           f->throttle--;
400         }
401       else if (f->remaining > 0)        /* how many items are in the pipe */
402         {
403           int g = (random() % state->nglyphs) + 1;
404           insert_glyph (state, g, x, f->y);
405           f->remaining--;
406           if (f->y >= 0)  /* bottom_feeder_p */
407             f->y++;
408         }
409       else                              /* if pipe is empty, insert spaces */
410         {
411           insert_glyph (state, 0, x, f->y);
412           if (f->y >= 0)  /* bottom_feeder_p */
413             f->y++;
414         }
415
416       if ((random() % 10) == 0)         /* randomly change throttle speed */
417         {
418           f->throttle = ((random() % 5) + (random() % 5));
419         }
420     }
421 }
422
423 static int
424 densitizer (m_state *state)
425 {
426   /* Horrid kludge that converts percentages (density of screen coverage)
427      to the parameter that actually controls this.  I got this mapping
428      empirically, on a 1024x768 screen.  Sue me. */
429   if      (state->density < 10) return 85;
430   else if (state->density < 15) return 60;
431   else if (state->density < 20) return 45;
432   else if (state->density < 25) return 25;
433   else if (state->density < 30) return 20;
434   else if (state->density < 35) return 15;
435   else if (state->density < 45) return 10;
436   else if (state->density < 50) return 8;
437   else if (state->density < 55) return 7;
438   else if (state->density < 65) return 5;
439   else if (state->density < 80) return 3;
440   else if (state->density < 90) return 2;
441   else return 1;
442 }
443
444
445 static void
446 hack_matrix (m_state *state)
447 {
448   int x;
449
450   /* Glow some characters. */
451   if (!state->insert_bottom_p)
452     {
453       int i = random() % (state->grid_width / 2);
454       while (--i > 0)
455         {
456           int x = random() % state->grid_width;
457           int y = random() % state->grid_height;
458           m_cell *cell = &state->cells[state->grid_width * y + x];
459           if (cell->glyph && cell->glow == 0)
460             {
461               cell->glow = random() % 10;
462               cell->changed = 1;
463             }
464         }
465     }
466
467   /* Change some of the feeders. */
468   for (x = 0; x < state->grid_width; x++)
469     {
470       m_feeder *f = &state->feeders[x];
471       Bool bottom_feeder_p;
472
473       if (f->remaining > 0)     /* never change if pipe isn't empty */
474         continue;
475
476       if ((random() % densitizer(state)) != 0) /* then change N% of the time */
477         continue;
478
479       f->remaining = 3 + (random() % state->grid_height);
480       f->throttle = ((random() % 5) + (random() % 5));
481
482       if ((random() % 4) != 0)
483         f->remaining = 0;
484
485       if (state->trace_p)
486         bottom_feeder_p = True;
487       if (state->insert_top_p && state->insert_bottom_p)
488         bottom_feeder_p = (random() & 1);
489       else
490         bottom_feeder_p = state->insert_bottom_p;
491
492       if (bottom_feeder_p)
493         f->y = random() % (state->grid_height / 2);
494       else
495         f->y = -1;
496     }
497
498   if (!state->trace_p &&
499       ! (random() % 500))
500     init_spinners (state);
501 }
502
503
504 static void
505 draw_matrix (m_state *state)
506 {
507   int x, y;
508   int count = 0;
509
510   feed_matrix (state);
511   hack_matrix (state);
512
513   for (y = 0; y < state->grid_height; y++)
514     for (x = 0; x < state->grid_width; x++)
515       {
516         m_cell *cell = &state->cells[state->grid_width * y + x];
517
518         if (cell->glyph)
519           count++;
520
521         if (state->trace_p)
522           {
523             int xx = x % strlen(state->tracing);
524             Bool dead_p = state->tracing[xx] > 0;
525
526             if (y == 0 && x == xx)
527               cell->glyph = (dead_p ? (state->tracing[xx]-'0'+1) : 0);
528             else if (y == 0)
529               cell->glyph = 0;
530             else
531               cell->glyph = (dead_p ? 0 : (random() % state->nglyphs) + 1);
532
533             cell->changed = 1;
534           }
535
536         if (!cell->changed)
537           continue;
538
539         if (cell->glyph == 0)
540           XFillRectangle (state->dpy, state->window, state->erase_gc,
541                           x * state->char_width,
542                           y * state->char_height,
543                           state->char_width,
544                           state->char_height);
545         else
546           XCopyArea (state->dpy, state->images, state->window, state->draw_gc,
547                      ((cell->glow > 0 || cell->spinner)
548                       ? (state->char_width * GLOW_COL)
549                       : (cell->glow == 0
550                          ? (state->char_width * PLAIN_COL)
551                          : (state->char_width * FADE_COL))),
552                      (cell->glyph - 1) * state->char_height,
553                      state->char_width, state->char_height,
554                      x * state->char_width,
555                      y * state->char_height);
556
557         cell->changed = 0;
558
559         if (cell->glow > 0)
560           {
561             cell->glow--;
562             cell->changed = 1;
563           }
564         else if (cell->glow < 0)
565           {
566             cell->glow++;
567             if (cell->glow == 0)
568               cell->glyph = 0;
569             cell->changed = 1;
570           }
571
572         if (cell->spinner)
573           {
574             cell->glyph = random() % CHAR_ROWS;
575             cell->changed = 1;
576           }
577       }
578
579   if (state->trace_p)
580     {
581       Bool any = False;
582       int i;
583       for (i = 0; i < strlen(state->tracing); i++)
584         if (state->tracing[i] < 0) any = True;
585
586       if (!any)
587         {
588           XSync (state->dpy, False);
589           sleep (3);
590           state->trace_p = False;
591           state->nglyphs = CHAR_ROWS;
592           flip_images (state);
593           free (state->tracing);
594           state->tracing = 0;
595         }
596       else if ((random() % 10) == 0)
597         {
598           int x = random() % strlen(state->tracing);
599           if (state->tracing[x] < 0)
600             state->tracing[x] = -state->tracing[x];
601         }
602     }
603
604 #if 0
605   {
606     static int i = 0;
607     static int ndens = 0;
608     static int tdens = 0;
609     i++;
610     if (i > 50)
611       {
612         int dens = (100.0 *
613                     (((double)count) /
614                      ((double) (state->grid_width * state->grid_height))));
615         tdens += dens;
616         ndens++;
617         printf ("density: %d%% (%d%%)\n", dens, (tdens / ndens));
618         i = 0;
619       }
620   }
621 #endif
622
623 }
624
625 \f
626 char *progclass = "XMatrix";
627
628 char *defaults [] = {
629   ".background:            black",
630   ".foreground:            green",
631   "*small:                 False",
632   "*delay:                 10000",
633   "*insert:                both",
634   "*trace:                 false",
635   "*tracePhone:            (212) 555-0690",
636   "*spinners:              5",
637   "*density:               75",
638   0
639 };
640
641 XrmOptionDescRec options [] = {
642   { "-small",           ".small",               XrmoptionNoArg, "True" },
643   { "-large",           ".small",               XrmoptionNoArg, "False" },
644   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
645   { "-top",             ".insert",              XrmoptionNoArg, "top" },
646   { "-bottom",          ".insert",              XrmoptionNoArg, "bottom" },
647   { "-both",            ".insert",              XrmoptionNoArg, "both" },
648   { "-density",         ".density",             XrmoptionSepArg, 0 },
649   { "-trace",           ".trace",               XrmoptionNoArg, "True" },
650   { "-phone",           ".tracePhone",          XrmoptionSepArg, 0 },
651   { 0, 0, 0, 0 }
652 };
653
654
655 void
656 screenhack (Display *dpy, Window window)
657 {
658   m_state *state = init_matrix (dpy, window);
659   int delay = get_integer_resource ("delay", "Integer");
660   while (1)
661     {
662       draw_matrix (state);
663       XSync (dpy, False);
664       screenhack_handle_events (dpy);
665       if (delay) usleep (delay);
666     }
667 }