1 /* xscreensaver, Copyright (c) 1992-2018 Jamie Zawinski <jwz@jwz.org>
2 *
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
9 * implied warranty.
10 */
11
12 /* This file contains code for running an external program to grab an image
13 onto the given window. The external program in question must take a
14 window ID as its argument, e.g., the "xscreensaver-getimage" program
15 in the hacks/ directory.
16
17 That program links against utils/grabimage.c, which happens to export the
18 same API as this program (utils/grabclient.c).
19 */
20
21 /* This code is a mess. There's two decades of history in this file.
22 There are several distinct paths through this file depending on what
23 platform it's being compiled for:
24
25
26 X11 execution path:
27
28 load_image_async CB
29 load_random_image_x11
30 fork_exec_cb
31 "xscreensaver-getimage 0xWINDOW 0xPIXMAP"
32 "xscreensaver-getimage-file --name /DIR"
33 draw_colorbars
34 XPutImage
35 XtAppAddInput xscreensaver_getimage_cb
36 ...
37 xscreensaver_getimage_cb
38 get_name_from_xprops
39 get_original_geometry_from_xprops
40 CB name, geom, closure
41
42
43 MacOS execution path:
44
45 load_image_async CB
46 load_random_image_cocoa
47 osx_grab_desktop_image (grabclient-osx.m, MacOS version)
48 copy_framebuffer_to_ximage
49 XPutImage
50 draw_colorbars
51 osx_load_image_file_async
52 open_image_name_pipe
53 "xscreensaver-getimage-file --name /DIR"
54 XtAppAddInput xscreensaver_getimage_file_cb
55 ...
56 xscreensaver_getimage_file_cb
57 osx_load_image_file
58 CB name, geom, closure
59
60
61 iOS execution path:
62
63 load_image_async CB
64 load_random_image_cocoa
65 osx_grab_desktop_image (grabclient-osx.m, iOS version)
66 CGWindowListCreateImage
67 jwxyz_draw_NSImage_or_CGImage
68 draw_colorbars
69 ios_load_random_image
70 ios_load_random_image_cb
71 jwxyz_draw_NSImage_or_CGImage
72 CB name, geom, closure
73
74
75 Android execution path:
76
77 load_image_async CB
78 load_random_image_android
79 jwxyz_load_random_image (jwxyz-android.c)
80 XPutImage
81 draw_colorbars
82 CB name, geom, closure
83 */
84
85 #include "utils.h"
86 #include "grabscreen.h"
87 #include "resources.h"
88 #include "yarandom.h"
89
90 #ifdef HAVE_JWXYZ
91 # include "jwxyz.h"
92 # include "colorbars.h"
93 #else /* !HAVE_COCOA -- real Xlib */
94 # include "vroot.h"
95 # include <X11/Xatom.h>
96 # include <X11/Intrinsic.h> /* for XtInputId, etc */
97 #endif /* !HAVE_COCOA */
98
99 #include <sys/stat.h>
100
101 #ifdef HAVE_UNISTD_H
102 # include <unistd.h>
103 #endif
104 #ifdef HAVE_SYS_WAIT_H
105 # include <sys/wait.h> /* for waitpid() and associated macros */
106 #endif
107
108
109 extern char *progname;
110
111 static void print_loading_msg (Screen *, Window);
112
113
114 /* Used for pipe callbacks in X11 or OSX mode.
115 X11: this is the xscreensaver_getimage_cb closure,
116 when waiting on the fork of "xscreensaver-getimage"
117 OSX: this is the xscreensaver_getimage_file_cb closure,
118 when waiting on the fork of "xscreensaver-getimage-file"
119 */
120 typedef struct {
121 void (*callback) (Screen *, Window, Drawable,
122 const char *name, XRectangle *geom, void *closure);
123 Screen *screen;
124 Window window;
125 Drawable drawable;
126 void *closure;
127 XtInputId pipe_id;
128 FILE *pipe;
129
130 # if !defined(USE_IPHONE) && !defined(HAVE_COCOA) /* Real X11 */
131 pid_t pid;
132 # endif
133
134 # if !defined(USE_IPHONE) && defined(HAVE_COCOA) /* Desktop OSX */
135 char *directory;
136 # endif
137
138 } xscreensaver_getimage_data;
139
140
141 #if !defined(HAVE_COCOA) && !defined(HAVE_ANDROID) /* Real X11 */
142
143 static Bool error_handler_hit_p = False;
144
145 static int
ignore_all_errors_ehandler(Display * dpy,XErrorEvent * error)146 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
147 {
148 error_handler_hit_p = True;
149 return 0;
150 }
151
152
153 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
154 */
155 static Bool
drawable_window_p(Display * dpy,Drawable d)156 drawable_window_p (Display *dpy, Drawable d)
157 {
158 XErrorHandler old_handler;
159 XWindowAttributes xgwa;
160
161 XSync (dpy, False);
162 old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
163 error_handler_hit_p = False;
164 XGetWindowAttributes (dpy, d, &xgwa);
165 XSync (dpy, False);
166 XSetErrorHandler (old_handler);
167 XSync (dpy, False);
168
169 if (!error_handler_hit_p)
170 return True; /* It's a Window. */
171 else
172 return False; /* It's a Pixmap, or an invalid ID. */
173 }
174
175
176 static Bool
xscreensaver_window_p(Display * dpy,Window window)177 xscreensaver_window_p (Display *dpy, Window window)
178 {
179 Atom type;
180 int format;
181 unsigned long nitems, bytesafter;
182 unsigned char *version;
183 if (XGetWindowProperty (dpy, window,
184 XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
185 0, 1, False, XA_STRING,
186 &type, &format, &nitems, &bytesafter,
187 &version)
188 == Success
189 && type != None)
190 return True;
191 return False;
192 }
193
194
195 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
196 on a window whose depth is not the maximal depth of the screen? Or
197 something. Anyway, things don't work unless we: use SubwindowMode for
198 the real root window (or a legitimate virtual root window); but do not
199 use SubwindowMode for the xscreensaver window. I make no attempt to
200 explain.
201 */
202 Bool
use_subwindow_mode_p(Screen * screen,Window window)203 use_subwindow_mode_p (Screen *screen, Window window)
204 {
205 if (window != VirtualRootWindowOfScreen(screen))
206 return False;
207 else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
208 return False;
209 else
210 return True;
211 }
212
213
214 static void
checkerboard(Screen * screen,Drawable drawable)215 checkerboard (Screen *screen, Drawable drawable)
216 {
217 Display *dpy = DisplayOfScreen (screen);
218 unsigned int x, y;
219 int size = 24;
220 XColor fg, bg;
221 XGCValues gcv;
222 GC gc = XCreateGC (dpy, drawable, 0, &gcv);
223 Colormap cmap;
224 unsigned int win_width, win_height;
225
226 fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
227 fg.red = fg.green = fg.blue = 0x0000;
228 bg.red = bg.green = bg.blue = 0x4444;
229 fg.pixel = 0;
230 bg.pixel = 1;
231
232 if (drawable_window_p (dpy, drawable))
233 {
234 XWindowAttributes xgwa;
235 XGetWindowAttributes (dpy, drawable, &xgwa);
236 win_width = xgwa.width;
237 win_height = xgwa.height;
238 cmap = xgwa.colormap;
239 screen = xgwa.screen;
240 }
241 else /* it's a pixmap */
242 {
243 XWindowAttributes xgwa;
244 Window root;
245 int x, y;
246 unsigned int bw, d;
247 XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
248 cmap = xgwa.colormap;
249 XGetGeometry (dpy, drawable,
250 &root, &x, &y, &win_width, &win_height, &bw, &d);
251 }
252
253 /* Allocate black and gray, but don't hold them locked. */
254 if (XAllocColor (dpy, cmap, &fg))
255 XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
256 if (XAllocColor (dpy, cmap, &bg))
257 XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
258
259 XSetForeground (dpy, gc, bg.pixel);
260 XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
261 XSetForeground (dpy, gc, fg.pixel);
262 for (y = 0; y < win_height; y += size+size)
263 for (x = 0; x < win_width; x += size+size)
264 {
265 XFillRectangle (dpy, drawable, gc, x, y, size, size);
266 XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
267 }
268 XFreeGC (dpy, gc);
269 }
270
271
272 /* Read the image's original name off of the window's X properties.
273 Used only when running "real" X11, not jwxyz.
274 */
275 static char *
get_name_from_xprops(Display * dpy,Window window)276 get_name_from_xprops (Display *dpy, Window window)
277 {
278 Atom type;
279 int format;
280 unsigned long nitems, bytesafter;
281 unsigned char *name = 0;
282 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
283 if (XGetWindowProperty (dpy, window, atom,
284 0, 1024, False, XA_STRING,
285 &type, &format, &nitems, &bytesafter,
286 &name)
287 == Success
288 && type != None)
289 return (char *) name;
290 else
291 return 0;
292 }
293
294
295 /* Read the image's original geometry off of the window's X properties.
296 Used only when running "real" X11, not jwxyz.
297 */
298 static Bool
get_original_geometry_from_xprops(Display * dpy,Window window,XRectangle * ret)299 get_original_geometry_from_xprops (Display *dpy, Window window, XRectangle *ret)
300 {
301 Atom type;
302 int format;
303 unsigned long nitems, bytesafter;
304 unsigned char *name = 0;
305 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
306 int x, y;
307 unsigned int w, h;
308 if (XGetWindowProperty (dpy, window, atom,
309 0, 1024, False, XA_STRING,
310 &type, &format, &nitems, &bytesafter,
311 &name)
312 == Success
313 && type != None)
314 {
315 int flags = XParseGeometry ((char *) name, &x, &y, &w, &h);
316 free (name);
317 /* Require all four, and don't allow negative positions. */
318 if (flags == (XValue|YValue|WidthValue|HeightValue))
319 {
320 ret->x = x;
321 ret->y = y;
322 ret->width = w;
323 ret->height = h;
324 return True;
325 }
326 else
327 return False;
328 }
329 else
330 return False;
331 }
332
333
334 static void
hack_subproc_environment(Display * dpy)335 hack_subproc_environment (Display *dpy)
336 {
337 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
338 the spawned processes inherit is what we are actually using.
339 */
340 const char *odpy = DisplayString (dpy);
341 char *ndpy = (char *) malloc(strlen(odpy) + 20);
342 strcpy (ndpy, "DISPLAY=");
343 strcat (ndpy, odpy);
344
345 /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
346 any more, right? It's not Posix, but everyone seems to have it. */
347 # ifdef HAVE_PUTENV
348 if (putenv (ndpy))
349 abort ();
350 # endif /* HAVE_PUTENV */
351
352 /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
353 glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2, MacOS)
354 do not. So we must leak it (and/or the previous setting). Yay.
355 */
356 }
357
358
359 /* Spawn a program, and wait for it to finish.
360 If we just use system() for this, then sometimes the subprocess
361 doesn't die when *this* process is sent a TERM signal. Perhaps
362 this is due to the intermediate /bin/sh that system() uses to
363 parse arguments? I'm not sure. But using fork() and execvp()
364 here seems to close the race.
365
366 Used to execute "xscreensaver-getimage".
367 Used only when running "real" X11, not jwxyz.
368 */
369 static void
exec_simple_command(const char * command)370 exec_simple_command (const char *command)
371 {
372 char *av[1024];
373 int ac = 0;
374 char *token = strtok (strdup(command), " \t");
375 while (token)
376 {
377 av[ac++] = token;
378 token = strtok(0, " \t");
379 }
380 av[ac] = 0;
381
382 execvp (av[0], av); /* shouldn't return. */
383 }
384
385
386 static void xscreensaver_getimage_cb (XtPointer closure,
387 int *fd, XtIntervalId *id);
388
389 /* Spawn a program, and run the callback when it finishes.
390 Used to execute "xscreensaver-getimage".
391 Used only when running "real" X11, not jwxyz.
392 */
393 static void
fork_exec_cb(const char * command,Screen * screen,Window window,Drawable drawable,void (* callback)(Screen *,Window,Drawable,const char * name,XRectangle * geom,void * closure),void * closure)394 fork_exec_cb (const char *command,
395 Screen *screen, Window window, Drawable drawable,
396 void (*callback) (Screen *, Window, Drawable,
397 const char *name, XRectangle *geom,
398 void *closure),
399 void *closure)
400 {
401 XtAppContext app = XtDisplayToApplicationContext (DisplayOfScreen (screen));
402 xscreensaver_getimage_data *data;
403 char buf [255];
404 pid_t forked;
405 FILE *wpipe;
406
407 int fds [2];
408
409 if (pipe (fds))
410 {
411 sprintf (buf, "%s: creating pipe", progname);
412 perror (buf);
413 exit (1);
414 }
415
416 data = (xscreensaver_getimage_data *) calloc (1, sizeof(*data));
417 data->callback = callback;
418 data->closure = closure;
419 data->screen = screen;
420 data->window = window;
421 data->drawable = drawable;
422 data->pipe = fdopen (fds[0], "r");
423 wpipe = fdopen (fds[1], "w"); /* Is this necessary? */
424
425 if (!data->pipe || !wpipe)
426 {
427 sprintf (buf, "%s: fdopen", progname);
428 perror (buf);
429 exit (1);
430 }
431
432 data->pipe_id =
433 XtAppAddInput (app, fileno (data->pipe),
434 (XtPointer) (XtInputReadMask | XtInputExceptMask),
435 xscreensaver_getimage_cb, (XtPointer) data);
436
437 forked = fork ();
438 switch ((int) forked)
439 {
440 case -1:
441 sprintf (buf, "%s: couldn't fork", progname);
442 perror (buf);
443 return;
444
445 case 0: /* child */
446
447 fclose (data->pipe);
448 data->pipe = 0;
449
450 /* clone the write pipe onto stdout so that it gets closed
451 when the fork exits. This will shut down the pipe and
452 signal the parent.
453 */
454 close (fileno (stdout));
455 dup2 (fds[1], fileno (stdout));
456 close (fds[1]);
457
458 close (fileno (stdin)); /* for kicks */
459
460 exec_simple_command (command);
461 exit (1); /* exits child fork */
462 break;
463
464 default: /* parent */
465 fclose (wpipe);
466 data->pid = forked;
467 break;
468 }
469 }
470
471
472 /* Called in the parent when the forked process dies.
473 Runs the caller's callback, and cleans up.
474 This runs when "xscreensaver-getimage" exits.
475 Used only when running "real" X11, not jwxyz.
476 */
477 static void
xscreensaver_getimage_cb(XtPointer closure,int * fd,XtIntervalId * id)478 xscreensaver_getimage_cb (XtPointer closure, int *fd, XtIntervalId *id)
479 {
480 xscreensaver_getimage_data *data = (xscreensaver_getimage_data *) closure;
481 Display *dpy = DisplayOfScreen (data->screen);
482 char *name;
483 XRectangle geom = { 0, 0, 0, 0 };
484
485 XtRemoveInput (*id);
486
487 name = get_name_from_xprops (dpy, data->window);
488 get_original_geometry_from_xprops (dpy, data->window, &geom);
489
490 data->callback (data->screen, data->window, data->drawable,
491 name, &geom, data->closure);
492 if (name) free (name);
493
494 fclose (data->pipe);
495
496 if (data->pid) /* reap zombies */
497 {
498 int status;
499 waitpid (data->pid, &status, 0);
500 data->pid = 0;
501 }
502
503 memset (data, 0, sizeof (*data));
504 free (data);
505 }
506
507
508 /* Loads an image into the Drawable.
509 When grabbing desktop images, the Window will be unmapped first.
510 Used only when running "real" X11, not jwxyz.
511 */
512 static void
load_random_image_x11(Screen * screen,Window window,Drawable drawable,void (* callback)(Screen *,Window,Drawable,const char * name,XRectangle * geom,void * closure),void * closure)513 load_random_image_x11 (Screen *screen, Window window, Drawable drawable,
514 void (*callback) (Screen *, Window, Drawable,
515 const char *name, XRectangle *geom,
516 void *closure),
517 void *closure)
518 {
519 Display *dpy = DisplayOfScreen (screen);
520 char *grabber = get_string_resource(dpy, "desktopGrabber", "DesktopGrabber");
521 char *cmd;
522 char id[200];
523
524 if (!grabber || !*grabber)
525 {
526 fprintf (stderr,
527 "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
528 progname);
529 exit (1);
530 }
531
532 sprintf (id, "0x%lx 0x%lx",
533 (unsigned long) window,
534 (unsigned long) drawable);
535 cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
536
537 /* Needn't worry about buffer overflows here, because the buffer is
538 longer than the length of the format string, and the length of what
539 we're putting into it. So the only way to crash would be if the
540 format string itself was corrupted, but that comes from the
541 resource database, and if hostile forces have access to that,
542 then the game is already over.
543 */
544 sprintf (cmd, grabber, id);
545 free (grabber);
546 grabber = 0;
547
548 /* In case "cmd" fails, leave some random image on the screen, not just
549 black or white, so that it's more obvious what went wrong. */
550 checkerboard (screen, drawable);
551 if (window == drawable)
552 print_loading_msg (screen, window);
553
554 XSync (dpy, True);
555 hack_subproc_environment (dpy);
556
557 /* Start the image loading in another fork and return immediately.
558 Invoke the callback function when done. */
559 fork_exec_cb (cmd, screen, window, drawable, callback, closure);
560
561 free (cmd);
562 XSync (dpy, True);
563 }
564
565 #elif defined (HAVE_COCOA) /* OSX or iOS */
566
567 # ifndef USE_IPHONE /* HAVE_COCOA && !USE_IPHONE -- desktop OSX */
568
569 # define BACKSLASH(c) \
570 (! ((c >= 'a' && c <= 'z') || \
571 (c >= 'A' && c <= 'Z') || \
572 (c >= '0' && c <= '9') || \
573 c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
574
575 /* Gets the name of an image file to load by running xscreensaver-getimage-file
576 at the end of a pipe. This can be very slow!
577 */
578 static FILE *
open_image_name_pipe(const char * dir)579 open_image_name_pipe (const char *dir)
580 {
581 char *s;
582
583 /* /bin/sh on OS X 10.10 wipes out the PATH. */
584 const char *path = getenv("PATH");
585 char *cmd = s = malloc ((strlen(dir) + strlen(path)) * 2 + 100);
586 strcpy (s, "/bin/sh -c 'export PATH=");
587 s += strlen (s);
588 while (*path) {
589 char c = *path++;
590 if (BACKSLASH(c)) *s++ = '\\';
591 *s++ = c;
592 }
593 strcpy (s, "; ");
594 s += strlen (s);
595
596 strcpy (s, "xscreensaver-getimage-file --name ");
597 s += strlen (s);
598 while (*dir) {
599 char c = *dir++;
600 if (BACKSLASH(c)) *s++ = '\\';
601 *s++ = c;
602 }
603
604 strcpy (s, "'");
605 s += strlen (s);
606
607 *s = 0;
608
609 FILE *pipe = popen (cmd, "r");
610 free (cmd);
611 return pipe;
612 }
613
614
615 static void
xscreensaver_getimage_file_cb(XtPointer closure,int * source,XtInputId * id)616 xscreensaver_getimage_file_cb (XtPointer closure, int *source, XtInputId *id)
617 {
618 /* This is not called from a signal handler, so doing stuff here is fine.
619 */
620 xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure;
621 char buf[10240];
622 const char *dir = clo2->directory;
623 char *absfile = 0;
624 *buf = 0;
625 fgets (buf, sizeof(buf)-1, clo2->pipe);
626 pclose (clo2->pipe);
627 clo2->pipe = 0;
628 XtRemoveInput (clo2->pipe_id);
629 clo2->pipe_id = 0;
630
631 /* strip trailing newline */
632 int L = strlen(buf);
633 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
634 buf[--L] = 0;
635
636 Display *dpy = DisplayOfScreen (clo2->screen);
637 XRectangle geom;
638
639 if (*buf && *buf != '/') /* pathname is relative to dir. */
640 {
641 absfile = malloc (strlen(dir) + strlen(buf) + 10);
642 strcpy (absfile, dir);
643 if (dir[strlen(dir)-1] != '/')
644 strcat (absfile, "/");
645 strcat (absfile, buf);
646 }
647
648 if (! osx_load_image_file (clo2->screen, clo2->window, clo2->drawable,
649 (absfile ? absfile : buf), &geom)) {
650 /* unable to load image - draw colorbars
651 */
652 XWindowAttributes xgwa;
653 XGetWindowAttributes (dpy, clo2->window, &xgwa);
654 Window r;
655 int x, y;
656 unsigned int w, h, bbw, d;
657 struct stat st;
658
659 /* Log something to syslog so we can tell the difference between
660 corrupted images and broken symlinks. */
661 if (!*buf)
662 fprintf (stderr, "%s: no image filename found\n", progname);
663 else if (! stat (buf, &st))
664 fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
665 else
666 {
667 char buf2[2048];
668 sprintf (buf2, "%.255s: %.1024s", progname, buf);
669 perror (buf2);
670 }
671
672 XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
673 draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
674 0, 0, w, h, 0, 0); /* #### logo missing */
675 geom.x = geom.y = 0;
676 geom.width = w;
677 geom.height = h;
678 }
679
680 /* Take the extension off of the file name. */
681 /* Duplicated in driver/xscreensaver-getimage.c. */
682 if (*buf)
683 {
684 char *slash = strrchr (buf, '/');
685 char *dot = strrchr ((slash ? slash : buf), '.');
686 if (dot) *dot = 0;
687 /* Replace slashes with newlines */
688 /* while (dot = strchr(buf, '/')) *dot = '\n'; */
689 /* Replace slashes with spaces */
690 /* while ((dot = strchr(buf, '/'))) *dot = ' '; */
691 }
692
693 if (absfile) free (absfile);
694 clo2->callback (clo2->screen, clo2->window, clo2->drawable, buf, &geom,
695 clo2->closure);
696 clo2->callback = 0;
697 free (clo2->directory);
698 free (clo2);
699 }
700
701
702 # else /* HAVE_COCOA && USE_IPHONE -- iOS */
703
704 /* Callback for ios_load_random_image(), called after we have loaded an
705 image from the iOS device's Photo Library. See grabclient-ios.m.
706 */
707 static void
ios_load_random_image_cb(void * uiimage,const char * filename,int width,int height,void * closure)708 ios_load_random_image_cb (void *uiimage, const char *filename,
709 int width, int height, void *closure)
710 {
711 xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure;
712 Display *dpy = DisplayOfScreen (clo2->screen);
713 XRectangle geom;
714 XWindowAttributes xgwa;
715 Window r;
716 int x, y;
717 unsigned int w, h, bbw, d;
718 int rot = 0;
719
720 XGetWindowAttributes (dpy, clo2->window, &xgwa);
721 XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
722
723 /* If the image is portrait and the window is landscape, or vice versa,
724 rotate the image. The idea is to fill up as many pixels as possible,
725 and assume the user will just rotate their phone until it looks right.
726 This makes "decayscreen", etc. much more easily viewable.
727 */
728 if (get_boolean_resource (dpy, "rotateImages", "RotateImages")) {
729 if ((width > height) != (w > h))
730 rot = 5;
731 }
732
733 if (uiimage)
734 {
735 jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (clo2->screen),
736 clo2->drawable,
737 True, uiimage, &geom,
738 rot);
739 }
740 else /* Probably means no images in the gallery. */
741 {
742 draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
743 0, 0, w, h, 0, 0); /* #### logo missing */
744 geom.x = geom.y = 0;
745 geom.width = w;
746 geom.height = h;
747 filename = 0;
748 }
749
750 clo2->callback (clo2->screen, clo2->window, clo2->drawable,
751 filename, &geom, clo2->closure);
752 clo2->callback = 0;
753 free (clo2);
754 }
755
756 # endif /* HAVE_COCOA && USE_IPHONE */
757
758
759 static void
osx_load_image_file_async(Screen * screen,Window xwindow,Drawable drawable,const char * dir,void (* callback)(Screen *,Window,Drawable,const char * name,XRectangle * geom,void * closure),void * closure)760 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
761 const char *dir,
762 void (*callback) (Screen *, Window, Drawable,
763 const char *name,
764 XRectangle *geom,
765 void *closure),
766 void *closure)
767 {
768 xscreensaver_getimage_data *clo2 =
769 (xscreensaver_getimage_data *) calloc (1, sizeof(*clo2));
770
771 clo2->screen = screen;
772 clo2->window = xwindow;
773 clo2->drawable = drawable;
774 clo2->callback = callback;
775 clo2->closure = closure;
776
777 # ifndef USE_IPHONE /* Desktop OSX */
778 clo2->directory = strdup (dir);
779 clo2->pipe = open_image_name_pipe (dir);
780 clo2->pipe_id = XtAppAddInput (XtDisplayToApplicationContext (
781 DisplayOfScreen (screen)),
782 fileno (clo2->pipe),
783 (XtPointer) (XtInputReadMask | XtInputExceptMask),
784 xscreensaver_getimage_file_cb, (XtPointer) clo2);
785 # else /* USE_IPHONE */
786 {
787 XWindowAttributes xgwa;
788 XGetWindowAttributes (DisplayOfScreen (screen), xwindow, &xgwa);
789 ios_load_random_image (ios_load_random_image_cb, clo2,
790 xgwa.width, xgwa.height);
791 }
792 # endif /* USE_IPHONE */
793 }
794
795
796 /* Loads an image into the Drawable, returning once the image is loaded.
797 */
798 static void
load_random_image_cocoa(Screen * screen,Window window,Drawable drawable,void (* callback)(Screen *,Window,Drawable,const char * name,XRectangle * geom,void * closure),void * closure)799 load_random_image_cocoa (Screen *screen, Window window, Drawable drawable,
800 void (*callback) (Screen *, Window, Drawable,
801 const char *name, XRectangle *geom,
802 void *closure),
803 void *closure)
804 {
805 Display *dpy = DisplayOfScreen (screen);
806 XWindowAttributes xgwa;
807 Bool deskp = get_boolean_resource (dpy, "grabDesktopImages", "Boolean");
808 Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
809 const char *dir = 0;
810 Bool done = False;
811 XRectangle geom;
812 char *name = 0;
813
814 if (!drawable) abort();
815
816 XGetWindowAttributes (dpy, window, &xgwa);
817 {
818 Window r;
819 int x, y;
820 unsigned int w, h, bbw, d;
821 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
822 xgwa.width = w;
823 xgwa.height = h;
824 }
825
826 geom.x = 0;
827 geom.y = 0;
828 geom.width = xgwa.width;
829 geom.height = xgwa.height;
830
831 # ifndef USE_IPHONE
832 if (filep)
833 dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
834
835 if (!dir || !*dir)
836 filep = False;
837 # endif /* ! USE_IPHONE */
838
839 if (deskp && filep) {
840 deskp = !(random() & 5); /* if both, desktop 1/5th of the time */
841 filep = !deskp;
842 }
843
844 if (filep && !done) {
845 osx_load_image_file_async (screen, window, drawable, dir,
846 callback, closure);
847 return;
848 }
849
850 if (deskp && !done) {
851 if (osx_grab_desktop_image (screen, window, drawable, &geom)) {
852 name = strdup ("desktop");
853 done = True;
854 }
855 }
856
857 if (! done)
858 draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap, 0, 0,
859 xgwa.width, xgwa.height, 0, 0); /* #### logo missing */
860
861 /* If we got here, we loaded synchronously, so we're done. */
862 callback (screen, window, drawable, name, &geom, closure);
863 if (name) free (name);
864 }
865
866
867 #elif defined(HAVE_ANDROID)
868
869 /* Loads an image into the Drawable, returning once the image is loaded.
870 */
871 static void
load_random_image_android(Screen * screen,Window window,Drawable drawable,void (* callback)(Screen *,Window,Drawable,const char * name,XRectangle * geom,void * closure),void * closure)872 load_random_image_android (Screen *screen, Window window, Drawable drawable,
873 void (*callback) (Screen *, Window, Drawable,
874 const char *name,
875 XRectangle *geom, void *closure),
876 void *closure)
877 {
878 Display *dpy = DisplayOfScreen (screen);
879 XWindowAttributes xgwa;
880 XRectangle geom;
881
882 if (!drawable) abort();
883
884 XGetWindowAttributes (dpy, window, &xgwa);
885 {
886 Window r;
887 int x, y;
888 unsigned int w, h, bbw, d;
889 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
890 xgwa.width = w;
891 xgwa.height = h;
892 }
893
894 geom.x = 0;
895 geom.y = 0;
896 geom.width = xgwa.width;
897 geom.height = xgwa.height;
898
899 XGCValues gcv;
900 gcv.foreground = BlackPixelOfScreen (screen);
901 GC gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
902 XFillRectangle (dpy, drawable, gc, 0, 0, xgwa.width, xgwa.height);
903 char *name = jwxyz_draw_random_image (dpy, drawable, gc);
904 if (! name) {
905 draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap, 0, 0,
906 xgwa.width, xgwa.height, 0, 0); /* #### logo missing */
907 }
908 XFreeGC (dpy, gc);
909
910 callback (screen, window, drawable, name, &geom, closure);
911 if (name) free (name);
912 }
913
914 #endif /* HAVE_ANDROID */
915
916
917
918 /* Writes the string "Loading..." in the middle of the screen.
919 This will presumably get blown away when the image finally loads,
920 minutes or hours later...
921
922 This is called by load_image_async_simple() but not by load_image_async(),
923 since it is assumed that hacks that are loading more than one image
924 *at one time* will be doing something more clever than just blocking
925 with a blank screen.
926 */
927 static void
print_loading_msg(Screen * screen,Window window)928 print_loading_msg (Screen *screen, Window window)
929 {
930 Display *dpy = DisplayOfScreen (screen);
931 XWindowAttributes xgwa;
932 XGCValues gcv;
933 XFontStruct *f = 0;
934 GC gc;
935 char *fn = get_string_resource (dpy, "labelFont", "Font");
936 const char *text = "Loading...";
937 int w;
938
939 if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
940 if (!fn) fn = get_string_resource (dpy, "font", "Font");
941 if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
942 f = XLoadQueryFont (dpy, fn);
943 if (!f) f = XLoadQueryFont (dpy, "fixed");
944 if (!f) abort();
945 free (fn);
946 fn = 0;
947
948 XGetWindowAttributes (dpy, window, &xgwa);
949 w = XTextWidth (f, text, (int) strlen(text));
950
951 gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
952 "foreground", "Foreground");
953 gcv.background = get_pixel_resource (dpy, xgwa.colormap,
954 "background", "Background");
955 gcv.font = f->fid;
956 gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
957 XDrawImageString (dpy, window, gc,
958 (xgwa.width - w) / 2,
959 (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
960 text, (int) strlen(text));
961 XFreeFont (dpy, f);
962 XFreeGC (dpy, gc);
963 XSync (dpy, False);
964 }
965
966
967 /* Loads an image into the Drawable in the background;
968 when the image is fully loaded, runs the callback.
969 When grabbing desktop images, the Window will be unmapped first.
970 */
971 void
load_image_async(Screen * screen,Window window,Drawable drawable,void (* callback)(Screen *,Window,Drawable,const char * name,XRectangle * geom,void * closure),void * closure)972 load_image_async (Screen *screen, Window window, Drawable drawable,
973 void (*callback) (Screen *, Window, Drawable,
974 const char *name, XRectangle *geom,
975 void *closure),
976 void *closure)
977 {
978 if (!callback) abort();
979 # if defined(HAVE_COCOA)
980 load_random_image_cocoa (screen, window, drawable, callback, closure);
981 # elif defined(HAVE_ANDROID)
982 load_random_image_android (screen, window, drawable, callback, closure);
983 # else /* real X11 */
984 load_random_image_x11 (screen, window, drawable, callback, closure);
985 # endif
986 }
987
988 struct async_load_state {
989 Bool done_p;
990 char *filename;
991 XRectangle geom;
992 };
993
994 static void
load_image_async_simple_cb(Screen * screen,Window window,Drawable drawable,const char * name,XRectangle * geom,void * closure)995 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
996 const char *name, XRectangle *geom, void *closure)
997 {
998 async_load_state *state = (async_load_state *) closure;
999 state->done_p = True;
1000 state->filename = (name ? strdup (name) : 0);
1001 state->geom = *geom;
1002 }
1003
1004 async_load_state *
load_image_async_simple(async_load_state * state,Screen * screen,Window window,Drawable drawable,char ** filename_ret,XRectangle * geometry_ret)1005 load_image_async_simple (async_load_state *state,
1006 Screen *screen,
1007 Window window,
1008 Drawable drawable,
1009 char **filename_ret,
1010 XRectangle *geometry_ret)
1011 {
1012 if (state && state->done_p) /* done! */
1013 {
1014 if (filename_ret)
1015 *filename_ret = state->filename;
1016 else if (state->filename)
1017 free (state->filename);
1018
1019 if (geometry_ret)
1020 *geometry_ret = state->geom;
1021
1022 free (state);
1023 return 0;
1024 }
1025 else if (! state) /* first time */
1026 {
1027 state = (async_load_state *) calloc (1, sizeof(*state));
1028 state->done_p = False;
1029 print_loading_msg (screen, window);
1030 load_image_async (screen, window, drawable,
1031 load_image_async_simple_cb,
1032 state);
1033 return state;
1034 }
1035 else /* still waiting */
1036 return state;
1037 }
1038