1 /* xlockmore.c --- xscreensaver compatibility layer for xlockmore modules.
2 * xscreensaver, Copyright (c) 1997-2018 Jamie Zawinski <jwz@jwz.org>
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
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...)
19 #include "xlockmoreI.h"
20 #include "screenhack.h"
23 # include <X11/Intrinsic.h>
24 #endif /* !HAVE_JWXYZ */
29 #define countof(x) (sizeof((x))/sizeof(*(x)))
31 #define MAX_COLORS (1L<<13)
33 extern struct xscreensaver_function_table *xscreensaver_function_table;
35 extern const char *progclass;
37 extern struct xlockmore_function_table xlockmore_function_table;
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 *);
49 xlockmore_setup (struct xscreensaver_function_table *xsft, void *arg)
51 struct xlockmore_function_table *xlmft =
52 (struct xlockmore_function_table *) arg;
55 XrmOptionDescRec *new_options;
57 const char *xlockmore_defaults;
58 ModeSpecOpt *xlockmore_opts = xlmft->opts;
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;
69 progclass = xlmft->progclass;
70 xlockmore_defaults = xlmft->defaults;
72 /* Translate the xlockmore `opts[]' argument to a form that
75 new_options = (XrmOptionDescRec *)
76 calloc (xlockmore_opts->numopts*3 + 100, sizeof (*new_options));
78 for (i = 0; i < xlockmore_opts->numopts; i++)
80 XrmOptionDescRec *old = &xlockmore_opts->opts[i];
81 XrmOptionDescRec *new = &new_options[i];
83 if (old->option[0] == '-')
84 new->option = old->option;
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);
93 new->specifier = strrchr (old->specifier, '.');
94 if (!new->specifier) abort();
96 new->argKind = old->argKind;
97 new->value = old->value;
100 /* Add extra args, if they're mentioned in the defaults... */
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))
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"))
113 new->argKind = XrmoptionNoArg;
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";
121 else if (!strcmp(new->option, "-use3d"))
124 new->argKind = XrmoptionNoArg;
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";
132 else if (!strcmp(new->option, "-useSHM"))
134 new->option = "-shm";
135 new->argKind = XrmoptionNoArg;
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";
145 new->argKind = XrmoptionSepArg;
153 /* Construct the kind of `defaults' that screenhack.c expects from
154 the xlockmore `vars[]' argument.
158 new_defaults = (char **) calloc (1, xlockmore_opts->numvarsdesc * 10 + 1000);
160 /* Put on the PROGCLASS.background/foreground resources. */
161 s = (char *) malloc(50);
164 strcpy (s, progclass);
166 strcat (s, ".background: black");
167 new_defaults [i++] = s;
169 s = (char *) malloc(50);
172 strcpy (s, progclass);
174 strcat (s, ".foreground: white");
175 new_defaults [i++] = s;
177 /* Copy the lines out of the `xlockmore_defaults' var and into this array. */
178 s = strdup (xlockmore_defaults);
181 new_defaults [i++] = s;
187 /* Copy the defaults out of the `xlockmore_opts->' variable. */
188 for (j = 0; j < xlockmore_opts->numvarsdesc; j++)
190 const char *def = xlockmore_opts->vars[j].def;
194 if (strlen(def) > 1000) abort();
196 s = (char *) malloc (strlen (xlockmore_opts->vars[j].name) +
199 strcat (s, xlockmore_opts->vars[j].name);
202 new_defaults [i++] = s;
204 /* Go through the list of resources and print a warning if there
208 char *onew = strdup (xlockmore_opts->vars[j].name);
209 const char *new = onew;
211 if ((s = strrchr (new, '.'))) new = s+1;
212 if ((s = strrchr (new, '*'))) new = s+1;
213 for (k = 0; k < i-1; k++)
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))
223 "%s: duplicate resource \"%s\": "
224 "set in both DEFAULTS and vars[]\n",
233 new_defaults [i] = 0;
235 xsft->progclass = progclass;
236 xsft->options = new_options;
237 xsft->defaults = (const char * const *) new_defaults;
242 xlockmore_release_screens (ModeInfo *mi)
244 struct xlockmore_function_table *xlmft = mi->xlmft;
246 /* 2. Call release_##, if it exists. */
247 if (xlmft->hack_release)
248 xlmft->hack_release (mi);
250 /* 3. Free the state array. */
251 if (xlmft->state_array) {
252 free(*xlmft->state_array);
253 *xlmft->state_array = NULL;
254 xlmft->state_array = NULL;
257 /* 4. Pretend FreeAllGL(mi) gets called here. */
259 mi->xlmft->got_init = 0;
264 xlockmore_free_screens (ModeInfo *mi)
266 struct xlockmore_function_table *xlmft = mi->xlmft;
268 /* Optimization: xlockmore_read_resources calls this lots on first start. */
269 if (!xlmft->got_init)
272 /* Order is important here: */
274 /* 1. Call free_## for all screens. */
275 if (xlmft->hack_free) {
276 int old_screen = mi->screen_number;
277 for (mi->screen_number = 0; mi->screen_number < XLOCKMORE_NUM_SCREENS;
278 ++mi->screen_number) {
279 xlmft->hack_free (mi);
281 mi->screen_number = old_screen;
284 xlockmore_release_screens (mi);
289 xlockmore_read_resources (ModeInfo *mi)
291 Display *dpy = mi->dpy;
292 ModeSpecOpt *xlockmore_opts = mi->xlmft->opts;
294 for (i = 0; i < xlockmore_opts->numvarsdesc; i++)
296 void *var = xlockmore_opts->vars[i].var;
297 Bool *var_b = (Bool *) var;
298 char **var_c = (char **) var;
299 int *var_i = (int *) var;
300 float *var_f = (float *) var;
302 /* If any of the options changed, stop this hack's other instances. */
303 switch (xlockmore_opts->vars[i].type)
307 char *c = get_string_resource (dpy, xlockmore_opts->vars[i].name,
308 xlockmore_opts->vars[i].classname);
309 if ((!c && !*var_c) || (c && *var_c && !strcmp(c, *var_c))) {
312 xlockmore_free_screens (mi);
313 if (*var_c) free (*var_c);
320 float f = get_float_resource (dpy, xlockmore_opts->vars[i].name,
321 xlockmore_opts->vars[i].classname);
322 float frac = fabsf(*var_f) * (1.0f / (1l << (FLT_MANT_DIG - 4)));
323 if (f < *var_f - frac || f > *var_f + frac) {
324 xlockmore_free_screens (mi);
331 int ii = get_integer_resource (dpy, xlockmore_opts->vars[i].name,
332 xlockmore_opts->vars[i].classname);
334 xlockmore_free_screens (mi);
341 Bool b = get_boolean_resource (dpy, xlockmore_opts->vars[i].name,
342 xlockmore_opts->vars[i].classname);
344 xlockmore_free_screens (mi);
356 /* We keep a list of all of the screen numbers that are in use and not
357 yet freed so that they can have sensible screen numbers. If three
358 displays are created (0, 1, 2) and then #1 is closed, then the fourth
359 display will be given the now-unused display number 1. (Everything in
360 here assumes a 1:1 Display/Screen mapping.)
362 XLOCKMORE_NUM_SCREENS is the most number of live displays at one time. So
363 if it's 64, then we'll blow up if the system has 64 monitors and also has
364 System Preferences open (the small preview window).
366 Note that xlockmore-style savers tend to allocate big structures, so
367 setting XLOCKMORE_NUM_SCREENS to 1000 will waste a few megabytes. Also
368 most (all?) of them assume that the number of screens never changes, so
369 dynamically expanding this array won't work.
374 xlockmore_init (Display *dpy, Window window,
375 struct xlockmore_function_table *xlmft)
377 ModeInfo *mi = (ModeInfo *) calloc (1, sizeof(*mi));
389 XGetWindowAttributes (dpy, window, &mi->xgwa);
391 /* In Cocoa and Android-based xscreensaver, as well as with DEBUG_PAIR,
392 hacks run in the same address space, so each one needs to get its own
395 Find the first empty slot in live_displays and plug us in.
398 const int size = XLOCKMORE_NUM_SCREENS;
400 for (i = 0; i < size; i++) {
401 if (! (xlmft->live_displays & (1 << i)))
404 if (i >= size) abort();
405 xlmft->live_displays |= 1ul << i;
406 xlmft->got_init &= ~(1ul << i);
407 mi->screen_number = i;
410 root_p = (window == RootWindowOfScreen (mi->xgwa.screen));
414 /* Everybody gets motion events, just in case. */
415 XSelectInput (dpy, window, (mi->xgwa.your_event_mask | PointerMotionMask));
417 #endif /* !HAVE_JWXYZ */
419 if (mi->xlmft->hack_release) {
423 "%s: WARNING: hack_release is not usually recommended; see MI_INIT.\n",
428 color.flags = DoRed|DoGreen|DoBlue;
429 color.red = color.green = color.blue = 0;
430 if (!XAllocColor(dpy, mi->xgwa.colormap, &color))
432 mi->black = color.pixel;
433 color.red = color.green = color.blue = 0xFFFF;
434 if (!XAllocColor(dpy, mi->xgwa.colormap, &color))
436 mi->white = color.pixel;
440 static XColor colors[2];
444 mi->pixels = (unsigned long *)
445 calloc (mi->npixels, sizeof (*mi->pixels));
447 mi->colors = (XColor *)
448 calloc (mi->npixels, sizeof (*mi->colors));
449 colors[0].flags = DoRed|DoGreen|DoBlue;
450 colors[1].flags = DoRed|DoGreen|DoBlue;
451 colors[0].red = colors[0].green = colors[0].blue = 0;
452 colors[1].red = colors[1].green = colors[1].blue = 0xFFFF;
453 mi->writable_p = False;
457 mi->npixels = get_integer_resource (dpy, "ncolors", "Integer");
458 if (mi->npixels <= 0)
460 else if (mi->npixels > MAX_COLORS)
461 mi->npixels = MAX_COLORS;
463 mi->colors = (XColor *) calloc (mi->npixels, sizeof (*mi->colors));
465 mi->writable_p = mi->xlmft->want_writable_colors;
467 switch (mi->xlmft->desired_color_scheme)
469 case color_scheme_uniform:
470 make_uniform_colormap (mi->xgwa.screen, mi->xgwa.visual,
472 mi->colors, &mi->npixels,
473 True, &mi->writable_p, True);
475 case color_scheme_smooth:
476 make_smooth_colormap (mi->xgwa.screen, mi->xgwa.visual,
478 mi->colors, &mi->npixels,
479 True, &mi->writable_p, True);
481 case color_scheme_bright:
482 case color_scheme_default:
483 make_random_colormap (mi->xgwa.screen, mi->xgwa.visual,
485 mi->colors, &mi->npixels,
486 (mi->xlmft->desired_color_scheme ==
487 color_scheme_bright),
488 True, &mi->writable_p, True);
494 if (mi->npixels <= 2)
498 mi->pixels = (unsigned long *)
499 calloc (mi->npixels, sizeof (*mi->pixels));
500 for (i = 0; i < mi->npixels; i++)
501 mi->pixels[i] = mi->colors[i].pixel;
505 gcv.foreground = mi->white;
506 gcv.background = mi->black;
507 mi->gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
509 mi->fullrandom = True;
511 mi->pause = get_integer_resource (dpy, "delay", "Usecs");
513 mi->cycles = get_integer_resource (dpy, "cycles", "Int");
514 mi->batchcount = get_integer_resource (dpy, "count", "Int");
515 mi->size = get_integer_resource (dpy, "size", "Int");
517 mi->threed = get_boolean_resource (dpy, "use3d", "Boolean");
518 mi->threed_delta = get_float_resource (dpy, "delta3d", "Float");
519 mi->threed_right_color = get_pixel_resource (dpy,
520 mi->xgwa.colormap, "right3d", "Color");
521 mi->threed_left_color = get_pixel_resource (dpy,
522 mi->xgwa.colormap, "left3d", "Color");
523 mi->threed_both_color = get_pixel_resource (dpy,
524 mi->xgwa.colormap, "both3d", "Color");
525 mi->threed_none_color = get_pixel_resource (dpy,
526 mi->xgwa.colormap, "none3d", "Color");
528 mi->wireframe_p = get_boolean_resource (dpy, "wireframe", "Boolean");
530 mi->fps_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
531 mi->recursion_depth = -1; /* see fps.c */
535 else if (mi->pause > 100000000)
536 mi->pause = 100000000;
538 /* If this hack uses fonts (meaning, mentioned "font" in DEFAULTS)
541 char *name = get_string_resource (dpy, "font", "Font");
544 XFontStruct *f = load_font_retry (dpy, name);
546 XSetFont (dpy, mi->gc, f->fid);
552 xlockmore_read_resources (mi);
559 xlockmore_clear (ModeInfo *mi)
561 # ifndef HAVE_ANDROID
562 /* TODO: Clear the window for Xlib hacks on Android. */
563 XClearWindow (mi->dpy, mi->window);
569 xlockmore_do_init (ModeInfo *mi)
571 mi->xlmft->got_init |= 1 << mi->screen_number;
572 xlockmore_clear (mi);
573 mi->xlmft->hack_init (mi);
578 xlockmore_got_init (ModeInfo *mi)
580 return mi->xlmft->got_init & (1 << mi->screen_number);
585 xlockmore_abort_erase (ModeInfo *mi)
588 eraser_free (mi->eraser);
591 mi->needs_clear = False;
596 xlockmore_check_init (ModeInfo *mi)
598 if (! xlockmore_got_init (mi)) {
599 xlockmore_abort_erase (mi);
600 xlockmore_do_init (mi);
606 xlockmore_draw (Display *dpy, Window window, void *closure)
608 ModeInfo *mi = (ModeInfo *) closure;
609 unsigned long orig_pause = mi->pause;
610 unsigned long this_pause;
612 if (mi->needs_clear) {
613 /* OpenGL hacks never get here. */
615 xlockmore_clear (mi);
617 mi->eraser = erase_window (dpy, window, mi->eraser);
618 /* Delay calls to xlockmore hooks while the erase animation is running. */
622 mi->needs_clear = False;
625 xlockmore_check_init (mi);
628 mi->xlmft->hack_draw (mi);
630 this_pause = mi->pause;
631 mi->pause = orig_pause;
632 return mi->needs_clear ? 0 : this_pause;
637 xlockmore_reshape (Display *dpy, Window window, void *closure,
638 unsigned int w, unsigned int h)
640 ModeInfo *mi = (ModeInfo *) closure;
642 /* Ignore spurious resize events, because xlockmore_do_init usually clears
643 the screen, and there's no reason to do that if we don't have to.
646 /* These are not spurious on mobile: they are rotations. */
647 if (mi->xgwa.width == w && mi->xgwa.height == h)
653 /* Finish any erase operations. */
654 if (mi->needs_clear) {
655 xlockmore_abort_erase (mi);
656 xlockmore_clear (mi);
659 /* If there hasn't been an init yet, init now, but don't call reshape_##.
661 if (xlockmore_got_init (mi) && mi->xlmft->hack_reshape) {
662 mi->xlmft->hack_reshape (mi, mi->xgwa.width, mi->xgwa.height);
664 mi->is_drawn = False;
665 xlockmore_do_init (mi);
671 xlockmore_event (Display *dpy, Window window, void *closure, XEvent *event)
673 ModeInfo *mi = (ModeInfo *) closure;
675 if (mi->xlmft->hack_handle_events) {
676 xlockmore_check_init (mi);
677 return mi->xlmft->hack_handle_events (mi, event);
680 if (screenhack_event_helper (mi->dpy, mi->window, event)) {
681 /* If a clear is in progress, don't interrupt or restart it. */
683 mi->xlmft->got_init &= ~(1ul << mi->screen_number);
685 mi->xlmft->hack_init (mi);
693 xlockmore_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
695 ModeInfo *mi = (ModeInfo *) closure;
696 fps_compute (fpst, 0, mi ? mi->recursion_depth : -1);
702 xlockmore_free (Display *dpy, Window window, void *closure)
704 ModeInfo *mi = (ModeInfo *) closure;
707 eraser_free (mi->eraser);
709 /* Some hacks may need to do things with their Display * on cleanup. And
710 under JWXYZ, the Display * for this hack gets cleaned up right after
711 xlockmore_free returns. Thus, hack_free has to happen now, rather than
712 after the final screen has been released.
714 if (mi->xlmft->hack_free)
715 mi->xlmft->hack_free (mi);
717 /* Find us in live_displays and clear that slot. */
718 assert (mi->xlmft->live_displays & (1ul << mi->screen_number));
719 mi->xlmft->live_displays &= ~(1ul << mi->screen_number);
720 if (!mi->xlmft->live_displays)
721 xlockmore_release_screens (mi);
723 XFreeGC (dpy, mi->gc);
724 free_colors (mi->xgwa.screen, mi->xgwa.colormap, mi->colors, mi->npixels);
733 xlockmore_mi_init (ModeInfo *mi, size_t state_size, void **state_array)
735 struct xlockmore_function_table *xlmft = mi->xlmft;
737 /* Steal the state_array for safe keeping.
738 Only necessary when the screenhack isn't a once per process deal.
739 (i.e. macOS, iOS, Android)
741 assert ((!xlmft->state_array && !*state_array) ||
742 xlmft->state_array == state_array);
743 xlmft->state_array = state_array;
745 if (!*xlmft->state_array) {
746 *xlmft->state_array = calloc (XLOCKMORE_NUM_SCREENS, state_size);
748 if (!*xlmft->state_array) {
750 /* Throws an exception instead of exiting the process. */
751 jwxyz_abort ("%s: out of memory", progname);
753 fprintf (stderr, "%s: out of memory\n", progname);
759 /* Find the appropriate state object, clear it, and we're done. */
761 if (xlmft->hack_free)
762 xlmft->hack_free (mi);
763 memset ((char *)(*xlmft->state_array) + mi->screen_number * state_size, 0,
770 xlockmore_no_events (ModeInfo *mi, XEvent *event)