1 /* xlockmore.c --- xscreensaver compatibility layer for xlockmore modules.
2 * xscreensaver, Copyright (c) 1997-2017 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);
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 = XLoadQueryFont (dpy, name);
545 const char *def1 = "-*-helvetica-bold-r-normal-*-180-*";
546 const char *def2 = "fixed";
549 fprintf (stderr, "%s: font %s does not exist, using %s\n",
550 progname, name, def1);
551 f = XLoadQueryFont (dpy, def1);
555 fprintf (stderr, "%s: font %s does not exist, using %s\n",
556 progname, def1, def2);
557 f = XLoadQueryFont (dpy, def2);
559 if (f) XSetFont (dpy, mi->gc, f->fid);
560 if (f) XFreeFont (dpy, f);
565 xlockmore_read_resources (mi);
572 xlockmore_do_init (ModeInfo *mi)
574 mi->xlmft->got_init |= 1 << mi->screen_number;
575 XClearWindow (mi->dpy, mi->window);
576 mi->xlmft->hack_init (mi);
581 xlockmore_got_init (ModeInfo *mi)
583 return mi->xlmft->got_init & (1 << mi->screen_number);
588 xlockmore_abort_erase (ModeInfo *mi)
591 eraser_free (mi->eraser);
594 mi->needs_clear = False;
599 xlockmore_check_init (ModeInfo *mi)
601 if (! xlockmore_got_init (mi)) {
602 xlockmore_abort_erase (mi);
603 xlockmore_do_init (mi);
609 xlockmore_draw (Display *dpy, Window window, void *closure)
611 ModeInfo *mi = (ModeInfo *) closure;
612 unsigned long orig_pause = mi->pause;
613 unsigned long this_pause;
615 if (mi->needs_clear) {
616 /* OpenGL hacks never get here. */
618 XClearWindow (dpy, window);
620 mi->eraser = erase_window (dpy, window, mi->eraser);
621 /* Delay calls to xlockmore hooks while the erase animation is running. */
625 mi->needs_clear = False;
628 xlockmore_check_init (mi);
631 mi->xlmft->hack_draw (mi);
633 this_pause = mi->pause;
634 mi->pause = orig_pause;
635 return mi->needs_clear ? 0 : this_pause;
640 xlockmore_reshape (Display *dpy, Window window, void *closure,
641 unsigned int w, unsigned int h)
643 ModeInfo *mi = (ModeInfo *) closure;
645 /* Ignore spurious resize events, because xlockmore_do_init usually clears
646 the screen, and there's no reason to do that if we don't have to.
649 /* These are not spurious on mobile: they are rotations. */
650 if (mi->xgwa.width == w && mi->xgwa.height == h)
656 /* Finish any erase operations. */
657 if (mi->needs_clear) {
658 xlockmore_abort_erase (mi);
659 XClearWindow (dpy, window);
662 /* If there hasn't been an init yet, init now, but don't call reshape_##.
664 if (xlockmore_got_init (mi) && mi->xlmft->hack_reshape) {
665 mi->xlmft->hack_reshape (mi, mi->xgwa.width, mi->xgwa.height);
667 mi->is_drawn = False;
668 xlockmore_do_init (mi);
674 xlockmore_event (Display *dpy, Window window, void *closure, XEvent *event)
676 ModeInfo *mi = (ModeInfo *) closure;
678 if (mi->xlmft->hack_handle_events) {
679 xlockmore_check_init (mi);
680 return mi->xlmft->hack_handle_events (mi, event);
683 if (screenhack_event_helper (mi->dpy, mi->window, event)) {
684 /* If a clear is in progress, don't interrupt or restart it. */
686 mi->xlmft->got_init &= ~(1ul << mi->screen_number);
688 mi->xlmft->hack_init (mi);
696 xlockmore_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
698 ModeInfo *mi = (ModeInfo *) closure;
699 fps_compute (fpst, 0, mi ? mi->recursion_depth : -1);
705 xlockmore_free (Display *dpy, Window window, void *closure)
707 ModeInfo *mi = (ModeInfo *) closure;
710 eraser_free (mi->eraser);
712 /* Some hacks may need to do things with their Display * on cleanup. And
713 under JWXYZ, the Display * for this hack gets cleaned up right after
714 xlockmore_free returns. Thus, hack_free has to happen now, rather than
715 after the final screen has been released.
717 if (mi->xlmft->hack_free)
718 mi->xlmft->hack_free (mi);
720 /* Find us in live_displays and clear that slot. */
721 assert (mi->xlmft->live_displays & (1ul << mi->screen_number));
722 mi->xlmft->live_displays &= ~(1ul << mi->screen_number);
723 if (!mi->xlmft->live_displays)
724 xlockmore_release_screens (mi);
726 XFreeGC (dpy, mi->gc);
727 free_colors (mi->xgwa.screen, mi->xgwa.colormap, mi->colors, mi->npixels);
736 xlockmore_mi_init (ModeInfo *mi, size_t state_size, void **state_array)
738 struct xlockmore_function_table *xlmft = mi->xlmft;
740 /* Steal the state_array for safe keeping.
741 Only necessary when the screenhack isn't a once per process deal.
742 (i.e. macOS, iOS, Android)
744 assert ((!xlmft->state_array && !*state_array) ||
745 xlmft->state_array == state_array);
746 xlmft->state_array = state_array;
748 if (!*xlmft->state_array) {
749 *xlmft->state_array = calloc (XLOCKMORE_NUM_SCREENS, state_size);
751 if (!*xlmft->state_array) {
753 /* Throws an exception instead of exiting the process. */
754 jwxyz_abort ("%s: out of memory", progname);
756 fprintf (stderr, "%s: out of memory\n", progname);
762 /* Find the appropriate state object, clear it, and we're done. */
764 if (xlmft->hack_free)
765 xlmft->hack_free (mi);
766 memset ((char *)(*xlmft->state_array) + mi->screen_number * state_size, 0,
773 xlockmore_no_events (ModeInfo *mi, XEvent *event)