11ff592b2f4ded2fd0060e343710d1c86c0b3551
[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 #include "screenhack.h"
21 #include <stdio.h>
22 #include <X11/Xutil.h>
23
24 #ifdef HAVE_XPM
25 # include <X11/xpm.h>
26 # include "images/matrix.xpm"
27 # include "images/matrix2.xpm"
28 #endif
29
30 #include "images/matrix.xbm"
31 #include "images/matrix2.xbm"
32
33 #define CHAR_ROWS 27
34 #define CHAR_COLS 3
35 #define FADE_COL  0
36 #define PLAIN_COL 1
37 #define GLOW_COL  2
38
39 typedef struct {
40   unsigned int glyph   : 8;
41            int glow    : 8;
42   unsigned int changed : 1;
43   unsigned int spinner : 1;
44 } m_cell;
45
46 typedef struct {
47   int remaining;
48   int throttle;
49   int y;
50 } m_feeder;
51
52 typedef struct {
53   Display *dpy;
54   Window window;
55   XWindowAttributes xgwa;
56   GC draw_gc, erase_gc;
57   int grid_width, grid_height;
58   int char_width, char_height;
59   m_cell *cells;
60   m_feeder *feeders;
61   Bool small_p;
62   Bool insert_top_p, insert_bottom_p;
63   int density;
64
65   Pixmap images;
66   int image_width, image_height;
67   int nglyphs;
68
69 } m_state;
70
71
72 static void
73 load_images (m_state *state)
74 {
75 #ifdef HAVE_XPM
76   if (!get_boolean_resource ("mono", "Boolean") &&
77       state->xgwa.depth > 1)
78     {
79       XpmAttributes xpmattrs;
80       int result;
81       xpmattrs.valuemask = 0;
82
83 # ifdef XpmCloseness
84       xpmattrs.valuemask |= XpmCloseness;
85       xpmattrs.closeness = 40000;
86 # endif
87 # ifdef XpmVisual
88       xpmattrs.valuemask |= XpmVisual;
89       xpmattrs.visual = state->xgwa.visual;
90 # endif
91 # ifdef XpmDepth
92       xpmattrs.valuemask |= XpmDepth;
93       xpmattrs.depth = state->xgwa.depth;
94 # endif
95 # ifdef XpmColormap
96       xpmattrs.valuemask |= XpmColormap;
97       xpmattrs.colormap = state->xgwa.colormap;
98 # endif
99
100       result = XpmCreatePixmapFromData (state->dpy, state->window,
101                                         (state->small_p
102                                          ? matrix2_xpm
103                                          : matrix_xpm),
104                                         &state->images, 0 /* mask */,
105                                         &xpmattrs);
106       if (!state->images || (result != XpmSuccess && result != XpmColorError))
107         state->images = 0;
108
109       state->image_width = xpmattrs.width;
110       state->image_height = xpmattrs.height;
111       state->nglyphs = CHAR_ROWS;
112     }
113   else
114 #endif /* !HAVE_XPM */
115     {
116       unsigned long fg, bg;
117       state->image_width =  (state->small_p ? matrix2_width  : matrix_width);
118       state->image_height = (state->small_p ? matrix2_height : matrix_height);
119       state->nglyphs = CHAR_ROWS;
120
121       fg = get_pixel_resource("foreground", "Foreground",
122                               state->dpy, state->xgwa.colormap);
123       bg = get_pixel_resource("background", "Background",
124                               state->dpy, state->xgwa.colormap);
125       state->images =
126         XCreatePixmapFromBitmapData (state->dpy, state->window,
127                                      (state->small_p
128                                       ? (char *) matrix2_bits
129                                       : (char *) matrix_bits),
130                                      state->image_width, state->image_height,
131                                      bg, fg, state->xgwa.depth);
132     }
133 }
134
135
136 static void
137 init_spinners (m_state *state)
138 {
139   int i = get_integer_resource ("spinners", "Integer");
140   int x, y;
141   m_cell *cell;
142
143   for (y = 0; y < state->grid_height; y++)
144     for (x = 0; x < state->grid_width; x++)
145       {
146         cell = &state->cells[state->grid_width * y + x];
147         cell->spinner = 0;
148       }
149
150   while (--i > 0)
151     {
152       x = random() % state->grid_width;
153       y = random() % state->grid_height;
154       cell = &state->cells[state->grid_width * y + x];
155       cell->spinner = 1;
156     }
157 }
158
159
160 static m_state *
161 init_matrix (Display *dpy, Window window)
162 {
163   XGCValues gcv;
164   char *insert;
165   m_state *state = (m_state *) calloc (sizeof(*state), 1);
166   state->dpy = dpy;
167   state->window = window;
168
169   XGetWindowAttributes (dpy, window, &state->xgwa);
170
171   state->small_p = get_boolean_resource ("small", "Boolean");
172   load_images (state);
173
174   gcv.foreground = get_pixel_resource("foreground", "Foreground",
175                                       state->dpy, state->xgwa.colormap);
176   gcv.background = get_pixel_resource("background", "Background",
177                                       state->dpy, state->xgwa.colormap);
178   state->draw_gc = XCreateGC (state->dpy, state->window,
179                               GCForeground|GCBackground, &gcv);
180   gcv.foreground = gcv.background;
181   state->erase_gc = XCreateGC (state->dpy, state->window,
182                                GCForeground|GCBackground, &gcv);
183
184   state->char_width =  state->image_width  / CHAR_COLS;
185   state->char_height = state->image_height / CHAR_ROWS;
186
187   state->grid_width  = state->xgwa.width  / state->char_width;
188   state->grid_height = state->xgwa.height / state->char_height;
189   state->grid_width++;
190   state->grid_height++;
191
192   state->cells = (m_cell *)
193     calloc (sizeof(m_cell), state->grid_width * state->grid_height);
194   state->feeders = (m_feeder *) calloc (sizeof(m_feeder), state->grid_width);
195
196   state->density = get_integer_resource ("density", "Integer");
197
198   insert = get_string_resource("insert", "Insert");
199   if (insert && !strcmp(insert, "top"))
200     {
201       state->insert_top_p = True;
202       state->insert_bottom_p = False;
203     }
204   else if (insert && !strcmp(insert, "bottom"))
205     {
206       state->insert_top_p = False;
207       state->insert_bottom_p = True;
208     }
209   else if (insert && !strcmp(insert, "both"))
210     {
211       state->insert_top_p = True;
212       state->insert_bottom_p = True;
213     }
214   else
215     {
216       if (insert && *insert)
217         fprintf (stderr,
218                  "%s: `insert' must be `top', `bottom', or `both', not `%s'\n",
219                  progname, insert);
220       state->insert_top_p = False;
221       state->insert_bottom_p = True;
222     }
223
224   if (insert)
225     free (insert);
226
227   init_spinners (state);
228
229   return state;
230 }
231
232
233 static void
234 insert_glyph (m_state *state, int glyph, int x, int y)
235 {
236   Bool bottom_feeder_p = (y >= 0);
237   m_cell *from, *to;
238
239   if (y >= state->grid_height)
240     return;
241
242   if (bottom_feeder_p)
243     {
244       to = &state->cells[state->grid_width * y + x];
245     }
246   else
247     {
248       for (y = state->grid_height-1; y > 0; y--)
249         {
250           from = &state->cells[state->grid_width * (y-1) + x];
251           to   = &state->cells[state->grid_width * y     + x];
252           to->glyph   = from->glyph;
253           to->glow    = from->glow;
254           to->changed = 1;
255         }
256       to = &state->cells[x];
257     }
258
259   to->glyph = glyph;
260   to->changed = 1;
261
262   if (!to->glyph)
263     ;
264   else if (bottom_feeder_p)
265     to->glow = 1 + (random() % 2);
266   else
267     to->glow = 0;
268 }
269
270
271 static void
272 feed_matrix (m_state *state)
273 {
274   int x;
275
276   /* Update according to current feeders. */
277   for (x = 0; x < state->grid_width; x++)
278     {
279       m_feeder *f = &state->feeders[x];
280
281       if (f->throttle)          /* this is a delay tick, synced to frame. */
282         {
283           f->throttle--;
284         }
285       else if (f->remaining > 0)        /* how many items are in the pipe */
286         {
287           int g = (random() % state->nglyphs) + 1;
288           insert_glyph (state, g, x, f->y);
289           f->remaining--;
290           if (f->y >= 0)  /* bottom_feeder_p */
291             f->y++;
292         }
293       else                              /* if pipe is empty, insert spaces */
294         {
295           insert_glyph (state, 0, x, f->y);
296           if (f->y >= 0)  /* bottom_feeder_p */
297             f->y++;
298         }
299
300       if ((random() % 10) == 0)         /* randomly change throttle speed */
301         {
302           f->throttle = ((random() % 5) + (random() % 5));
303         }
304     }
305 }
306
307 static int
308 densitizer (m_state *state)
309 {
310   /* Horrid kludge that converts percentages (density of screen coverage)
311      to the parameter that actually controls this.  I got this mapping
312      empirically, on a 1024x768 screen.  Sue me. */
313   if      (state->density < 10) return 85;
314   else if (state->density < 15) return 60;
315   else if (state->density < 20) return 45;
316   else if (state->density < 25) return 25;
317   else if (state->density < 30) return 20;
318   else if (state->density < 35) return 15;
319   else if (state->density < 45) return 10;
320   else if (state->density < 50) return 8;
321   else if (state->density < 55) return 7;
322   else if (state->density < 65) return 5;
323   else if (state->density < 80) return 3;
324   else if (state->density < 90) return 2;
325   else return 1;
326 }
327
328
329 static void
330 hack_matrix (m_state *state)
331 {
332   int x;
333
334   /* Glow some characters. */
335   if (!state->insert_bottom_p)
336     {
337       int i = random() % (state->grid_width / 2);
338       while (--i > 0)
339         {
340           int x = random() % state->grid_width;
341           int y = random() % state->grid_height;
342           m_cell *cell = &state->cells[state->grid_width * y + x];
343           if (cell->glyph && cell->glow == 0)
344             {
345               cell->glow = random() % 10;
346               cell->changed = 1;
347             }
348         }
349     }
350
351   /* Change some of the feeders. */
352   for (x = 0; x < state->grid_width; x++)
353     {
354       m_feeder *f = &state->feeders[x];
355       Bool bottom_feeder_p;
356
357       if (f->remaining > 0)     /* never change if pipe isn't empty */
358         continue;
359
360       if ((random() % densitizer(state)) != 0) /* then change N% of the time */
361         continue;
362
363       f->remaining = 3 + (random() % state->grid_height);
364       f->throttle = ((random() % 5) + (random() % 5));
365
366       if ((random() % 4) != 0)
367         f->remaining = 0;
368
369       if (state->insert_top_p && state->insert_bottom_p)
370         bottom_feeder_p = (random() & 1);
371       else
372         bottom_feeder_p = state->insert_bottom_p;
373
374       if (bottom_feeder_p)
375         f->y = random() % (state->grid_height / 2);
376       else
377         f->y = -1;
378     }
379
380   if (! (random() % 500))
381     init_spinners (state);
382 }
383
384
385 static void
386 draw_matrix (m_state *state)
387 {
388   int x, y;
389   int count = 0;
390
391   feed_matrix (state);
392   hack_matrix (state);
393
394   for (y = 0; y < state->grid_height; y++)
395     for (x = 0; x < state->grid_width; x++)
396       {
397         m_cell *cell = &state->cells[state->grid_width * y + x];
398
399         if (cell->glyph)
400           count++;
401
402         if (!cell->changed)
403           continue;
404
405         if (cell->glyph == 0)
406           XFillRectangle (state->dpy, state->window, state->erase_gc,
407                           x * state->char_width,
408                           y * state->char_height,
409                           state->char_width,
410                           state->char_height);
411         else
412           XCopyArea (state->dpy, state->images, state->window, state->draw_gc,
413                      ((cell->glow > 0 || cell->spinner)
414                       ? (state->char_width * GLOW_COL)
415                       : (cell->glow == 0
416                          ? (state->char_width * PLAIN_COL)
417                          : (state->char_width * FADE_COL))),
418                      (cell->glyph - 1) * state->char_height,
419                      state->char_width, state->char_height,
420                      x * state->char_width,
421                      y * state->char_height);
422
423         cell->changed = 0;
424
425         if (cell->glow > 0)
426           {
427             cell->glow--;
428             cell->changed = 1;
429           }
430         else if (cell->glow < 0)
431           {
432             cell->glow++;
433             if (cell->glow == 0)
434               cell->glyph = 0;
435             cell->changed = 1;
436           }
437
438         if (cell->spinner)
439           {
440             cell->glyph = random() % CHAR_ROWS;
441             cell->changed = 1;
442           }
443       }
444
445 #if 0
446   {
447     static int i = 0;
448     static int ndens = 0;
449     static int tdens = 0;
450     i++;
451     if (i > 50)
452       {
453         int dens = (100.0 *
454                     (((double)count) /
455                      ((double) (state->grid_width * state->grid_height))));
456         tdens += dens;
457         ndens++;
458         printf ("density: %d%% (%d%%)\n", dens, (tdens / ndens));
459         i = 0;
460       }
461   }
462 #endif
463
464 }
465
466 \f
467 char *progclass = "XMatrix";
468
469 char *defaults [] = {
470   ".background:            black",
471   ".foreground:            green",
472   "*small:                 False",
473   "*delay:                 10000",
474   "*insert:                both",
475   "*spinners:              5",
476   "*density:               75",
477   0
478 };
479
480 XrmOptionDescRec options [] = {
481   { "-small",           ".small",               XrmoptionNoArg, "True" },
482   { "-large",           ".small",               XrmoptionNoArg, "False" },
483   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
484   { "-top",             ".insert",              XrmoptionNoArg, "top" },
485   { "-bottom",          ".insert",              XrmoptionNoArg, "bottom" },
486   { "-both",            ".insert",              XrmoptionNoArg, "both" },
487   { "-density",         ".density",             XrmoptionSepArg, 0 },
488   { 0, 0, 0, 0 }
489 };
490
491
492 void
493 screenhack (Display *dpy, Window window)
494 {
495   m_state *state = init_matrix (dpy, window);
496   int delay = get_integer_resource ("delay", "Integer");
497   while (1)
498     {
499       draw_matrix (state);
500       XSync (dpy, False);
501       screenhack_handle_events (dpy);
502       if (delay) usleep (delay);
503     }
504 }