From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / xlockmore.c
1 /* xlockmore.c --- xscreensaver compatibility layer for xlockmore modules.
2  * xscreensaver, Copyright (c) 1997-2017 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  *
12  * This file, along with xlockmore.h, make it possible to compile an xlockmore
13  * module into a standalone program, and thus use it with xscreensaver.
14  * By Jamie Zawinski <jwz@jwz.org> on 10-May-97; based on the ideas
15  * in the older xlock.h by Charles Hannum <mycroft@ai.mit.edu>.  (I had
16  * to redo it, since xlockmore has diverged so far from xlock...)
17  */
18
19 #include "xlockmoreI.h"
20 #include "screenhack.h"
21
22 #ifndef HAVE_JWXYZ
23 # include <X11/Intrinsic.h>
24 #endif /* !HAVE_JWXYZ */
25
26 #include <assert.h>
27 #include <float.h>
28
29 #define countof(x) (sizeof((x))/sizeof(*(x)))
30
31 #define MAX_COLORS (1L<<13)
32
33 extern struct xscreensaver_function_table *xscreensaver_function_table;
34
35 extern const char *progclass;
36
37 extern struct xlockmore_function_table xlockmore_function_table;
38
39 static void *xlockmore_init (Display *, Window, 
40                              struct xlockmore_function_table *);
41 static unsigned long xlockmore_draw (Display *, Window, void *);
42 static void xlockmore_reshape (Display *, Window, void *, 
43                                unsigned int w, unsigned int h);
44 static Bool xlockmore_event (Display *, Window, void *, XEvent *);
45 static void xlockmore_free (Display *, Window, void *);
46
47
48 void
49 xlockmore_setup (struct xscreensaver_function_table *xsft, void *arg)
50 {
51   struct xlockmore_function_table *xlmft = 
52     (struct xlockmore_function_table *) arg;
53   int i, j;
54   char *s;
55   XrmOptionDescRec *new_options;
56   char **new_defaults;
57   const char *xlockmore_defaults;
58   ModeSpecOpt *xlockmore_opts = xlmft->opts;
59
60 # undef ya_rand_init
61   ya_rand_init (0);
62
63   xsft->init_cb    = (void *(*) (Display *, Window)) xlockmore_init;
64   xsft->draw_cb    = xlockmore_draw;
65   xsft->reshape_cb = xlockmore_reshape;
66   xsft->event_cb   = xlockmore_event;
67   xsft->free_cb    = xlockmore_free;
68
69   progclass = xlmft->progclass;
70   xlockmore_defaults = xlmft->defaults;
71
72   /* Translate the xlockmore `opts[]' argument to a form that
73      screenhack.c expects.
74    */
75   new_options = (XrmOptionDescRec *) 
76     calloc (xlockmore_opts->numopts*3 + 100, sizeof (*new_options));
77
78   for (i = 0; i < xlockmore_opts->numopts; i++)
79     {
80       XrmOptionDescRec *old = &xlockmore_opts->opts[i];
81       XrmOptionDescRec *new = &new_options[i];
82
83       if (old->option[0] == '-')
84         new->option = old->option;
85       else
86         {
87           /* Convert "+foo" to "-no-foo". */
88           new->option = (char *) malloc (strlen(old->option) + 5);
89           strcpy (new->option, "-no-");
90           strcat (new->option, old->option + 1);
91         }
92
93       new->specifier = strrchr (old->specifier, '.');
94       if (!new->specifier) abort();
95
96       new->argKind = old->argKind;
97       new->value = old->value;
98     }
99
100   /* Add extra args, if they're mentioned in the defaults... */
101   {
102     char *args[] = { "-count", "-cycles", "-delay", "-ncolors",
103                      "-size", "-font", "-wireframe", "-use3d", "-useSHM" };
104     for (j = 0; j < countof(args); j++)
105       if (strstr(xlockmore_defaults, args[j]+1))
106         {
107           XrmOptionDescRec *new = &new_options[i++];
108           new->option = args[j];
109           new->specifier = strdup(args[j]);
110           new->specifier[0] = '.';
111           if (!strcmp(new->option, "-wireframe"))
112             {
113               new->argKind = XrmoptionNoArg;
114               new->value = "True";
115               new = &new_options[i++];
116               new->option = "-no-wireframe";
117               new->specifier = new_options[i-2].specifier;
118               new->argKind = XrmoptionNoArg;
119               new->value = "False";
120             }
121           else if (!strcmp(new->option, "-use3d"))
122             {
123               new->option = "-3d";
124               new->argKind = XrmoptionNoArg;
125               new->value = "True";
126               new = &new_options[i++];
127               new->option = "-no-3d";
128               new->specifier = new_options[i-2].specifier;
129               new->argKind = XrmoptionNoArg;
130               new->value = "False";
131             }
132           else if (!strcmp(new->option, "-useSHM"))
133             {
134               new->option = "-shm";
135               new->argKind = XrmoptionNoArg;
136               new->value = "True";
137               new = &new_options[i++];
138               new->option = "-no-shm";
139               new->specifier = new_options[i-2].specifier;
140               new->argKind = XrmoptionNoArg;
141               new->value = "False";
142             }
143           else
144             {
145               new->argKind = XrmoptionSepArg;
146               new->value = 0;
147             }
148         }
149   }
150
151
152
153   /* Construct the kind of `defaults' that screenhack.c expects from
154      the xlockmore `vars[]' argument.
155    */
156   i = 0;
157
158   new_defaults = (char **) calloc (1, xlockmore_opts->numvarsdesc * 10 + 1000);
159
160   /* Put on the PROGCLASS.background/foreground resources. */
161   s = (char *) malloc(50);
162   *s = 0;
163 # ifndef HAVE_JWXYZ
164   strcpy (s, progclass);
165 # endif
166   strcat (s, ".background: black");
167   new_defaults [i++] = s;
168
169   s = (char *) malloc(50);
170   *s = 0;
171 # ifndef HAVE_JWXYZ
172   strcpy (s, progclass);
173 # endif
174   strcat (s, ".foreground: white");
175   new_defaults [i++] = s;
176
177   /* Copy the lines out of the `xlockmore_defaults' var and into this array. */
178   s = strdup (xlockmore_defaults);
179   while (s && *s)
180     {
181       new_defaults [i++] = s;
182       s = strchr(s, '\n');
183       if (s)
184         *s++ = 0;
185     }
186
187   /* Copy the defaults out of the `xlockmore_opts->' variable. */
188   for (j = 0; j < xlockmore_opts->numvarsdesc; j++)
189     {
190       const char *def = xlockmore_opts->vars[j].def;
191
192       if (!def) abort();
193       if (!*def) abort();
194       if (strlen(def) > 1000) abort();
195
196       s = (char *) malloc (strlen (xlockmore_opts->vars[j].name) +
197                            strlen (def) + 10);
198       strcpy (s, "*");
199       strcat (s, xlockmore_opts->vars[j].name);
200       strcat (s, ": ");
201       strcat (s, def);
202       new_defaults [i++] = s;
203
204       /* Go through the list of resources and print a warning if there
205          are any duplicates.
206        */
207       {
208         char *onew = strdup (xlockmore_opts->vars[j].name);
209         const char *new = onew;
210         int k;
211         if ((s = strrchr (new, '.'))) new = s+1;
212         if ((s = strrchr (new, '*'))) new = s+1;
213         for (k = 0; k < i-1; k++)
214           {
215             char *oold = strdup (new_defaults[k]);
216             const char *old = oold;
217             if ((s = strchr (oold, ':'))) *s = 0;
218             if ((s = strrchr (old, '.'))) old = s+1;
219             if ((s = strrchr (old, '*'))) old = s+1;
220             if (!strcasecmp (old, new))
221               {
222                 fprintf (stderr,
223                          "%s: duplicate resource \"%s\": "
224                          "set in both DEFAULTS and vars[]\n",
225                          progname, old);
226               }
227             free (oold);
228           }
229         free (onew);
230       }
231     }
232
233   new_defaults [i] = 0;
234
235   xsft->progclass = progclass;
236   xsft->options   = new_options;
237   xsft->defaults  = (const char * const *) new_defaults;
238 }
239
240
241 static void
242 xlockmore_free_screens (ModeInfo *mi)
243 {
244   struct xlockmore_function_table *xlmft = mi->xlmft;
245
246   /* Optimization: xlockmore_read_resources calls this lots on first start. */
247   if (!xlmft->got_init)
248     return;
249
250   /* Order is important here: */
251
252   /* 1. Call free_## for all screens. */
253   if (xlmft->hack_free_state) {
254     int old_screen = mi->screen_number;
255     for (mi->screen_number = 0; mi->screen_number < XLOCKMORE_NUM_SCREENS;
256          ++mi->screen_number) {
257       xlmft->hack_free_state (mi);
258     }
259     mi->screen_number = old_screen;
260   }
261
262   /* 2. Call release_##, if it exists. */
263   if (xlmft->hack_release)
264     xlmft->hack_release (mi);
265
266   /* 3. Free the state array. */
267   if (xlmft->state_array) {
268     free(*xlmft->state_array);
269     *xlmft->state_array = NULL;
270     xlmft->state_array = NULL;
271   }
272
273   /* 4. Pretend FreeAllGL(mi) gets called here. */
274
275   mi->xlmft->got_init = 0;
276 }
277
278
279 static void
280 xlockmore_read_resources (ModeInfo *mi)
281 {
282   Display *dpy = mi->dpy;
283   ModeSpecOpt *xlockmore_opts = mi->xlmft->opts;
284   int i;
285   for (i = 0; i < xlockmore_opts->numvarsdesc; i++)
286     {
287       void  *var   = xlockmore_opts->vars[i].var;
288       Bool  *var_b = (Bool *)  var;
289       char **var_c = (char **) var;
290       int   *var_i = (int *) var;
291       float *var_f = (float *) var;
292
293       /* If any of the options changed, stop this hack's other instances. */
294       switch (xlockmore_opts->vars[i].type)
295         {
296         case t_String:
297           {
298             char *c = get_string_resource (dpy, xlockmore_opts->vars[i].name,
299                                            xlockmore_opts->vars[i].classname);
300             if ((!c && !*var_c) || (c && *var_c && !strcmp(c, *var_c))) {
301               free (c);
302             } else {
303               xlockmore_free_screens (mi);
304               free (*var_c);
305               *var_c = c;
306             }
307           }
308           break;
309         case t_Float:
310           {
311             float f = get_float_resource (dpy, xlockmore_opts->vars[i].name,
312                                           xlockmore_opts->vars[i].classname);
313             float frac = fabsf(*var_f) * (1.0f / (1l << (FLT_MANT_DIG - 4)));
314             if (f < *var_f - frac || f > *var_f + frac) {
315               xlockmore_free_screens (mi);
316               *var_f = f;
317             }
318           }
319           break;
320         case t_Int:
321           {
322             int ii = get_integer_resource (dpy, xlockmore_opts->vars[i].name,
323                                           xlockmore_opts->vars[i].classname);
324             if (ii != *var_i) {
325               xlockmore_free_screens (mi);
326               *var_i = ii;
327             }
328           }
329           break;
330         case t_Bool:
331           {
332             Bool b = get_boolean_resource (dpy, xlockmore_opts->vars[i].name,
333                                            xlockmore_opts->vars[i].classname);
334             if (b != *var_b) {
335               xlockmore_free_screens (mi);
336               *var_b = b;
337             }
338           }
339           break;
340         default:
341           abort ();
342         }
343     }
344 }
345
346
347 /* We keep a list of all of the screen numbers that are in use and not
348    yet freed so that they can have sensible screen numbers.  If three
349    displays are created (0, 1, 2) and then #1 is closed, then the fourth
350    display will be given the now-unused display number 1. (Everything in
351    here assumes a 1:1 Display/Screen mapping.)
352
353    XLOCKMORE_NUM_SCREENS is the most number of live displays at one time. So
354    if it's 64, then we'll blow up if the system has 64 monitors and also has
355    System Preferences open (the small preview window).
356
357    Note that xlockmore-style savers tend to allocate big structures, so
358    setting XLOCKMORE_NUM_SCREENS to 1000 will waste a few megabytes.  Also
359    most (all?) of them assume that the number of screens never changes, so
360    dynamically expanding this array won't work.
361  */
362
363
364 static void *
365 xlockmore_init (Display *dpy, Window window, 
366                 struct xlockmore_function_table *xlmft)
367 {
368   ModeInfo *mi = (ModeInfo *) calloc (1, sizeof(*mi));
369   XGCValues gcv;
370   XColor color;
371   int i;
372   Bool root_p;
373
374   if (! xlmft)
375     abort();
376   mi->xlmft = xlmft;
377
378   mi->dpy = dpy;
379   mi->window = window;
380   XGetWindowAttributes (dpy, window, &mi->xgwa);
381   
382   /* In Cocoa and Android-based xscreensaver, as well as with DEBUG_PAIR,
383      hacks run in the same address space, so each one needs to get its own
384      screen number.
385
386      Find the first empty slot in live_displays and plug us in.
387    */
388   {
389     const int size = XLOCKMORE_NUM_SCREENS;
390     int i;
391     for (i = 0; i < size; i++) {
392       if (! (xlmft->live_displays & (1 << i)))
393         break;
394     }
395     if (i >= size) abort();
396     xlmft->live_displays |= 1ul << i;
397     xlmft->got_init &= ~(1ul << i);
398     mi->screen_number = i;
399   }
400
401   root_p = (window == RootWindowOfScreen (mi->xgwa.screen));
402
403 #ifndef HAVE_JWXYZ
404
405   /* Everybody gets motion events, just in case. */
406   XSelectInput (dpy, window, (mi->xgwa.your_event_mask | PointerMotionMask));
407
408 #endif /* !HAVE_JWXYZ */
409
410   if (mi->xlmft->hack_release) {
411 /*
412     fprintf (
413       stderr,
414       "%s: WARNING: hack_release is not usually recommended; see MI_INIT.\n",
415       progname);
416 */
417   }
418
419   color.flags = DoRed|DoGreen|DoBlue;
420   color.red = color.green = color.blue = 0;
421   if (!XAllocColor(dpy, mi->xgwa.colormap, &color))
422     abort();
423   mi->black = color.pixel;
424   color.red = color.green = color.blue = 0xFFFF;
425   if (!XAllocColor(dpy, mi->xgwa.colormap, &color))
426     abort();
427   mi->white = color.pixel;
428
429   if (mono_p)
430     {
431       static XColor colors[2];
432     MONO:
433       mi->npixels = 2;
434       if (! mi->pixels)
435         mi->pixels = (unsigned long *) 
436           calloc (mi->npixels, sizeof (*mi->pixels));
437       if (!mi->colors)
438         mi->colors = (XColor *) 
439           calloc (mi->npixels, sizeof (*mi->colors));
440       colors[0].flags = DoRed|DoGreen|DoBlue;
441       colors[1].flags = DoRed|DoGreen|DoBlue;
442       colors[0].red = colors[0].green = colors[0].blue = 0;
443       colors[1].red = colors[1].green = colors[1].blue = 0xFFFF;
444       mi->writable_p = False;
445     }
446   else
447     {
448       mi->npixels = get_integer_resource (dpy, "ncolors", "Integer");
449       if (mi->npixels <= 0)
450         mi->npixels = 64;
451       else if (mi->npixels > MAX_COLORS)
452         mi->npixels = MAX_COLORS;
453
454       mi->colors = (XColor *) calloc (mi->npixels, sizeof (*mi->colors));
455
456       mi->writable_p = mi->xlmft->want_writable_colors;
457
458       switch (mi->xlmft->desired_color_scheme)
459         {
460         case color_scheme_uniform:
461           make_uniform_colormap (mi->xgwa.screen, mi->xgwa.visual,
462                                  mi->xgwa.colormap,
463                                  mi->colors, &mi->npixels,
464                                  True, &mi->writable_p, True);
465           break;
466         case color_scheme_smooth:
467           make_smooth_colormap (mi->xgwa.screen, mi->xgwa.visual,
468                                 mi->xgwa.colormap,
469                                 mi->colors, &mi->npixels,
470                                 True, &mi->writable_p, True);
471           break;
472         case color_scheme_bright:
473         case color_scheme_default:
474           make_random_colormap (mi->xgwa.screen, mi->xgwa.visual,
475                                 mi->xgwa.colormap,
476                                 mi->colors, &mi->npixels,
477                                 (mi->xlmft->desired_color_scheme ==
478                                  color_scheme_bright),
479                                 True, &mi->writable_p, True);
480           break;
481         default:
482           abort();
483         }
484
485       if (mi->npixels <= 2)
486         goto MONO;
487       else
488         {
489           mi->pixels = (unsigned long *)
490             calloc (mi->npixels, sizeof (*mi->pixels));
491           for (i = 0; i < mi->npixels; i++)
492             mi->pixels[i] = mi->colors[i].pixel;
493         }
494     }
495
496   gcv.foreground = mi->white;
497   gcv.background = mi->black;
498   mi->gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
499
500   mi->fullrandom = True;
501
502   mi->pause      = get_integer_resource (dpy, "delay", "Usecs");
503
504   mi->cycles     = get_integer_resource (dpy, "cycles", "Int");
505   mi->batchcount = get_integer_resource (dpy, "count", "Int");
506   mi->size       = get_integer_resource (dpy, "size", "Int");
507
508   mi->threed = get_boolean_resource (dpy, "use3d", "Boolean");
509   mi->threed_delta = get_float_resource (dpy, "delta3d", "Float");
510   mi->threed_right_color = get_pixel_resource (dpy,
511                                                mi->xgwa.colormap, "right3d", "Color");
512   mi->threed_left_color = get_pixel_resource (dpy,
513                                               mi->xgwa.colormap, "left3d", "Color");
514   mi->threed_both_color = get_pixel_resource (dpy,
515                                               mi->xgwa.colormap, "both3d", "Color");
516   mi->threed_none_color = get_pixel_resource (dpy,
517                                               mi->xgwa.colormap, "none3d", "Color");
518
519   mi->wireframe_p = get_boolean_resource (dpy, "wireframe", "Boolean");
520   mi->root_p = root_p;
521   mi->fps_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
522   mi->recursion_depth = -1;  /* see fps.c */
523
524   if (mi->pause < 0)
525     mi->pause = 0;
526   else if (mi->pause > 100000000)
527     mi->pause = 100000000;
528
529   /* If this hack uses fonts (meaning, mentioned "font" in DEFAULTS)
530      then load it. */
531   {
532     char *name = get_string_resource (dpy, "font", "Font");
533     if (name)
534       {
535         XFontStruct *f = XLoadQueryFont (dpy, name);
536         const char *def1 = "-*-helvetica-bold-r-normal-*-180-*";
537         const char *def2 = "fixed";
538         if (!f)
539           {
540             fprintf (stderr, "%s: font %s does not exist, using %s\n",
541                      progname, name, def1);
542             f = XLoadQueryFont (dpy, def1);
543           }
544         if (!f)
545           {
546             fprintf (stderr, "%s: font %s does not exist, using %s\n",
547                      progname, def1, def2);
548             f = XLoadQueryFont (dpy, def2);
549           }
550         if (f) XSetFont (dpy, mi->gc, f->fid);
551         if (f) XFreeFont (dpy, f);
552         free (name);
553       }
554   }
555   
556   xlockmore_read_resources (mi);
557
558   return mi;
559 }
560
561 static void xlockmore_do_init (ModeInfo *mi)
562 {
563   if (! (mi->xlmft->got_init & (1 << mi->screen_number))) {
564     mi->xlmft->got_init |= 1 << mi->screen_number;
565     XClearWindow (mi->dpy, mi->window);
566     mi->xlmft->hack_init (mi);
567   }
568 }
569
570
571 static unsigned long
572 xlockmore_draw (Display *dpy, Window window, void *closure)
573 {
574   ModeInfo *mi = (ModeInfo *) closure;
575   unsigned long orig_pause = mi->pause;
576   unsigned long this_pause;
577
578   xlockmore_do_init (mi);
579   mi->xlmft->hack_draw (mi);
580
581   this_pause = mi->pause;
582   mi->pause  = orig_pause;
583   return this_pause;
584 }
585
586
587 static void
588 xlockmore_reshape (Display *dpy, Window window, void *closure, 
589                  unsigned int w, unsigned int h)
590 {
591   ModeInfo *mi = (ModeInfo *) closure;
592   if (mi && mi->xlmft->hack_reshape)
593     {
594       XGetWindowAttributes (dpy, window, &mi->xgwa);
595       xlockmore_do_init (mi);
596       mi->xlmft->hack_reshape (mi, mi->xgwa.width, mi->xgwa.height);
597     }
598 }
599
600 static Bool
601 xlockmore_event (Display *dpy, Window window, void *closure, XEvent *event)
602 {
603   ModeInfo *mi = (ModeInfo *) closure;
604   if (mi && mi->xlmft->hack_handle_events) {
605     xlockmore_do_init (mi);
606     return mi->xlmft->hack_handle_events (mi, event);
607   } else {
608     return False;
609   }
610 }
611
612 void
613 xlockmore_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
614 {
615   ModeInfo *mi = (ModeInfo *) closure;
616   fps_compute (fpst, 0, mi ? mi->recursion_depth : -1);
617   fps_draw (fpst);
618 }
619
620
621 static void
622 xlockmore_free (Display *dpy, Window window, void *closure)
623 {
624   ModeInfo *mi = (ModeInfo *) closure;
625
626   /* Find us in live_displays and clear that slot. */
627   assert (mi->xlmft->live_displays & (1ul << mi->screen_number));
628   mi->xlmft->live_displays &= ~(1ul << mi->screen_number);
629   if (!mi->xlmft->live_displays)
630     xlockmore_free_screens (mi);
631
632   XFreeGC (dpy, mi->gc);
633   free_colors (mi->xgwa.screen, mi->xgwa.colormap, mi->colors, mi->npixels);
634   free (mi->colors);
635   free (mi->pixels);
636
637   free (mi);
638 }
639
640
641 void
642 xlockmore_mi_init (ModeInfo *mi, size_t state_size, void **state_array,
643                          void (*hack_free_state) (ModeInfo *))
644 {
645   struct xlockmore_function_table *xlmft = mi->xlmft;
646
647   /* Steal the state_array for safe keeping.
648      Only necessary when the screenhack isn't a once per process deal.
649      (i.e. macOS, iOS, Android)
650    */
651   assert ((!xlmft->state_array && !*state_array) ||
652           xlmft->state_array == state_array);
653   xlmft->state_array = state_array;
654   assert (!xlmft->state_size || xlmft->state_size == state_size);
655   xlmft->state_size = state_size;
656   assert (!xlmft->hack_free_state ||
657           xlmft->hack_free_state == hack_free_state);
658   xlmft->hack_free_state = hack_free_state;
659
660   if (!*xlmft->state_array) {
661     *xlmft->state_array = calloc (XLOCKMORE_NUM_SCREENS, state_size);
662
663     if (!*xlmft->state_array) {
664 #ifdef HAVE_JWXYZ
665       /* Throws an exception instead of exiting the process. */
666       jwxyz_abort ("%s: out of memory", progname);
667 #else
668       fprintf (stderr, "%s: out of memory\n", progname);
669       exit (1);
670 #endif
671     }
672   }
673
674   /* Find the appropriate state object, clear it, and we're done. */
675   {
676     if (xlmft->hack_free_state)
677       xlmft->hack_free_state (mi);
678     memset ((char *)(*xlmft->state_array) + mi->screen_number * state_size, 0,
679             state_size);
680   }
681 }