Commit | Line | Data |
---|---|---|
3144ee8a AT |
1 | /* xscreensaver, Copyright (c) 1992-2020 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 | * And remember: X Windows is to graphics hacking as roman numerals are to | |
12 | * the square root of pi. | |
13 | */ | |
14 | ||
15 | /* This file contains simple code to open a window or draw on the root. | |
16 | The idea being that, when writing a graphics hack, you can just link | |
17 | with this .o to get all of the uninteresting junk out of the way. | |
18 | ||
19 | Create a few static global procedures and variables: | |
20 | ||
21 | static void *YOURNAME_init (Display *, Window); | |
22 | ||
23 | Return an opaque structure representing your drawing state. | |
24 | ||
25 | static unsigned long YOURNAME_draw (Display *, Window, void *closure); | |
26 | ||
27 | Draw one frame. | |
28 | The `closure' arg is your drawing state, that you created in `init'. | |
29 | Return the number of microseconds to wait until the next frame. | |
30 | ||
31 | This should return in some small fraction of a second. | |
32 | Do not call `usleep' or loop excessively. For long loops, use a | |
33 | finite state machine. | |
34 | ||
35 | static void YOURNAME_reshape (Display *, Window, void *closure, | |
36 | unsigned int width, unsigned int height); | |
37 | ||
38 | Called when the size of the window changes with the new size. | |
39 | ||
40 | static Bool YOURNAME_event (Display *, Window, void *closure, | |
41 | XEvent *event); | |
42 | ||
43 | Called when a keyboard or mouse event arrives. | |
44 | Return True if you handle it in some way, False otherwise. | |
45 | ||
46 | static void YOURNAME_free (Display *, Window, void *closure); | |
47 | ||
48 | Called when you are done: free everything you've allocated, | |
49 | including your private `state' structure. | |
50 | ||
51 | NOTE: this is called in windowed-mode when the user typed | |
52 | 'q' or clicks on the window's close box; but when | |
53 | xscreensaver terminates this screenhack, it does so by | |
54 | killing the process with SIGSTOP. So this callback is | |
55 | mostly useless. | |
56 | ||
57 | static char YOURNAME_defaults [] = { "...", "...", ... , 0 }; | |
58 | ||
59 | This variable is an array of strings, your default resources. | |
60 | Null-terminate the list. | |
61 | ||
62 | static XrmOptionDescRec YOURNAME_options[] = { { ... }, ... { 0,0,0,0 } } | |
63 | ||
64 | This variable describes your command-line options. | |
65 | Null-terminate the list. | |
66 | ||
67 | Finally , invoke the XSCREENSAVER_MODULE() macro to tie it all together. | |
68 | ||
69 | Additional caveats: | |
70 | ||
71 | - Make sure that all functions in your module are static (check this | |
72 | by running "nm -g" on the .o file). | |
73 | ||
74 | - Do not use global variables: all such info must be stored in the | |
75 | private `state' structure. | |
76 | ||
77 | - Do not use static function-local variables, either. Put it in `state'. | |
78 | ||
79 | Assume that there are N independent runs of this code going in the | |
80 | same address space at the same time: they must not affect each other. | |
81 | ||
82 | - Don't forget to write an XML file to describe the user interface | |
83 | of your screen saver module. See .../hacks/config/README for details. | |
84 | */ | |
85 | ||
86 | #define DEBUG_PAIR | |
87 | ||
88 | #include <stdio.h> | |
89 | #include <X11/Intrinsic.h> | |
90 | #include <X11/IntrinsicP.h> | |
91 | #include <X11/CoreP.h> | |
92 | #include <X11/Shell.h> | |
93 | #include <X11/StringDefs.h> | |
94 | #include <X11/keysym.h> | |
95 | ||
96 | #ifdef __sgi | |
97 | # include <X11/SGIScheme.h> /* for SgiUseSchemes() */ | |
98 | #endif /* __sgi */ | |
99 | ||
100 | #ifdef HAVE_XMU | |
101 | # ifndef VMS | |
102 | # include <X11/Xmu/Error.h> | |
103 | # else /* VMS */ | |
104 | # include <Xmu/Error.h> | |
105 | # endif | |
106 | #else | |
107 | # include "xmu.h" | |
108 | #endif | |
109 | ||
110 | #include "screenhackI.h" | |
111 | #include "version.h" | |
112 | #include "vroot.h" | |
113 | #include "fps.h" | |
114 | ||
115 | #ifdef HAVE_RECORD_ANIM | |
116 | # include "recanim.h" | |
117 | #endif | |
118 | ||
119 | #ifndef _XSCREENSAVER_VROOT_H_ | |
120 | # error Error! You have an old version of vroot.h! Check -I args. | |
121 | #endif /* _XSCREENSAVER_VROOT_H_ */ | |
122 | ||
123 | #ifndef isupper | |
124 | # define isupper(c) ((c) >= 'A' && (c) <= 'Z') | |
125 | #endif | |
126 | #ifndef _tolower | |
127 | # define _tolower(c) ((c) - 'A' + 'a') | |
128 | #endif | |
129 | ||
130 | ||
131 | /* This is defined by the SCREENHACK_MAIN() macro via screenhack.h. | |
132 | */ | |
133 | extern struct xscreensaver_function_table *xscreensaver_function_table; | |
134 | ||
135 | ||
136 | const char *progname; /* used by hacks in error messages */ | |
137 | const char *progclass; /* used by ../utils/resources.c */ | |
138 | Bool mono_p; /* used by hacks */ | |
139 | ||
140 | #ifdef EXIT_AFTER | |
141 | static time_t exit_after; /* Exit gracefully after N seconds */ | |
142 | #endif | |
143 | ||
144 | static XrmOptionDescRec default_options [] = { | |
145 | { "-root", ".root", XrmoptionNoArg, "True" }, | |
146 | { "-window", ".root", XrmoptionNoArg, "False" }, | |
147 | { "-mono", ".mono", XrmoptionNoArg, "True" }, | |
148 | { "-install", ".installColormap", XrmoptionNoArg, "True" }, | |
149 | { "-noinstall",".installColormap", XrmoptionNoArg, "False" }, | |
150 | { "-visual", ".visualID", XrmoptionSepArg, 0 }, | |
151 | { "-window-id", ".windowID", XrmoptionSepArg, 0 }, | |
152 | { "-fps", ".doFPS", XrmoptionNoArg, "True" }, | |
153 | { "-no-fps", ".doFPS", XrmoptionNoArg, "False" }, | |
154 | ||
155 | # ifdef DEBUG_PAIR | |
156 | { "-pair", ".pair", XrmoptionNoArg, "True" }, | |
157 | # endif | |
158 | # ifdef HAVE_RECORD_ANIM | |
159 | { "-record-animation", ".recordAnim", XrmoptionSepArg, 0 }, | |
160 | # endif | |
161 | # ifdef EXIT_AFTER | |
162 | { "-exit-after", ".exitAfter", XrmoptionSepArg, 0 }, | |
163 | # endif | |
164 | ||
165 | { 0, 0, 0, 0 } | |
166 | }; | |
167 | ||
168 | static char *default_defaults[] = { | |
169 | ".root: false", | |
170 | "*geometry: 1280x720", /* this should be .geometry, but noooo... */ | |
171 | "*mono: false", | |
172 | "*installColormap: false", | |
173 | "*doFPS: false", | |
174 | "*multiSample: false", | |
175 | "*visualID: default", | |
176 | "*windowID: ", | |
177 | "*desktopGrabber: xscreensaver-getimage %s", | |
178 | 0 | |
179 | }; | |
180 | ||
181 | static XrmOptionDescRec *merged_options; | |
182 | static int merged_options_size; | |
183 | static char **merged_defaults; | |
184 | ||
185 | ||
186 | static void | |
187 | merge_options (void) | |
188 | { | |
189 | struct xscreensaver_function_table *ft = xscreensaver_function_table; | |
190 | ||
191 | const XrmOptionDescRec *options = ft->options; | |
192 | const char * const *defaults = ft->defaults; | |
193 | const char *progclass = ft->progclass; | |
194 | ||
195 | int def_opts_size, opts_size; | |
196 | int def_defaults_size, defaults_size; | |
197 | ||
198 | for (def_opts_size = 0; default_options[def_opts_size].option; | |
199 | def_opts_size++) | |
200 | ; | |
201 | for (opts_size = 0; options[opts_size].option; opts_size++) | |
202 | ; | |
203 | ||
204 | merged_options_size = def_opts_size + opts_size; | |
205 | merged_options = (XrmOptionDescRec *) | |
206 | malloc ((merged_options_size + 1) * sizeof(*default_options)); | |
207 | memcpy (merged_options, default_options, | |
208 | (def_opts_size * sizeof(*default_options))); | |
209 | memcpy (merged_options + def_opts_size, options, | |
210 | ((opts_size + 1) * sizeof(*default_options))); | |
211 | ||
212 | for (def_defaults_size = 0; default_defaults[def_defaults_size]; | |
213 | def_defaults_size++) | |
214 | ; | |
215 | for (defaults_size = 0; defaults[defaults_size]; defaults_size++) | |
216 | ; | |
217 | merged_defaults = (char **) | |
218 | malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));; | |
219 | memcpy (merged_defaults, default_defaults, | |
220 | def_defaults_size * sizeof(*defaults)); | |
221 | memcpy (merged_defaults + def_defaults_size, defaults, | |
222 | (defaults_size + 1) * sizeof(*defaults)); | |
223 | ||
224 | /* This totally sucks. Xt should behave like this by default. | |
225 | If the string in `defaults' looks like ".foo", change that | |
226 | to "Progclass.foo". | |
227 | */ | |
228 | { | |
229 | char **s; | |
230 | for (s = merged_defaults; *s; s++) | |
231 | if (**s == '.') | |
232 | { | |
233 | const char *oldr = *s; | |
234 | char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3); | |
235 | strcpy (newr, progclass); | |
236 | strcat (newr, oldr); | |
237 | *s = newr; | |
238 | } | |
239 | else | |
240 | *s = strdup (*s); | |
241 | } | |
242 | } | |
243 | ||
244 | \f | |
245 | /* Make the X errors print out the name of this program, so we have some | |
246 | clue which one has a bug when they die under the screensaver. | |
247 | */ | |
248 | ||
249 | static int | |
250 | screenhack_ehandler (Display *dpy, XErrorEvent *error) | |
251 | { | |
252 | fprintf (stderr, "\nX error in %s:\n", progname); | |
253 | if (XmuPrintDefaultErrorMessage (dpy, error, stderr)) | |
254 | exit (-1); | |
255 | else | |
256 | fprintf (stderr, " (nonfatal.)\n"); | |
257 | return 0; | |
258 | } | |
259 | ||
260 | static Bool | |
261 | MapNotify_event_p (Display *dpy, XEvent *event, XPointer window) | |
262 | { | |
263 | return (event->xany.type == MapNotify && | |
264 | event->xvisibility.window == (Window) window); | |
265 | } | |
266 | ||
267 | ||
268 | static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW, XA_NET_WM_PID; | |
269 | ||
270 | /* Dead-trivial event handling: exits if "q" or "ESC" are typed. | |
271 | Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received. | |
272 | Returns False if the screen saver should now terminate. | |
273 | */ | |
274 | static Bool | |
275 | screenhack_handle_event_1 (Display *dpy, XEvent *event) | |
276 | { | |
277 | switch (event->xany.type) | |
278 | { | |
279 | case KeyPress: | |
280 | { | |
281 | KeySym keysym; | |
282 | char c = 0; | |
283 | XLookupString (&event->xkey, &c, 1, &keysym, 0); | |
284 | if (c == 'q' || | |
285 | c == 'Q' || | |
286 | c == 3 || /* ^C */ | |
287 | c == 27) /* ESC */ | |
288 | return False; /* exit */ | |
289 | else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R)) | |
290 | XBell (dpy, 0); /* beep for non-chord keys */ | |
291 | } | |
292 | break; | |
293 | case ButtonPress: | |
294 | XBell (dpy, 0); | |
295 | break; | |
296 | case ClientMessage: | |
297 | { | |
298 | if (event->xclient.message_type != XA_WM_PROTOCOLS) | |
299 | { | |
300 | char *s = XGetAtomName(dpy, event->xclient.message_type); | |
301 | if (!s) s = "(null)"; | |
302 | fprintf (stderr, "%s: unknown ClientMessage %s received!\n", | |
303 | progname, s); | |
304 | } | |
305 | else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW) | |
306 | { | |
307 | char *s1 = XGetAtomName(dpy, event->xclient.message_type); | |
308 | char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]); | |
309 | if (!s1) s1 = "(null)"; | |
310 | if (!s2) s2 = "(null)"; | |
311 | fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n", | |
312 | progname, s1, s2); | |
313 | } | |
314 | else | |
315 | { | |
316 | return False; /* exit */ | |
317 | } | |
318 | } | |
319 | break; | |
320 | } | |
321 | return True; | |
322 | } | |
323 | ||
324 | ||
325 | static Visual * | |
326 | pick_visual (Screen *screen) | |
327 | { | |
328 | struct xscreensaver_function_table *ft = xscreensaver_function_table; | |
329 | ||
330 | if (ft->pick_visual_hook) | |
331 | { | |
332 | Visual *v = ft->pick_visual_hook (screen); | |
333 | if (v) return v; | |
334 | } | |
335 | ||
336 | return get_visual_resource (screen, "visualID", "VisualID", False); | |
337 | } | |
338 | ||
339 | ||
340 | /* Notice when the user has requested a different visual or colormap | |
341 | on a pre-existing window (e.g., "-root -visual truecolor" or | |
342 | "-window-id 0x2c00001 -install") and complain, since when drawing | |
343 | on an existing window, we have no choice about these things. | |
344 | */ | |
345 | static void | |
346 | visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap, | |
347 | Bool window_p) | |
348 | { | |
349 | struct xscreensaver_function_table *ft = xscreensaver_function_table; | |
350 | ||
351 | char *visual_string = get_string_resource (DisplayOfScreen (screen), | |
352 | "visualID", "VisualID"); | |
353 | Visual *desired_visual = pick_visual (screen); | |
354 | char win[100]; | |
355 | char why[100]; | |
356 | ||
357 | if (window == RootWindowOfScreen (screen)) | |
358 | strcpy (win, "root window"); | |
359 | else | |
360 | sprintf (win, "window 0x%lx", (unsigned long) window); | |
361 | ||
362 | if (window_p) | |
363 | sprintf (why, "-window-id 0x%lx", (unsigned long) window); | |
364 | else | |
365 | strcpy (why, "-root"); | |
366 | ||
367 | if (visual_string && *visual_string) | |
368 | { | |
369 | char *s; | |
370 | for (s = visual_string; *s; s++) | |
371 | if (isupper (*s)) *s = _tolower (*s); | |
372 | ||
373 | if (!strcmp (visual_string, "default") || | |
374 | !strcmp (visual_string, "default") || | |
375 | !strcmp (visual_string, "best")) | |
376 | /* don't warn about these, just silently DWIM. */ | |
377 | ; | |
378 | else if (visual != desired_visual) | |
379 | { | |
380 | fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n", | |
381 | progname, visual_string, why); | |
382 | fprintf (stderr, "%s: using %s's visual 0x%lx.\n", | |
383 | progname, win, XVisualIDFromVisual (visual)); | |
384 | } | |
385 | free (visual_string); | |
386 | } | |
387 | ||
388 | if (visual == DefaultVisualOfScreen (screen) && | |
389 | has_writable_cells (screen, visual) && | |
390 | get_boolean_resource (DisplayOfScreen (screen), | |
391 | "installColormap", "InstallColormap")) | |
392 | { | |
393 | fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n", | |
394 | progname, why); | |
395 | fprintf (stderr, "%s: using %s's colormap 0x%lx.\n", | |
396 | progname, win, (unsigned long) cmap); | |
397 | } | |
398 | ||
399 | if (ft->validate_visual_hook) | |
400 | { | |
401 | if (! ft->validate_visual_hook (screen, win, visual)) | |
402 | exit (1); | |
403 | } | |
404 | } | |
405 | ||
406 | ||
407 | static void | |
408 | fix_fds (void) | |
409 | { | |
410 | /* Bad Things Happen if stdin, stdout, and stderr have been closed | |
411 | (as by the `sh incantation "attraction >&- 2>&-"). When you do | |
412 | that, the X connection gets allocated to one of these fds, and | |
413 | then some random library writes to stderr, and random bits get | |
414 | stuffed down the X pipe, causing "Xlib: sequence lost" errors. | |
415 | So, we cause the first three file descriptors to be open to | |
416 | /dev/null if they aren't open to something else already. This | |
417 | must be done before any other files are opened (or the closing | |
418 | of that other file will again free up one of the "magic" first | |
419 | three FDs.) | |
420 | ||
421 | We do this by opening /dev/null three times, and then closing | |
422 | those fds, *unless* any of them got allocated as #0, #1, or #2, | |
423 | in which case we leave them open. Gag. | |
424 | ||
425 | Really, this crap is technically required of *every* X program, | |
426 | if you want it to be robust in the face of "2>&-". | |
427 | */ | |
428 | int fd0 = open ("/dev/null", O_RDWR); | |
429 | int fd1 = open ("/dev/null", O_RDWR); | |
430 | int fd2 = open ("/dev/null", O_RDWR); | |
431 | if (fd0 > 2) close (fd0); | |
432 | if (fd1 > 2) close (fd1); | |
433 | if (fd2 > 2) close (fd2); | |
434 | } | |
435 | ||
436 | ||
437 | static Boolean | |
438 | screenhack_table_handle_events (Display *dpy, | |
439 | const struct xscreensaver_function_table *ft, | |
440 | Window window, void *closure | |
441 | #ifdef DEBUG_PAIR | |
442 | , Window window2, void *closure2 | |
443 | #endif | |
444 | ) | |
445 | { | |
446 | XtAppContext app = XtDisplayToApplicationContext (dpy); | |
447 | ||
448 | if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput)) | |
449 | XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput); | |
450 | ||
451 | while (XPending (dpy)) | |
452 | { | |
453 | XEvent event; | |
454 | XNextEvent (dpy, &event); | |
455 | ||
456 | if (event.xany.type == ConfigureNotify) | |
457 | { | |
458 | if (event.xany.window == window) | |
459 | ft->reshape_cb (dpy, window, closure, | |
460 | event.xconfigure.width, event.xconfigure.height); | |
461 | #ifdef DEBUG_PAIR | |
462 | if (window2 && event.xany.window == window2) | |
463 | ft->reshape_cb (dpy, window2, closure2, | |
464 | event.xconfigure.width, event.xconfigure.height); | |
465 | #endif | |
466 | } | |
467 | else if (event.xany.type == ClientMessage || | |
468 | (! (event.xany.window == window | |
469 | ? ft->event_cb (dpy, window, closure, &event) | |
470 | #ifdef DEBUG_PAIR | |
471 | : (window2 && event.xany.window == window2) | |
472 | ? ft->event_cb (dpy, window2, closure2, &event) | |
473 | #endif | |
474 | : 0))) | |
475 | if (! screenhack_handle_event_1 (dpy, &event)) | |
476 | return False; | |
477 | ||
478 | if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput)) | |
479 | XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput); | |
480 | } | |
481 | ||
482 | # ifdef EXIT_AFTER | |
483 | if (exit_after != 0 && time ((time_t *) 0) >= exit_after) | |
484 | return False; | |
485 | # endif | |
486 | ||
487 | return True; | |
488 | } | |
489 | ||
490 | ||
491 | static Boolean | |
492 | usleep_and_process_events (Display *dpy, | |
493 | const struct xscreensaver_function_table *ft, | |
494 | Window window, fps_state *fpst, void *closure, | |
495 | unsigned long delay | |
496 | #ifdef DEBUG_PAIR | |
497 | , Window window2, fps_state *fpst2, void *closure2, | |
498 | unsigned long delay2 | |
499 | #endif | |
500 | # ifdef HAVE_RECORD_ANIM | |
501 | , record_anim_state *anim_state | |
502 | # endif | |
503 | ) | |
504 | { | |
505 | do { | |
506 | unsigned long quantum = 33333; /* 30 fps */ | |
507 | if (quantum > delay) | |
508 | quantum = delay; | |
509 | delay -= quantum; | |
510 | ||
511 | XSync (dpy, False); | |
512 | ||
513 | #ifdef HAVE_RECORD_ANIM | |
514 | if (anim_state) screenhack_record_anim (anim_state); | |
515 | #endif | |
516 | ||
517 | if (quantum > 0) | |
518 | { | |
519 | usleep (quantum); | |
520 | if (fpst) fps_slept (fpst, quantum); | |
521 | #ifdef DEBUG_PAIR | |
522 | if (fpst2) fps_slept (fpst2, quantum); | |
523 | #endif | |
524 | } | |
525 | ||
526 | if (! screenhack_table_handle_events (dpy, ft, window, closure | |
527 | #ifdef DEBUG_PAIR | |
528 | , window2, closure2 | |
529 | #endif | |
530 | )) | |
531 | return False; | |
532 | } while (delay > 0); | |
533 | ||
534 | return True; | |
535 | } | |
536 | ||
537 | ||
538 | static void | |
539 | screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) | |
540 | { | |
541 | fps_compute (fpst, 0, -1); | |
542 | fps_draw (fpst); | |
543 | } | |
544 | ||
545 | ||
546 | static void | |
547 | run_screenhack_table (Display *dpy, | |
548 | Window window, | |
549 | # ifdef DEBUG_PAIR | |
550 | Window window2, | |
551 | # endif | |
552 | # ifdef HAVE_RECORD_ANIM | |
553 | record_anim_state *anim_state, | |
554 | # endif | |
555 | const struct xscreensaver_function_table *ft) | |
556 | { | |
557 | ||
558 | /* Kludge: even though the init_cb functions are declared to take 2 args, | |
559 | actually call them with 3, for the benefit of xlockmore_init() and | |
560 | xlockmore_setup(). | |
561 | */ | |
562 | void *(*init_cb) (Display *, Window, void *) = | |
563 | (void *(*) (Display *, Window, void *)) ft->init_cb; | |
564 | ||
565 | void (*fps_cb) (Display *, Window, fps_state *, void *) = ft->fps_cb; | |
566 | ||
567 | void *closure = init_cb (dpy, window, ft->setup_arg); | |
568 | fps_state *fpst = fps_init (dpy, window); | |
569 | unsigned long delay = 0; | |
570 | ||
571 | #ifdef DEBUG_PAIR | |
572 | void *closure2 = 0; | |
573 | fps_state *fpst2 = 0; | |
574 | unsigned long delay2 = 0; | |
575 | if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg); | |
576 | if (window2) fpst2 = fps_init (dpy, window2); | |
577 | #endif | |
578 | ||
579 | if (! closure) /* if it returns nothing, it can't possibly be re-entrant. */ | |
580 | abort(); | |
581 | ||
582 | if (! fps_cb) fps_cb = screenhack_do_fps; | |
583 | ||
584 | while (1) | |
585 | { | |
586 | if (! usleep_and_process_events (dpy, ft, | |
587 | window, fpst, closure, delay | |
588 | #ifdef DEBUG_PAIR | |
589 | , window2, fpst2, closure2, delay2 | |
590 | #endif | |
591 | #ifdef HAVE_RECORD_ANIM | |
592 | , anim_state | |
593 | #endif | |
594 | )) | |
595 | break; | |
596 | ||
597 | delay = ft->draw_cb (dpy, window, closure); | |
598 | #ifdef DEBUG_PAIR | |
599 | delay2 = 0; | |
600 | if (window2) delay2 = ft->draw_cb (dpy, window2, closure2); | |
601 | #endif | |
602 | ||
603 | if (fpst) fps_cb (dpy, window, fpst, closure); | |
604 | #ifdef DEBUG_PAIR | |
605 | if (fpst2) fps_cb (dpy, window2, fpst2, closure2); | |
606 | #endif | |
607 | } | |
608 | ||
609 | #ifdef HAVE_RECORD_ANIM | |
610 | /* Exiting before target frames hit: write the video anyway. */ | |
611 | if (anim_state) screenhack_record_anim_free (anim_state); | |
612 | #endif | |
613 | ||
614 | ft->free_cb (dpy, window, closure); | |
615 | if (fpst) ft->fps_free (fpst); | |
616 | ||
617 | #ifdef DEBUG_PAIR | |
618 | if (window2) ft->free_cb (dpy, window2, closure2); | |
619 | if (fpst2) ft->fps_free (fpst2); | |
620 | #endif | |
621 | } | |
622 | ||
623 | ||
624 | static Widget | |
625 | make_shell (Screen *screen, Widget toplevel, int width, int height) | |
626 | { | |
627 | Display *dpy = DisplayOfScreen (screen); | |
628 | Visual *visual = pick_visual (screen); | |
629 | Boolean def_visual_p = (toplevel && | |
630 | visual == DefaultVisualOfScreen (screen)); | |
631 | ||
632 | if (width <= 0) width = 600; | |
633 | if (height <= 0) height = 480; | |
634 | ||
635 | if (def_visual_p) | |
636 | { | |
637 | Window window; | |
638 | XtVaSetValues (toplevel, | |
639 | XtNmappedWhenManaged, False, | |
640 | XtNwidth, width, | |
641 | XtNheight, height, | |
642 | XtNinput, True, /* for WM_HINTS */ | |
643 | NULL); | |
644 | XtRealizeWidget (toplevel); | |
645 | window = XtWindow (toplevel); | |
646 | ||
647 | if (get_boolean_resource (dpy, "installColormap", "InstallColormap")) | |
648 | { | |
649 | Colormap cmap = | |
650 | XCreateColormap (dpy, window, DefaultVisualOfScreen (screen), | |
651 | AllocNone); | |
652 | XSetWindowColormap (dpy, window, cmap); | |
653 | } | |
654 | } | |
655 | else | |
656 | { | |
657 | unsigned int bg, bd; | |
658 | Widget new; | |
659 | Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen), | |
660 | visual, AllocNone); | |
661 | bg = get_pixel_resource (dpy, cmap, "background", "Background"); | |
662 | bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground"); | |
663 | ||
664 | new = XtVaAppCreateShell (progname, progclass, | |
665 | topLevelShellWidgetClass, dpy, | |
666 | XtNmappedWhenManaged, False, | |
667 | XtNvisual, visual, | |
668 | XtNdepth, visual_depth (screen, visual), | |
669 | XtNwidth, width, | |
670 | XtNheight, height, | |
671 | XtNcolormap, cmap, | |
672 | XtNbackground, (Pixel) bg, | |
673 | XtNborderColor, (Pixel) bd, | |
674 | XtNinput, True, /* for WM_HINTS */ | |
675 | NULL); | |
676 | ||
677 | if (!toplevel) /* kludge for the second window in -pair mode... */ | |
678 | XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL); | |
679 | ||
680 | XtRealizeWidget (new); | |
681 | toplevel = new; | |
682 | } | |
683 | ||
684 | return toplevel; | |
685 | } | |
686 | ||
687 | static void | |
688 | init_window (Display *dpy, Widget toplevel, const char *title) | |
689 | { | |
690 | Window window; | |
691 | XWindowAttributes xgwa; | |
692 | long pid = getpid(); | |
693 | XtPopup (toplevel, XtGrabNone); | |
694 | XtVaSetValues (toplevel, XtNtitle, title, NULL); | |
695 | ||
696 | /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW. | |
697 | */ | |
698 | window = XtWindow (toplevel); | |
699 | XGetWindowAttributes (dpy, window, &xgwa); | |
700 | XSelectInput (dpy, window, | |
701 | (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask | | |
702 | ButtonPressMask | ButtonReleaseMask)); | |
703 | XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32, | |
704 | PropModeReplace, | |
705 | (unsigned char *) &XA_WM_DELETE_WINDOW, 1); | |
706 | XChangeProperty (dpy, window, XA_NET_WM_PID, XA_CARDINAL, 32, | |
707 | PropModeReplace, (unsigned char *)&pid, 1); | |
708 | } | |
709 | ||
710 | ||
711 | int | |
712 | main (int argc, char **argv) | |
713 | { | |
714 | struct xscreensaver_function_table *ft = xscreensaver_function_table; | |
715 | ||
716 | XWindowAttributes xgwa; | |
717 | Widget toplevel; | |
718 | Display *dpy; | |
719 | Window window; | |
720 | # ifdef DEBUG_PAIR | |
721 | Window window2 = 0; | |
722 | Widget toplevel2 = 0; | |
723 | # endif | |
724 | # ifdef HAVE_RECORD_ANIM | |
725 | record_anim_state *anim_state = 0; | |
726 | # endif | |
727 | XtAppContext app; | |
728 | Bool root_p; | |
729 | Window on_window = 0; | |
730 | XEvent event; | |
731 | Boolean dont_clear; | |
732 | char version[255]; | |
733 | ||
734 | fix_fds(); | |
735 | ||
736 | progname = argv[0]; /* reset later */ | |
737 | progclass = ft->progclass; | |
738 | ||
739 | if (ft->setup_cb) | |
740 | ft->setup_cb (ft, ft->setup_arg); | |
741 | ||
742 | merge_options (); | |
743 | ||
744 | #ifdef __sgi | |
745 | /* We have to do this on SGI to prevent the background color from being | |
746 | overridden by the current desktop color scheme (we'd like our backgrounds | |
747 | to be black, thanks.) This should be the same as setting the | |
748 | "*useSchemes: none" resource, but it's not -- if that resource is | |
749 | present in the `default_defaults' above, it doesn't work, though it | |
750 | does work when passed as an -xrm arg on the command line. So screw it, | |
751 | turn them off from C instead. | |
752 | */ | |
753 | SgiUseSchemes ("none"); | |
754 | #endif /* __sgi */ | |
755 | ||
756 | toplevel = XtAppInitialize (&app, progclass, merged_options, | |
757 | merged_options_size, &argc, argv, | |
758 | merged_defaults, 0, 0); | |
759 | ||
760 | dpy = XtDisplay (toplevel); | |
761 | ||
762 | XtGetApplicationNameAndClass (dpy, | |
763 | (char **) &progname, | |
764 | (char **) &progclass); | |
765 | ||
766 | /* half-assed way of avoiding buffer-overrun attacks. */ | |
767 | if (strlen (progname) >= 100) ((char *) progname)[100] = 0; | |
768 | ||
769 | XSetErrorHandler (screenhack_ehandler); | |
770 | ||
771 | XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False); | |
772 | XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False); | |
773 | XA_NET_WM_PID = XInternAtom (dpy, "_NET_WM_PID", False); | |
774 | ||
775 | { | |
776 | char *v = (char *) strdup(strchr(screensaver_id, ' ')); | |
777 | char *s1, *s2, *s3, *s4; | |
778 | const char *ot = get_string_resource (dpy, "title", "Title"); | |
779 | s1 = (char *) strchr(v, ' '); s1++; | |
780 | s2 = (char *) strchr(s1, ' '); | |
781 | s3 = (char *) strchr(v, '('); s3++; | |
782 | s4 = (char *) strchr(s3, ')'); | |
783 | *s2 = 0; | |
784 | *s4 = 0; | |
785 | if (ot && !*ot) ot = 0; | |
786 | sprintf (version, "%.50s%s%s: from the XScreenSaver %s distribution (%s)", | |
787 | (ot ? ot : ""), | |
788 | (ot ? ": " : ""), | |
789 | progclass, s1, s3); | |
790 | free(v); | |
791 | } | |
792 | ||
793 | if (argc > 1) | |
794 | { | |
795 | const char *s; | |
796 | int i; | |
797 | int x = 18; | |
798 | int end = 78; | |
799 | Bool help_p = (!strcmp(argv[1], "-help") || | |
800 | !strcmp(argv[1], "--help")); | |
801 | fprintf (stderr, "%s\n", version); | |
802 | for (s = progclass; *s; s++) fprintf(stderr, " "); | |
803 | fprintf (stderr, " https://www.jwz.org/xscreensaver/\n\n"); | |
804 | ||
805 | if (!help_p) | |
806 | fprintf(stderr, "Unrecognised option: %s\n", argv[1]); | |
807 | fprintf (stderr, "Options include: "); | |
808 | for (i = 0; i < merged_options_size; i++) | |
809 | { | |
810 | char *sw = merged_options [i].option; | |
811 | Bool argp = (merged_options [i].argKind == XrmoptionSepArg); | |
812 | int size = strlen (sw) + (argp ? 6 : 0) + 2; | |
813 | if (x + size >= end) | |
814 | { | |
815 | fprintf (stderr, "\n\t\t "); | |
816 | x = 18; | |
817 | } | |
818 | x += size; | |
819 | fprintf (stderr, "%s", sw); | |
820 | if (argp) fprintf (stderr, " <arg>"); | |
821 | if (i != merged_options_size - 1) fprintf (stderr, ", "); | |
822 | } | |
823 | ||
824 | fprintf (stderr, ".\n"); | |
825 | ||
826 | #if 0 | |
827 | if (help_p) | |
828 | { | |
829 | fprintf (stderr, "\nResources:\n\n"); | |
830 | for (i = 0; i < merged_options_size; i++) | |
831 | { | |
832 | const char *opt = merged_options [i].option; | |
833 | const char *res = merged_options [i].specifier + 1; | |
834 | const char *val = merged_options [i].value; | |
835 | char *s = get_string_resource (dpy, (char *) res, (char *) res); | |
836 | ||
837 | if (s) | |
838 | { | |
839 | int L = strlen(s); | |
840 | while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t')) | |
841 | s[--L] = 0; | |
842 | } | |
843 | ||
844 | fprintf (stderr, " %-16s %-18s ", opt, res); | |
845 | if (merged_options [i].argKind == XrmoptionSepArg) | |
846 | { | |
847 | fprintf (stderr, "[%s]", (s ? s : "?")); | |
848 | } | |
849 | else | |
850 | { | |
851 | fprintf (stderr, "%s", (val ? val : "(null)")); | |
852 | if (val && s && !strcasecmp (val, s)) | |
853 | fprintf (stderr, " [default]"); | |
854 | } | |
855 | fprintf (stderr, "\n"); | |
856 | } | |
857 | fprintf (stderr, "\n"); | |
858 | } | |
859 | #endif | |
860 | ||
861 | exit (help_p ? 0 : 1); | |
862 | } | |
863 | ||
864 | { | |
865 | char **s; | |
866 | for (s = merged_defaults; *s; s++) | |
867 | free(*s); | |
868 | } | |
869 | ||
870 | free (merged_options); | |
871 | free (merged_defaults); | |
872 | merged_options = 0; | |
873 | merged_defaults = 0; | |
874 | ||
875 | dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean"); | |
876 | mono_p = get_boolean_resource (dpy, "mono", "Boolean"); | |
877 | if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2) | |
878 | mono_p = True; | |
879 | ||
880 | root_p = get_boolean_resource (dpy, "root", "Boolean"); | |
881 | ||
882 | # ifdef EXIT_AFTER | |
883 | { | |
884 | int secs = get_integer_resource (dpy, "exitAfter", "Integer"); | |
885 | exit_after = (secs > 0 | |
886 | ? time((time_t *) 0) + secs | |
887 | : 0); | |
888 | } | |
889 | # endif | |
890 | ||
891 | { | |
892 | char *s = get_string_resource (dpy, "windowID", "WindowID"); | |
893 | if (s && *s) | |
894 | on_window = get_integer_resource (dpy, "windowID", "WindowID"); | |
895 | if (s) free (s); | |
896 | } | |
897 | ||
898 | if (on_window) | |
899 | { | |
900 | window = (Window) on_window; | |
901 | XtDestroyWidget (toplevel); | |
902 | XGetWindowAttributes (dpy, window, &xgwa); | |
903 | visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True); | |
904 | ||
905 | /* Select KeyPress and resize events on the external window. | |
906 | */ | |
907 | xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask; | |
908 | XSelectInput (dpy, window, xgwa.your_event_mask); | |
909 | ||
910 | /* Select ButtonPress and ButtonRelease events on the external window, | |
911 | if no other app has already selected them (only one app can select | |
912 | ButtonPress at a time: BadAccess results.) | |
913 | */ | |
914 | if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask))) | |
915 | XSelectInput (dpy, window, | |
916 | (xgwa.your_event_mask | | |
917 | ButtonPressMask | ButtonReleaseMask)); | |
918 | } | |
919 | else if (root_p) | |
920 | { | |
921 | window = VirtualRootWindowOfScreen (XtScreen (toplevel)); | |
922 | XtDestroyWidget (toplevel); | |
923 | XGetWindowAttributes (dpy, window, &xgwa); | |
924 | /* With RANDR, the root window can resize! */ | |
925 | XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask); | |
926 | visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False); | |
927 | } | |
928 | else | |
929 | { | |
930 | Widget new = make_shell (XtScreen (toplevel), toplevel, | |
931 | toplevel->core.width, | |
932 | toplevel->core.height); | |
933 | if (new != toplevel) | |
934 | { | |
935 | XtDestroyWidget (toplevel); | |
936 | toplevel = new; | |
937 | } | |
938 | ||
939 | init_window (dpy, toplevel, version); | |
940 | window = XtWindow (toplevel); | |
941 | XGetWindowAttributes (dpy, window, &xgwa); | |
942 | ||
943 | # ifdef DEBUG_PAIR | |
944 | if (get_boolean_resource (dpy, "pair", "Boolean")) | |
945 | { | |
946 | toplevel2 = make_shell (xgwa.screen, 0, | |
947 | toplevel->core.width, | |
948 | toplevel->core.height); | |
949 | init_window (dpy, toplevel2, version); | |
950 | window2 = XtWindow (toplevel2); | |
951 | } | |
952 | # endif /* DEBUG_PAIR */ | |
953 | } | |
954 | ||
955 | if (!dont_clear) | |
956 | { | |
957 | unsigned int bg = get_pixel_resource (dpy, xgwa.colormap, | |
958 | "background", "Background"); | |
959 | XSetWindowBackground (dpy, window, bg); | |
960 | XClearWindow (dpy, window); | |
961 | # ifdef DEBUG_PAIR | |
962 | if (window2) | |
963 | { | |
964 | XSetWindowBackground (dpy, window2, bg); | |
965 | XClearWindow (dpy, window2); | |
966 | } | |
967 | # endif | |
968 | } | |
969 | ||
970 | if (!root_p && !on_window) | |
971 | /* wait for it to be mapped */ | |
972 | XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window); | |
973 | ||
974 | XSync (dpy, False); | |
975 | ||
976 | /* This is the one and only place that the random-number generator is | |
977 | seeded in any screenhack. You do not need to seed the RNG again, | |
978 | it is done for you before your code is invoked. */ | |
979 | # undef ya_rand_init | |
980 | ya_rand_init (0); | |
981 | ||
982 | ||
983 | #ifdef HAVE_RECORD_ANIM | |
984 | { | |
985 | int frames = get_integer_resource (dpy, "recordAnim", "Integer"); | |
986 | if (frames > 0) | |
987 | anim_state = screenhack_record_anim_init (xgwa.screen, window, frames); | |
988 | } | |
989 | #endif | |
990 | ||
991 | run_screenhack_table (dpy, window, | |
992 | # ifdef DEBUG_PAIR | |
993 | window2, | |
994 | # endif | |
995 | # ifdef HAVE_RECORD_ANIM | |
996 | anim_state, | |
997 | # endif | |
998 | ft); | |
999 | ||
1000 | #ifdef HAVE_RECORD_ANIM | |
1001 | if (anim_state) screenhack_record_anim_free (anim_state); | |
1002 | #endif | |
1003 | ||
1004 | XtDestroyWidget (toplevel); | |
1005 | XtDestroyApplicationContext (app); | |
1006 | ||
1007 | return 0; | |
1008 | } |