3c6e4d1e4a04c3114915c6be28cfa2e07508ab32
[xscreensaver] / driver / remote.c
1 /* xscreensaver-command, Copyright (c) 1991-1998
2  *  by Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <time.h>
20 #include <sys/time.h>
21 #include <sys/types.h>
22
23 #ifdef HAVE_SYS_SELECT_H
24 # include <sys/select.h>
25 #endif /* HAVE_SYS_SELECT_H */
26
27 #ifdef HAVE_UNISTD_H
28 # include <unistd.h>
29 #endif
30
31 #include <X11/Xproto.h>         /* for CARD32 */
32 #include <X11/Xlib.h>
33 #include <X11/Xatom.h>
34 #include <X11/Xutil.h>          /* for XGetClassHint() */
35 #include <X11/Xos.h>
36
37 #include "remote.h"
38
39 #ifdef _VROOT_H_
40 ERROR! you must not include vroot.h in this file
41 #endif
42
43 extern char *progname;
44 extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE;
45 extern Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_EXIT;
46 extern Atom XA_VROOT, XA_SELECT, XA_DEMO, XA_BLANK, XA_LOCK;
47
48
49 static XErrorHandler old_handler = 0;
50 static Bool got_badwindow = False;
51 static int
52 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
53 {
54   if (error->error_code == BadWindow)
55     {
56       got_badwindow = True;
57       return 0;
58     }
59   else
60     {
61       fprintf (stderr, "%s: ", progname);
62       if (!old_handler) abort();
63       return (*old_handler) (dpy, error);
64     }
65 }
66
67
68
69 static Window
70 find_screensaver_window (Display *dpy, char **version)
71 {
72   int i;
73   Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
74   Window root2, parent, *kids;
75   unsigned int nkids;
76
77   if (version) *version = 0;
78
79   if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
80     abort ();
81   if (root != root2)
82     abort ();
83   if (parent)
84     abort ();
85   if (! (kids && nkids))
86     return 0;
87   for (i = 0; i < nkids; i++)
88     {
89       Atom type;
90       int format;
91       unsigned long nitems, bytesafter;
92       char *v;
93       int status;
94
95       /* We're walking the list of root-level windows and trying to find
96          the one that has a particular property on it.  We need to trap
97          BadWindows errors while doing this, because it's possible that
98          some random window might get deleted in the meantime.  (That
99          window won't have been the one we're looking for.)
100        */
101       XSync (dpy, False);
102       if (old_handler) abort();
103       got_badwindow = False;
104       old_handler = XSetErrorHandler (BadWindow_ehandler);
105       status = XGetWindowProperty (dpy, kids[i],
106                                    XA_SCREENSAVER_VERSION,
107                                    0, 200, False, XA_STRING,
108                                    &type, &format, &nitems, &bytesafter,
109                                    (unsigned char **) &v);
110       XSync (dpy, False);
111       XSetErrorHandler (old_handler);
112       old_handler = 0;
113
114       if (got_badwindow)
115         {
116           status = BadWindow;
117           got_badwindow = False;
118         }
119
120       if (status == Success && type != None)
121         {
122           Window ret = kids[i];
123           if (version)
124             *version = v;
125           XFree (kids);
126           return ret;
127         }
128     }
129
130   if (kids) XFree (kids);
131   return 0;
132 }
133
134
135 static int
136 send_xscreensaver_command (Display *dpy, Atom command, long arg,
137                            Window *window_ret, char **error_ret)
138 {
139   int status = -1;
140   char *v = 0;
141   Window window = find_screensaver_window (dpy, &v);
142   XWindowAttributes xgwa;
143   char err[2048];
144
145   if (window_ret)
146     *window_ret = window;
147
148   if (!window)
149     {
150       sprintf (err, "no screensaver is running on display %s",
151                DisplayString (dpy));
152
153       if (error_ret)
154         {
155           *error_ret = strdup (err);
156           status = -1;
157           goto DONE;
158         }
159
160       if (command == XA_EXIT)
161         {
162           /* Don't print an error if xscreensaver is already dead. */
163           status = 1;
164           goto DONE;
165         }
166
167       fprintf (stderr, "%s: %s\n", progname, err);
168       status = -1;
169       goto DONE;
170     }
171
172   /* Select for property change events, so that we can read the response. */
173   XGetWindowAttributes (dpy, window, &xgwa);
174   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
175
176   if (command == XA_SCREENSAVER_STATUS ||
177       command == XA_SCREENSAVER_VERSION)
178     {
179       XClassHint hint;
180       memset (&hint, 0, sizeof(hint));
181       if (!v || !*v)
182         {
183           sprintf (err, "version property not set on window 0x%x?",
184                    (unsigned int) window);
185           if (error_ret)
186             *error_ret = strdup (err);
187           else
188             fprintf (stderr, "%s: %s\n", progname, err);
189
190           status = -1;
191           goto DONE;
192         }
193
194       XGetClassHint(dpy, window, &hint);
195       if (!hint.res_class)
196         {
197           sprintf (err, "class hints not set on window 0x%x?",
198                    (unsigned int) window);
199           if (error_ret)
200             *error_ret = strdup (err);
201           else
202             fprintf (stderr, "%s: %s\n", progname, err);
203
204           if (v) free (v);
205           status = -1;
206           goto DONE;
207         }
208
209       fprintf (stdout, "%s %s", hint.res_class, v);
210
211       if (command != XA_SCREENSAVER_STATUS)
212         {
213           fprintf (stdout, "\n");
214         }
215       else
216         {
217           Atom type;
218           int format;
219           unsigned long nitems, bytesafter;
220           CARD32 *data = 0;
221
222           if (XGetWindowProperty (dpy,
223                                   RootWindow (dpy, 0),
224                                   XA_SCREENSAVER_STATUS,
225                                   0, 999, False, XA_INTEGER,
226                                   &type, &format, &nitems, &bytesafter,
227                                   (unsigned char **) &data)
228               == Success
229               && type
230               && data)
231             {
232               Atom blanked;
233               time_t tt;
234               char *s;
235
236               if (type != XA_INTEGER || nitems < 3)
237                 {
238                 STATUS_LOSE:
239                   if (data) free (data);
240                   fprintf (stdout, "\n");
241                   fflush (stdout);
242                   fprintf (stderr, "bad status format on root window.\n");
243                   status = -1;
244                   goto DONE;
245                 }
246                   
247               blanked = (Atom) data[0];
248               tt = (time_t) data[1];
249
250               if (tt <= (time_t) 666000000L) /* early 1991 */
251                 goto STATUS_LOSE;
252
253               if (blanked == XA_BLANK)
254                 fputs (": screen blanked since ", stdout);
255               else if (blanked == XA_LOCK)
256                 fputs (": screen locked since ", stdout);
257               else if (blanked == 0)
258                 /* suggestions for a better way to phrase this are welcome. */
259                 fputs (": screen non-blanked since ", stdout);
260               else
261                 /* `blanked' has an unknown value - fail. */
262                 goto STATUS_LOSE;
263
264               s = ctime(&tt);
265               if (s[strlen(s)-1] == '\n')
266                 s[strlen(s)-1] = 0;
267               fputs (s, stdout);
268
269               {
270                 int nhacks = nitems - 2;
271                 Bool any = False;
272                 int i;
273                 for (i = 0; i < nhacks; i++)
274                   if (data[i + 2] > 0)
275                     {
276                       any = True;
277                       break;
278                     }
279
280                 if (any && nhacks == 1)
281                   fprintf (stdout, " (hack #%d)\n", (int) data[2]);
282                 else if (any)
283                   {
284                     fprintf (stdout, " (hacks: ");
285                     for (i = 0; i < nhacks; i++)
286                       {
287                         fprintf (stdout, "#%d", (int) data[2 + i]);
288                         if (i != nhacks-1)
289                           fputs (", ", stdout);
290                       }
291                     fputs (")\n", stdout);
292                   }
293                 else
294                   fputs ("\n", stdout);
295               }
296
297               if (data) free (data);
298             }
299           else
300             {
301               if (data) free (data);
302               fprintf (stdout, "\n");
303               fflush (stdout);
304               fprintf (stderr, "no saver status on root window.\n");
305               status = -1;
306               goto DONE;
307             }
308         }
309
310       /* No need to read a response for these commands. */
311       status = 1;
312       goto DONE;
313     }
314   else
315     {
316       XEvent event;
317       long arg1 = arg;
318       long arg2 = 0;
319
320       if (arg < 0)
321         abort();
322       else if (arg == 0 && command == XA_SELECT)
323         abort();
324       else if (arg != 0 && command == XA_DEMO)
325         {
326           arg1 = 300;   /* version number of the XA_DEMO protocol, */
327           arg2 = arg;   /* since it didn't use to take an argument. */
328         }
329
330       event.xany.type = ClientMessage;
331       event.xclient.display = dpy;
332       event.xclient.window = window;
333       event.xclient.message_type = XA_SCREENSAVER;
334       event.xclient.format = 32;
335       memset (&event.xclient.data, 0, sizeof(event.xclient.data));
336       event.xclient.data.l[0] = (long) command;
337       event.xclient.data.l[1] = arg1;
338       event.xclient.data.l[2] = arg2;
339       if (! XSendEvent (dpy, window, False, 0L, &event))
340         {
341           sprintf (err, "XSendEvent(dpy, 0x%x ...) failed.\n",
342                    (unsigned int) window);
343           if (error_ret)
344             *error_ret = strdup (err);
345           else
346             fprintf (stderr, "%s: %s\n", progname, err);
347           status = -1;
348           goto DONE;
349         }
350     }
351
352   status = 0;
353
354  DONE:
355   if (v) free (v);
356   XSync (dpy, 0);
357   return status;
358 }
359
360
361 static int
362 xscreensaver_command_response (Display *dpy, Window window,
363                                Bool verbose_p, Bool exiting_p,
364                                char **error_ret)
365 {
366   int fd = ConnectionNumber (dpy);
367   int timeout = 10;
368   int status;
369   fd_set fds;
370   struct timeval tv;
371   char err[2048];
372
373   while (1)
374     {
375       FD_ZERO(&fds);
376       FD_SET(fd, &fds);
377       memset(&tv, 0, sizeof(tv));
378       tv.tv_sec = timeout;
379       status = select (fd+1, &fds, 0, &fds, &tv);
380
381       if (status < 0)
382         {
383           char buf[1024];
384           if (error_ret)
385             {
386               sprintf (buf, "error waiting for reply");
387               *error_ret = strdup (buf);
388             }
389           else
390             {
391               sprintf (buf, "%s: error waiting for reply", progname);
392               perror (buf);
393             }
394           return status;
395         }
396       else if (status == 0)
397         {
398           sprintf (err, "no response to command.");
399           if (error_ret)
400             *error_ret = strdup (err);
401           else
402             fprintf (stderr, "%s: %s\n", progname, err);
403           return -1;
404         }
405       else
406         {
407           XEvent event;
408           XNextEvent (dpy, &event);
409           if (event.xany.type == PropertyNotify &&
410               event.xproperty.state == PropertyNewValue &&
411               event.xproperty.atom == XA_SCREENSAVER_RESPONSE)
412             {
413               Status st2;
414               Atom type;
415               int format;
416               unsigned long nitems, bytesafter;
417               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                                         (unsigned char **) &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, 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     }
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       char *v = 0;
535       XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
536                           False, XA_STRING, &type, &format, &nitems,
537                           &bytesafter, (unsigned char **) &v);
538       if (v)
539         {
540           *version_ret = strdup (v);
541           XFree (v);
542         }
543     }
544
545   if (user_ret || host_ret)
546     {
547       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, (unsigned char **) &id);
554       if (id && *id)
555         {
556           const char *old_tag = " on host ";
557           const char *s = strstr (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 (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 }