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