http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / memscroller.c
1 /* xscreensaver, Copyright (c) 2002, 2004, 2006 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  * Memscroller -- scrolls a dump of its own RAM across the screen.
12  */
13
14 #include "screenhack.h"
15 #include <stdio.h>
16
17 #ifdef HAVE_XSHM_EXTENSION
18 #include "xshm.h"
19 #endif
20
21 #undef countof
22 #define countof(x) (sizeof(x)/sizeof(*(x)))
23
24 typedef struct {
25   int which;
26   XRectangle rect;
27   XImage *image;
28   int rez;
29   int speed;
30   int scroll_tick;
31   unsigned int value;
32   unsigned char *data;
33   int count_zero;
34 } scroller;
35
36 typedef struct {
37   Display *dpy;
38   Window window;
39   XWindowAttributes xgwa;
40   GC draw_gc, erase_gc, text_gc;
41   XFontStruct *fonts[6];
42   int border;
43
44   enum { SEED_RAM, SEED_RANDOM, SEED_FILE } seed_mode;
45   enum { DRAW_COLOR, DRAW_MONO } draw_mode;
46
47   char *filename;
48   FILE *in;
49
50   int nscrollers;
51   scroller *scrollers;
52
53 # ifdef HAVE_XSHM_EXTENSION
54   Bool shm_p;
55   XShmSegmentInfo shm_info;
56 # endif
57
58   int delay;
59
60 } state;
61
62
63 static void reshape_memscroller (state *st);
64
65
66 static void *
67 memscroller_init (Display *dpy, Window window)
68 {
69   int i;
70   XGCValues gcv;
71   state *st = (state *) calloc (1, sizeof (*st));
72   char *s;
73   st->dpy = dpy;
74   st->window = window;
75   st->delay = get_integer_resource (dpy, "delay", "Integer");
76
77   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
78
79   /* Fill up the colormap with random colors.
80      We don't actually use these explicitly, but in 8-bit mode,
81      they will be used implicitly by the random image bits. */
82   {
83     int ncolors = 255;
84     XColor colors[256];
85     make_random_colormap (st->dpy, st->xgwa.visual, st->xgwa.colormap,
86                           colors, &ncolors, True, True, 0, False);
87   }
88
89   st->border = get_integer_resource (dpy, "borderSize", "BorderSize");
90
91   {
92     int i;
93     int nfonts = countof (st->fonts);
94     for (i = nfonts-1; i >= 0; i--)
95       {
96         char *fontname;
97         char res[20];
98         sprintf (res, "font%d", i+1);
99         fontname = get_string_resource (dpy, res, "Font");
100         /* Each resource can be a comma-separated list of font names.
101            We use the first one that exists. */
102         if (fontname && *fontname) 
103           {
104             char *f2 = strdup(fontname);
105             char *f, *token = f2;
106             while ((f = strtok(token, ",")) && !st->fonts[i])
107               {
108                 token = 0;
109                 while (*f == ' ' || *f == '\t') f++;
110                 st->fonts[i] = XLoadQueryFont (dpy, f);
111               }
112             free (f2);
113             if (!st->fonts[i] && i < nfonts-1)
114               {
115                 fprintf (stderr, "%s: unable to load font: \"%s\"\n",
116                          progname, fontname);
117                 st->fonts[i] = st->fonts[i+1];
118               }
119           }
120       }
121
122     if (!st->fonts[0])
123       st->fonts[0] = XLoadQueryFont (dpy, "fixed");
124
125     if (!st->fonts[0])
126       {
127         fprintf (stderr, "%s: unable to load any fonts!", progname);
128         exit (1);
129       }
130   }
131
132   gcv.line_width = st->border;
133
134   gcv.background = get_pixel_resource(st->dpy, st->xgwa.colormap,
135                                       "background", "Background");
136   gcv.foreground = get_pixel_resource(st->dpy, st->xgwa.colormap,
137                                       "textColor", "Foreground");
138   st->text_gc = XCreateGC (st->dpy, st->window,
139                            GCForeground|GCBackground, &gcv);
140
141   gcv.foreground = get_pixel_resource(st->dpy, st->xgwa.colormap,
142                                       "foreground", "Foreground");
143   st->draw_gc = XCreateGC (st->dpy, st->window,
144                            GCForeground|GCBackground|GCLineWidth,
145                            &gcv);
146   gcv.foreground = gcv.background;
147   st->erase_gc = XCreateGC (st->dpy, st->window,
148                             GCForeground|GCBackground, &gcv);
149
150
151   s = get_string_resource (dpy, "drawMode", "DrawMode");
152   if (!s || !*s || !strcasecmp (s, "color"))
153     st->draw_mode = DRAW_COLOR;
154   else if (!strcasecmp (s, "mono"))
155     st->draw_mode = DRAW_MONO;
156   else
157     {
158       fprintf (stderr, "%s: drawMode must be 'mono' or 'color', not '%s'\n",
159                progname, s);
160       exit (1);
161     }
162   if (s) free (s);
163   s = 0;
164
165
166   st->filename = get_string_resource (dpy, "filename", "Filename");
167
168   if (!st->filename ||
169       !*st->filename ||
170       !strcasecmp (st->filename, "(ram)") ||
171       !strcasecmp (st->filename, "(mem)") ||
172       !strcasecmp (st->filename, "(memory)"))
173     st->seed_mode = SEED_RAM;
174   else if (st->filename &&
175            (!strcasecmp (st->filename, "(rand)") ||
176             !strcasecmp (st->filename, "(random)")))
177     st->seed_mode = SEED_RANDOM;
178   else
179     st->seed_mode = SEED_FILE;
180
181   st->nscrollers = 3;
182   st->scrollers = (scroller *) calloc (st->nscrollers, sizeof(scroller));
183
184   for (i = 0; i < st->nscrollers; i++)
185     {
186       scroller *sc = &st->scrollers[i];
187       int max_height = 4096;
188
189       sc->which = i;
190       sc->speed = i+1;
191
192       sc->image = 0;
193 # ifdef HAVE_XSHM_EXTENSION
194       st->shm_p = get_boolean_resource (dpy, "useSHM", "Boolean");
195       if (st->shm_p)
196         {
197           sc->image = create_xshm_image (st->dpy, st->xgwa.visual,
198                                          st->xgwa.depth,
199                                          ZPixmap, 0, &st->shm_info,
200                                          1, max_height);
201           if (! sc->image)
202             st->shm_p = False;
203         }
204 # endif /* HAVE_XSHM_EXTENSION */
205
206       if (!sc->image)
207         sc->image = XCreateImage (st->dpy, st->xgwa.visual, st->xgwa.depth,
208                                   ZPixmap, 0, 0, 1, max_height, 8, 0);
209
210       if (sc->image && !sc->image->data)
211         sc->image->data = (char *)
212           malloc (sc->image->bytes_per_line * sc->image->height + 1);
213
214       if (!sc->image || !sc->image->data)
215         {
216           fprintf (stderr, "%s: out of memory (allocating 1x%d image)\n",
217                    progname, sc->image->height);
218           exit (1);
219         }
220     }
221
222   reshape_memscroller (st);
223   return st;
224 }
225
226
227 static void
228 reshape_memscroller (state *st)
229 {
230   int i;
231
232   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
233
234   for (i = 0; i < st->nscrollers; i++)
235     {
236       scroller *sc = &st->scrollers[i];
237
238       if (i == 0)
239         {
240           sc->rez = 6;  /* #### */
241
242           sc->rect.width  = (((int) (st->xgwa.width * 0.8)
243                               / sc->rez) * sc->rez);
244           sc->rect.height = (((int) (st->xgwa.height * 0.3)
245                               / sc->rez) * sc->rez);
246
247           sc->rect.x = (st->xgwa.width  - sc->rect.width)  / 2;
248           sc->rect.y = (st->xgwa.height - sc->rect.height) / 2;
249         }
250       else
251         {
252           scroller *sc0 = &st->scrollers[i-1];
253           sc->rez = sc0->rez * 1.8;
254
255           sc->rect.x      = sc0->rect.x;
256           sc->rect.y      = (sc0->rect.y + sc0->rect.height + st->border
257                              + (st->border + 2) * 7);
258           sc->rect.width  = sc0->rect.width;
259           sc->rect.height = (((int) (st->xgwa.height * 0.1)
260                               / sc->rez) * sc->rez);
261         }
262
263       XDrawRectangle (st->dpy, st->window, st->draw_gc,
264                       sc->rect.x - st->border*2,
265                       sc->rect.y - st->border*2,
266                       sc->rect.width  + st->border*4,
267                       sc->rect.height + st->border*4);
268     }
269 }
270
271
272
273 static void
274 open_file (state *st)
275 {
276   if (st->in)
277     {
278       fclose (st->in);
279       st->in = 0;
280     }
281
282   st->in = fopen (st->filename, "r");
283   if (!st->in)
284     {
285       char buf[1024];
286       sprintf (buf, "%s: %s", progname, st->filename);
287       perror (buf);
288       exit (1);
289     }
290 }
291
292
293 static unsigned int
294 more_bits (state *st, scroller *sc)
295 {
296   static unsigned char *lomem = 0;
297   static unsigned char *himem = 0;
298   unsigned char r, g, b;
299
300   /* vv: Each incoming byte rolls through all 4 bytes of this (it is sc->value)
301          This is the number displayed at the top.
302      pv: the pixel color value.  incoming bytes land in R,G,B, or maybe just G.
303    */
304   unsigned int vv, pv;
305
306   unsigned int rmsk = st->scrollers[0].image->red_mask;
307   unsigned int gmsk = st->scrollers[0].image->green_mask;
308   unsigned int bmsk = st->scrollers[0].image->blue_mask;
309   unsigned int amsk = ~(rmsk | gmsk | bmsk);
310
311   vv = sc->value;
312
313   /* Pack RGB into a pixel according to the XImage component masks;
314      set the remaining bits to 1 for the benefit of HAVE_COCOA alpha.
315    */
316 # undef PACK
317 # define PACK() ((((r << 24) | (r << 16) | (r << 8) | r) & rmsk) | \
318                  (((g << 24) | (g << 16) | (g << 8) | g) & gmsk) | \
319                  (((b << 24) | (b << 16) | (b << 8) | b) & bmsk) | \
320                  amsk)
321
322   switch (st->seed_mode)
323     {
324     case SEED_RAM:
325       if (himem == 0)
326         {
327           lomem = (unsigned char *) progname; /* not first malloc, but early */
328           himem = (unsigned char *)           /* not last malloc, but late */
329             st->scrollers[st->nscrollers-1].image->data;
330         }
331
332       if (sc->data < lomem)
333         sc->data = lomem;
334
335 # ifdef HAVE_SBRK  /* re-get it each time through */
336       /* "The brk and sbrk functions are historical curiosities left over
337          from earlier days before the advent of virtual memory management."
338             -- sbrk(2) man page on MacOS
339        */
340       himem = ((unsigned char *) sbrk(0)) - (2 * sizeof(void *));
341 # endif
342
343       if (!lomem || !himem) 
344         {
345           /* bad craziness! give up! */
346           st->seed_mode = SEED_RANDOM;
347           return 0;
348         }
349
350       /* I don't understand what's going on there, but on MacOS X, we're
351          getting insane values for lomem and himem (both Xlib and HAVE_COCOA).
352          Does malloc() draw from more than one heap? */
353       if ((unsigned long) himem - (unsigned long) lomem > 0x0FFFFFFF) {
354 # if 0
355         fprintf (stderr, "%s: wonky: 0x%08x - 0x%08x = 0x%08x\n", progname,
356                  (unsigned int) himem,  (unsigned int) lomem,
357                  (unsigned int) himem - (unsigned int) lomem);
358 # endif
359         himem = lomem + 0xFFFF;
360       }
361
362       if (lomem >= himem) abort();
363
364     RETRY:
365       if (sc->data >= himem)
366         sc->data = lomem;
367
368       switch (st->draw_mode)
369         {
370         case DRAW_COLOR:
371           r = *sc->data++;
372           g = *sc->data++;
373           b = *sc->data++;
374           vv = (vv << 24) | (r << 16) | (g << 8) | b;
375           break;
376         case DRAW_MONO:
377           r = 0;
378           g = *sc->data++;
379           b = 0;
380           vv = (vv << 8) | g;
381           break;
382         default:
383           abort();
384         }
385
386       pv = PACK();
387
388       /* avoid having many seconds of blackness: truncate zeros at 24K.
389        */
390       if (vv == 0)
391         sc->count_zero++;
392       else
393         sc->count_zero = 0;
394       if (sc->count_zero > 1024 * (st->draw_mode == DRAW_COLOR ? 24 : 8))
395         goto RETRY;
396
397       break;
398
399     case SEED_RANDOM:
400       vv = random();
401       switch (st->draw_mode)
402         {
403         case DRAW_COLOR:
404           r = (vv >> 16) & 0xFF;
405           g = (vv >>  8) & 0xFF;
406           b = (vv      ) & 0xFF;
407           break;
408         case DRAW_MONO:
409           r = 0;
410           g = vv & 0xFF;
411           b = 0;
412           break;
413         default:
414           abort();
415         }
416       pv = PACK();
417       break;
418
419     case SEED_FILE:
420       {
421         int i;
422
423   /* this one returns only bytes from the file */
424 # define GETC(V) \
425             do { \
426               i = fgetc (st->in); \
427             } while (i == EOF \
428                      ? (open_file (st), 1) \
429                      : 0); \
430             V = i
431
432   /* this one returns a null at EOF -- else we hang on zero-length files */
433 # undef GETC
434 # define GETC(V) \
435             i = fgetc (st->in); \
436             if (i == EOF) { i = 0; open_file (st); } \
437             V = i
438
439         if (!st->in)
440           open_file (st);
441
442         switch (st->draw_mode)
443           {
444           case DRAW_COLOR:
445             GETC(r);
446             GETC(g);
447             GETC(b);
448             vv = (vv << 24) | (r << 16) | (g << 8) | b;
449             break;
450           case DRAW_MONO:
451             r = 0;
452             GETC(g);
453             b = 0;
454             vv = (vv << 8) | g;
455             break;
456           default:
457             abort();
458           }
459 # undef GETC
460         pv = PACK();
461       }
462       break;
463
464     default:
465       abort();
466     }
467
468 # undef PACK
469
470   sc->value = vv;
471   return pv;
472 }
473
474
475 static void
476 draw_string (state *st)
477 {
478   char buf[40];
479   int direction, ascent, descent;
480   int bot = st->scrollers[0].rect.y;
481   const char *fmt = "%08X";
482   int i;
483
484   /* Draw the first font that fits.
485    */
486   for (i = 0; i < countof (st->fonts); i++)
487     {
488       XCharStruct overall;
489       int x, y, w, h;
490
491       if (! st->fonts[i]) continue;
492
493       sprintf (buf, fmt, 0);
494       XTextExtents (st->fonts[i], buf, strlen(buf), 
495                     &direction, &ascent, &descent, &overall);
496       sprintf (buf, "%08X", st->scrollers[0].value);
497
498       w = overall.width;
499       h = ascent + descent + 1;
500       x = (st->xgwa.width - w) / 2;
501       y = (bot - h) / 2;
502
503       if (y + h + 10 <= bot && x > -10)
504         {
505           XSetFont (st->dpy, st->text_gc, st->fonts[i]->fid);
506           XFillRectangle (st->dpy, st->window, st->erase_gc,
507                           x-w, y, w*3, h);
508           XDrawString (st->dpy, st->window, st->text_gc,
509                        x, y + ascent, buf, strlen(buf));
510           break;
511         }
512     }
513 }
514
515
516 static unsigned long
517 memscroller_draw (Display *dpy, Window window, void *closure)
518 {
519   state *st = (state *) closure;
520   int i;
521   draw_string (st);
522
523   for (i = 0; i < st->nscrollers; i++)
524     {
525       scroller *sc = &st->scrollers[i];
526       int j;
527
528       XCopyArea (st->dpy, st->window, st->window, st->draw_gc,
529                  sc->rect.x + sc->speed, sc->rect.y,
530                  sc->rect.width - sc->speed, sc->rect.height,
531                  sc->rect.x, sc->rect.y);
532
533       if (sc->scroll_tick == 0)
534         {
535           int top = ((sc->image->bytes_per_line * sc->rect.height) /
536                      (4 * sc->rez));
537           unsigned int *out = (unsigned int *) sc->image->data;
538           for (j = 0; j < top; j++)
539             {
540               unsigned int v = more_bits(st, sc);
541               int k;
542               for (k = 0; k < sc->rez; k++)
543                 *out++ = v;
544             }
545         }
546
547       sc->scroll_tick++;
548       if (sc->scroll_tick * sc->speed >= sc->rez)
549         sc->scroll_tick = 0;
550
551       for (j = 0; j < sc->speed; j++)
552         {
553 # ifdef HAVE_XSHM_EXTENSION
554           if (st->shm_p)
555             XShmPutImage (st->dpy, st->window, st->draw_gc, sc->image,
556                           0, 0,
557                           sc->rect.x + sc->rect.width - sc->image->width - j,
558                           sc->rect.y,
559                           sc->rect.width, sc->rect.height,
560                           False);
561           else
562 # endif /* HAVE_XSHM_EXTENSION */
563             XPutImage (st->dpy, st->window, st->draw_gc, sc->image,
564                        0, 0,
565                        sc->rect.x + sc->rect.width - sc->image->width - j,
566                        sc->rect.y,
567                        sc->rect.width, sc->rect.height);
568         }
569     }
570
571   return st->delay;
572 }
573
574
575 static void
576 memscroller_reshape (Display *dpy, Window window, void *closure, 
577                  unsigned int w, unsigned int h)
578 {
579   state *st = (state *) closure;
580   XClearWindow (st->dpy, st->window);
581   reshape_memscroller (st);
582 }
583
584 static Bool
585 memscroller_event (Display *dpy, Window window, void *closure, XEvent *event)
586 {
587   return False;
588 }
589
590
591 static void
592 memscroller_free (Display *dpy, Window window, void *closure)
593 {
594 }
595
596
597 static const char *memscroller_defaults [] = {
598   ".background:            black",
599   "*drawMode:              color",
600   "*fpsSolid:              true",
601   "*fpsTop:                true",
602   "*filename:              (RAM)",
603   ".textColor:             #00FF00",
604   ".foreground:            #00FF00",
605   "*borderSize:            2",
606
607 #ifdef HAVE_COCOA
608   ".font1:                 OCR A Std 192, Lucida Console 192",
609   ".font2:                 OCR A Std 144, Lucida Console 144",
610   ".font3:                 OCR A Std 128, Lucida Console 128",
611   ".font4:                 OCR A Std 96,  Lucida Console 96",
612   ".font5:                 OCR A Std 48,  Lucida Console 48",
613   ".font6:                 OCR A Std 24,  Lucida Console 24",
614 #else  /* !HAVE_COCOA */
615   ".font1:                 -*-courier-bold-r-*-*-*-1440-*-*-m-*-*-*",
616   ".font2:                 -*-courier-bold-r-*-*-*-960-*-*-m-*-*-*",
617   ".font3:                 -*-courier-bold-r-*-*-*-480-*-*-m-*-*-*",
618   ".font4:                 -*-courier-bold-r-*-*-*-320-*-*-m-*-*-*",
619   ".font5:                 -*-courier-bold-r-*-*-*-180-*-*-m-*-*-*",
620   ".font6:                 fixed",
621 #endif /* !HAVE_COCOA */
622
623   "*delay:                 10000",
624   "*offset:                0",
625   0
626 };
627
628 static XrmOptionDescRec memscroller_options [] = {
629   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
630   { "-font",            ".font",                XrmoptionSepArg, 0 },
631   { "-filename",        ".filename",            XrmoptionSepArg, 0 },
632   { "-color",           ".drawMode",            XrmoptionNoArg, "color"    },
633   { "-mono",            ".drawMode",            XrmoptionNoArg, "mono"     },
634   { "-ram",             ".filename",            XrmoptionNoArg, "(RAM)"    },
635   { "-random",          ".filename",            XrmoptionNoArg, "(RANDOM)" },
636   { 0, 0, 0, 0 }
637 };
638
639 XSCREENSAVER_MODULE ("MemScroller", memscroller)