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