http://www.jwz.org/xscreensaver/xscreensaver-5.12.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 # if defined(HAVE_SELECT)
384       /* Wait for an event, but don't wait longer than 1 sec.  Note that we
385          might do this multiple times if an event comes in, but it wasn't
386          the event we're waiting for.
387        */
388       int fd = XConnectionNumber(dpy);
389       fd_set rset;
390       struct timeval tv;
391       tv.tv_sec  = 1;
392       tv.tv_usec = 0;
393       FD_ZERO (&rset);
394       FD_SET (fd, &rset);
395       select (fd+1, &rset, 0, 0, &tv);
396 # else  /* !HAVE_SELECT */
397       sleep(1);
398 # endif /* !HAVE_SELECT */
399     }
400
401   if (!got_event)
402     {
403       sprintf (err, "no response to command.");
404       if (error_ret)
405         *error_ret = strdup (err);
406       else
407         fprintf (stderr, "%s: %s\n", progname, err);
408
409       return -1;
410     }
411   else
412     {
413       Status st2;
414       Atom type;
415       int format;
416       unsigned long nitems, bytesafter;
417       unsigned char *msg = 0;
418
419       XSync (dpy, False);
420       if (old_handler) abort();
421       old_handler = XSetErrorHandler (BadWindow_ehandler);
422       st2 = XGetWindowProperty (dpy, window,
423                                 XA_SCREENSAVER_RESPONSE,
424                                 0, 1024, True,
425                                 AnyPropertyType,
426                                 &type, &format, &nitems, &bytesafter,
427                                 &msg);
428       XSync (dpy, False);
429       XSetErrorHandler (old_handler);
430       old_handler = 0;
431
432       if (got_badwindow)
433         {
434           if (exiting_p)
435             return 0;
436
437           sprintf (err, "xscreensaver window unexpectedly deleted.");
438
439           if (error_ret)
440             *error_ret = strdup (err);
441           else
442             fprintf (stderr, "%s: %s\n", progname, err);
443
444           return -1;
445         }
446
447       if (st2 == Success && type != None)
448         {
449           if (type != XA_STRING || format != 8)
450             {
451               sprintf (err, "unrecognized response property.");
452
453               if (error_ret)
454                 *error_ret = strdup (err);
455               else
456                 fprintf (stderr, "%s: %s\n", progname, err);
457
458               if (msg) XFree (msg);
459               return -1;
460             }
461           else if (!msg || (msg[0] != '+' && msg[0] != '-'))
462             {
463               sprintf (err, "unrecognized response message.");
464
465               if (error_ret)
466                 *error_ret = strdup (err);
467               else  
468                 fprintf (stderr, "%s: %s\n", progname, err);
469
470               if (msg) XFree (msg);
471               return -1;
472             }
473           else
474             {
475               int ret = (msg[0] == '+' ? 0 : -1);
476               sprintf (err, "%s: %s\n", progname, (char *) msg+1);
477
478               if (error_ret)
479                 *error_ret = strdup (err);
480               else if (verbose_p || ret != 0)
481                 fprintf ((ret < 0 ? stderr : stdout), "%s\n", err);
482
483               XFree (msg);
484               return ret;
485             }
486         }
487     }
488
489   return -1;  /* warning suppression: not actually reached */
490 }
491
492
493 int
494 xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
495                       char **error_ret)
496 {
497   Window w = 0;
498   int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret);
499   if (status == 0)
500     status = xscreensaver_command_response (dpy, w, verbose_p,
501                                             (command == XA_EXIT),
502                                             error_ret);
503
504   fflush (stdout);
505   fflush (stderr);
506   return (status < 0 ? status : 0);
507 }
508
509
510 void
511 server_xscreensaver_version (Display *dpy,
512                              char **version_ret,
513                              char **user_ret,
514                              char **host_ret)
515 {
516   Window window = find_screensaver_window (dpy, 0);
517
518   Atom type;
519   int format;
520   unsigned long nitems, bytesafter;
521
522   if (version_ret)
523     *version_ret = 0;
524   if (user_ret)
525     *user_ret = 0;
526   if (host_ret)
527     *host_ret = 0;
528
529   if (!window)
530     return;
531
532   if (version_ret)
533     {
534       unsigned char *v = 0;
535       XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
536                           False, XA_STRING, &type, &format, &nitems,
537                           &bytesafter, &v);
538       if (v)
539         {
540           *version_ret = strdup ((char *) v);
541           XFree (v);
542         }
543     }
544
545   if (user_ret || host_ret)
546     {
547       unsigned char *id = 0;
548       const char *user = 0;
549       const char *host = 0;
550
551       XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512,
552                           False, XA_STRING, &type, &format, &nitems,
553                           &bytesafter, &id);
554       if (id && *id)
555         {
556           const char *old_tag = " on host ";
557           const char *s = strstr ((char *) id, old_tag);
558           if (s)
559             {
560               /* found ID of the form "1234 on host xyz". */
561               user = 0;
562               host = s + strlen (old_tag);
563             }
564           else
565             {
566               char *o = 0, *p = 0, *c = 0;
567               o = strchr ((char *) id, '(');
568               if (o) p = strchr (o, '@');
569               if (p) c = strchr (p, ')');
570               if (c)
571                 {
572                   /* found ID of the form "1234 (user@host)". */
573                   user = o+1;
574                   host = p+1;
575                   *p = 0;
576                   *c = 0;
577                 }
578             }
579
580         }
581
582       if (user && *user && *user != '?')
583         *user_ret = strdup (user);
584       else
585         *user_ret = 0;
586
587       if (host && *host && *host != '?')
588         *host_ret = strdup (host);
589       else
590         *host_ret = 0;
591
592       if (id)
593         XFree (id);
594     }
595 }