http://www.jwz.org/xscreensaver/xscreensaver-5.09.tar.gz
[xscreensaver] / driver / remote.c
1 /* xscreensaver-command, Copyright (c) 1991-2009 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 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <time.h>
19 #include <sys/time.h>
20 #include <sys/types.h>
21
22 #ifdef HAVE_SYS_SELECT_H
23 # include <sys/select.h>
24 #endif /* HAVE_SYS_SELECT_H */
25
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
29
30 #include <X11/Xproto.h>         /* for CARD32 */
31 #include <X11/Xlib.h>
32 #include <X11/Xatom.h>
33 #include <X11/Xutil.h>          /* for XGetClassHint() */
34 #include <X11/Xos.h>
35
36 #include "remote.h"
37
38 #ifdef _VROOT_H_
39 ERROR! you must not include vroot.h in this file
40 #endif
41
42 extern char *progname;
43 extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE;
44 extern Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_EXIT;
45 extern Atom XA_VROOT, XA_SELECT, XA_DEMO, XA_BLANK, XA_LOCK;
46
47
48 static XErrorHandler old_handler = 0;
49 static Bool got_badwindow = False;
50 static int
51 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
52 {
53   if (error->error_code == BadWindow)
54     {
55       got_badwindow = True;
56       return 0;
57     }
58   else
59     {
60       fprintf (stderr, "%s: ", progname);
61       if (!old_handler) abort();
62       return (*old_handler) (dpy, error);
63     }
64 }
65
66
67
68 static Window
69 find_screensaver_window (Display *dpy, char **version)
70 {
71   int i;
72   Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
73   Window root2, parent, *kids;
74   unsigned int nkids;
75
76   if (version) *version = 0;
77
78   if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
79     abort ();
80   if (root != root2)
81     abort ();
82   if (parent)
83     abort ();
84   if (! (kids && nkids))
85     return 0;
86   for (i = 0; i < nkids; i++)
87     {
88       Atom type;
89       int format;
90       unsigned long nitems, bytesafter;
91       unsigned char *v;
92       int status;
93
94       /* We're walking the list of root-level windows and trying to find
95          the one that has a particular property on it.  We need to trap
96          BadWindows errors while doing this, because it's possible that
97          some random window might get deleted in the meantime.  (That
98          window won't have been the one we're looking for.)
99        */
100       XSync (dpy, False);
101       if (old_handler) abort();
102       got_badwindow = False;
103       old_handler = XSetErrorHandler (BadWindow_ehandler);
104       status = XGetWindowProperty (dpy, kids[i],
105                                    XA_SCREENSAVER_VERSION,
106                                    0, 200, False, XA_STRING,
107                                    &type, &format, &nitems, &bytesafter,
108                                    &v);
109       XSync (dpy, False);
110       XSetErrorHandler (old_handler);
111       old_handler = 0;
112
113       if (got_badwindow)
114         {
115           status = BadWindow;
116           got_badwindow = False;
117         }
118
119       if (status == Success && type != None)
120         {
121           Window ret = kids[i];
122           if (version)
123             *version = (char *) v;
124           XFree (kids);
125           return ret;
126         }
127     }
128
129   if (kids) XFree (kids);
130   return 0;
131 }
132
133
134 static int
135 send_xscreensaver_command (Display *dpy, Atom command, long arg,
136                            Window *window_ret, char **error_ret)
137 {
138   int status = -1;
139   char *v = 0;
140   Window window = find_screensaver_window (dpy, &v);
141   XWindowAttributes xgwa;
142   char err[2048];
143
144   if (window_ret)
145     *window_ret = window;
146
147   if (!window)
148     {
149       sprintf (err, "no screensaver is running on display %s",
150                DisplayString (dpy));
151
152       if (error_ret)
153         {
154           *error_ret = strdup (err);
155           status = -1;
156           goto DONE;
157         }
158
159       if (command == XA_EXIT)
160         {
161           /* Don't print an error if xscreensaver is already dead. */
162           status = 1;
163           goto DONE;
164         }
165
166       fprintf (stderr, "%s: %s\n", progname, err);
167       status = -1;
168       goto DONE;
169     }
170
171   /* Select for property change events, so that we can read the response. */
172   XGetWindowAttributes (dpy, window, &xgwa);
173   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
174
175   if (command == XA_SCREENSAVER_STATUS ||
176       command == XA_SCREENSAVER_VERSION)
177     {
178       XClassHint hint;
179       memset (&hint, 0, sizeof(hint));
180       if (!v || !*v)
181         {
182           sprintf (err, "version property not set on window 0x%x?",
183                    (unsigned int) window);
184           if (error_ret)
185             *error_ret = strdup (err);
186           else
187             fprintf (stderr, "%s: %s\n", progname, err);
188
189           status = -1;
190           goto DONE;
191         }
192
193       XGetClassHint(dpy, window, &hint);
194       if (!hint.res_class)
195         {
196           sprintf (err, "class hints not set on window 0x%x?",
197                    (unsigned int) window);
198           if (error_ret)
199             *error_ret = strdup (err);
200           else
201             fprintf (stderr, "%s: %s\n", progname, err);
202
203           status = -1;
204           goto DONE;
205         }
206
207       fprintf (stdout, "%s %s", hint.res_class, v);
208
209       if (command != XA_SCREENSAVER_STATUS)
210         {
211           fprintf (stdout, "\n");
212         }
213       else
214         {
215           Atom type;
216           int format;
217           unsigned long nitems, bytesafter;
218           unsigned char *dataP = 0;
219
220           if (XGetWindowProperty (dpy,
221                                   RootWindow (dpy, 0),
222                                   XA_SCREENSAVER_STATUS,
223                                   0, 999, False, XA_INTEGER,
224                                   &type, &format, &nitems, &bytesafter,
225                                   &dataP)
226               == Success
227               && type
228               && dataP)
229             {
230               Atom blanked;
231               time_t tt;
232               char *s;
233               Atom *data = (Atom *) dataP;
234
235               if (type != XA_INTEGER || nitems < 3)
236                 {
237                 STATUS_LOSE:
238                   if (data) free (data);
239                   fprintf (stdout, "\n");
240                   fflush (stdout);
241                   fprintf (stderr, "bad status format on root window.\n");
242                   status = -1;
243                   goto DONE;
244                 }
245                   
246               blanked = (Atom) data[0];
247               tt = (time_t) data[1];
248
249               if (tt <= (time_t) 666000000L) /* early 1991 */
250                 goto STATUS_LOSE;
251
252               if (blanked == XA_BLANK)
253                 fputs (": screen blanked since ", stdout);
254               else if (blanked == XA_LOCK)
255                 fputs (": screen locked since ", stdout);
256               else if (blanked == 0)
257                 /* suggestions for a better way to phrase this are welcome. */
258                 fputs (": screen non-blanked since ", stdout);
259               else
260                 /* `blanked' has an unknown value - fail. */
261                 goto STATUS_LOSE;
262
263               s = ctime(&tt);
264               if (s[strlen(s)-1] == '\n')
265                 s[strlen(s)-1] = 0;
266               fputs (s, stdout);
267
268               {
269                 int nhacks = nitems - 2;
270                 Bool any = False;
271                 int i;
272                 for (i = 0; i < nhacks; i++)
273                   if (data[i + 2] > 0)
274                     {
275                       any = True;
276                       break;
277                     }
278
279                 if (any && nhacks == 1)
280                   fprintf (stdout, " (hack #%d)\n", (int) data[2]);
281                 else if (any)
282                   {
283                     fprintf (stdout, " (hacks: ");
284                     for (i = 0; i < nhacks; i++)
285                       {
286                         fprintf (stdout, "#%d", (int) data[2 + i]);
287                         if (i != nhacks-1)
288                           fputs (", ", stdout);
289                       }
290                     fputs (")\n", stdout);
291                   }
292                 else
293                   fputs ("\n", stdout);
294               }
295
296               if (data) free (data);
297             }
298           else
299             {
300               if (dataP) XFree (dataP);
301               fprintf (stdout, "\n");
302               fflush (stdout);
303               fprintf (stderr, "no saver status on root window.\n");
304               status = -1;
305               goto DONE;
306             }
307         }
308
309       /* No need to read a response for these commands. */
310       status = 1;
311       goto DONE;
312     }
313   else
314     {
315       XEvent event;
316       long arg1 = arg;
317       long arg2 = 0;
318
319       if (arg < 0)
320         abort();
321       else if (arg == 0 && command == XA_SELECT)
322         abort();
323       else if (arg != 0 && command == XA_DEMO)
324         {
325           arg1 = 5000;  /* version number of the XA_DEMO protocol, */
326           arg2 = arg;   /* since it didn't use to take an argument. */
327         }
328
329       event.xany.type = ClientMessage;
330       event.xclient.display = dpy;
331       event.xclient.window = window;
332       event.xclient.message_type = XA_SCREENSAVER;
333       event.xclient.format = 32;
334       memset (&event.xclient.data, 0, sizeof(event.xclient.data));
335       event.xclient.data.l[0] = (long) command;
336       event.xclient.data.l[1] = arg1;
337       event.xclient.data.l[2] = arg2;
338       if (! XSendEvent (dpy, window, False, 0L, &event))
339         {
340           sprintf (err, "XSendEvent(dpy, 0x%x ...) failed.\n",
341                    (unsigned int) window);
342           if (error_ret)
343             *error_ret = strdup (err);
344           else
345             fprintf (stderr, "%s: %s\n", progname, err);
346           status = -1;
347           goto DONE;
348         }
349     }
350
351   status = 0;
352
353  DONE:
354   if (v) free (v);
355   XSync (dpy, 0);
356   return status;
357 }
358
359
360 static Bool
361 xscreensaver_command_event_p (Display *dpy, XEvent *event, XPointer arg)
362 {
363   return (event->xany.type == PropertyNotify &&
364           event->xproperty.state == PropertyNewValue &&
365           event->xproperty.atom == XA_SCREENSAVER_RESPONSE);
366 }
367
368
369 static int
370 xscreensaver_command_response (Display *dpy, Window window,
371                                Bool verbose_p, Bool exiting_p,
372                                char **error_ret)
373 {
374   int sleep_count = 0;
375   char err[2048];
376   XEvent event;
377   Bool got_event = False;
378
379   while (!(got_event = XCheckIfEvent(dpy, &event,
380                                      &xscreensaver_command_event_p, 0)) &&
381          sleep_count++ < 10)
382     {
383       sleep(1);
384     }
385
386   if (!got_event)
387     {
388       sprintf (err, "no response to command.");
389       if (error_ret)
390         *error_ret = strdup (err);
391       else
392         fprintf (stderr, "%s: %s\n", progname, err);
393
394       return -1;
395     }
396   else
397     {
398       Status st2;
399       Atom type;
400       int format;
401       unsigned long nitems, bytesafter;
402       unsigned char *msg = 0;
403
404       XSync (dpy, False);
405       if (old_handler) abort();
406       old_handler = XSetErrorHandler (BadWindow_ehandler);
407       st2 = XGetWindowProperty (dpy, window,
408                                 XA_SCREENSAVER_RESPONSE,
409                                 0, 1024, True,
410                                 AnyPropertyType,
411                                 &type, &format, &nitems, &bytesafter,
412                                 &msg);
413       XSync (dpy, False);
414       XSetErrorHandler (old_handler);
415       old_handler = 0;
416
417       if (got_badwindow)
418         {
419           if (exiting_p)
420             return 0;
421
422           sprintf (err, "xscreensaver window unexpectedly deleted.");
423
424           if (error_ret)
425             *error_ret = strdup (err);
426           else
427             fprintf (stderr, "%s: %s\n", progname, err);
428
429           return -1;
430         }
431
432       if (st2 == Success && type != None)
433         {
434           if (type != XA_STRING || format != 8)
435             {
436               sprintf (err, "unrecognized response property.");
437
438               if (error_ret)
439                 *error_ret = strdup (err);
440               else
441                 fprintf (stderr, "%s: %s\n", progname, err);
442
443               if (msg) XFree (msg);
444               return -1;
445             }
446           else if (!msg || (msg[0] != '+' && msg[0] != '-'))
447             {
448               sprintf (err, "unrecognized response message.");
449
450               if (error_ret)
451                 *error_ret = strdup (err);
452               else  
453                 fprintf (stderr, "%s: %s\n", progname, err);
454
455               if (msg) XFree (msg);
456               return -1;
457             }
458           else
459             {
460               int ret = (msg[0] == '+' ? 0 : -1);
461               sprintf (err, "%s: %s\n", progname, (char *) msg+1);
462
463               if (error_ret)
464                 *error_ret = strdup (err);
465               else if (verbose_p || ret != 0)
466                 fprintf ((ret < 0 ? stderr : stdout), "%s\n", err);
467
468               XFree (msg);
469               return ret;
470             }
471         }
472     }
473
474   return -1;  /* warning suppression: not actually reached */
475 }
476
477
478 int
479 xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
480                       char **error_ret)
481 {
482   Window w = 0;
483   int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret);
484   if (status == 0)
485     status = xscreensaver_command_response (dpy, w, verbose_p,
486                                             (command == XA_EXIT),
487                                             error_ret);
488
489   fflush (stdout);
490   fflush (stderr);
491   return (status < 0 ? status : 0);
492 }
493
494
495 void
496 server_xscreensaver_version (Display *dpy,
497                              char **version_ret,
498                              char **user_ret,
499                              char **host_ret)
500 {
501   Window window = find_screensaver_window (dpy, 0);
502
503   Atom type;
504   int format;
505   unsigned long nitems, bytesafter;
506
507   if (version_ret)
508     *version_ret = 0;
509   if (user_ret)
510     *user_ret = 0;
511   if (host_ret)
512     *host_ret = 0;
513
514   if (!window)
515     return;
516
517   if (version_ret)
518     {
519       unsigned char *v = 0;
520       XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
521                           False, XA_STRING, &type, &format, &nitems,
522                           &bytesafter, &v);
523       if (v)
524         {
525           *version_ret = strdup ((char *) v);
526           XFree (v);
527         }
528     }
529
530   if (user_ret || host_ret)
531     {
532       unsigned char *id = 0;
533       const char *user = 0;
534       const char *host = 0;
535
536       XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512,
537                           False, XA_STRING, &type, &format, &nitems,
538                           &bytesafter, &id);
539       if (id && *id)
540         {
541           const char *old_tag = " on host ";
542           const char *s = strstr ((char *) id, old_tag);
543           if (s)
544             {
545               /* found ID of the form "1234 on host xyz". */
546               user = 0;
547               host = s + strlen (old_tag);
548             }
549           else
550             {
551               char *o = 0, *p = 0, *c = 0;
552               o = strchr ((char *) id, '(');
553               if (o) p = strchr (o, '@');
554               if (p) c = strchr (p, ')');
555               if (c)
556                 {
557                   /* found ID of the form "1234 (user@host)". */
558                   user = o+1;
559                   host = p+1;
560                   *p = 0;
561                   *c = 0;
562                 }
563             }
564
565         }
566
567       if (user && *user && *user != '?')
568         *user_ret = strdup (user);
569       else
570         *user_ret = 0;
571
572       if (host && *host && *host != '?')
573         *host_ret = strdup (host);
574       else
575         *host_ret = 0;
576
577       if (id)
578         XFree (id);
579     }
580 }