592d5d03ec75206c7262865e29f678b601eb8b86
[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 <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       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                                    (unsigned char **) &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           if (version)
122             *version = v;
123           return kids[i];
124         }
125     }
126   return 0;
127 }
128
129
130 static int
131 send_xscreensaver_command (Display *dpy, Atom command, long arg,
132                            Window *window_ret, char **error_ret)
133 {
134   char *v = 0;
135   Window window = find_screensaver_window (dpy, &v);
136   XWindowAttributes xgwa;
137   char err[2048];
138
139   if (window_ret)
140     *window_ret = window;
141
142   if (!window)
143     {
144       sprintf (err, "no screensaver is running on display %s",
145                DisplayString (dpy));
146
147       if (error_ret)
148         {
149           *error_ret = strdup (err);
150           return -1;
151         }
152
153       if (command == XA_EXIT)
154         /* Don't print an error if xscreensaver is already dead. */
155         return 1;
156
157       fprintf (stderr, "%s: %s\n", progname, err);
158       return -1;
159     }
160
161   /* Select for property change events, so that we can read the response. */
162   XGetWindowAttributes (dpy, window, &xgwa);
163   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
164
165   if (command == XA_SCREENSAVER_STATUS ||
166       command == XA_SCREENSAVER_VERSION)
167     {
168       XClassHint hint;
169       memset (&hint, 0, sizeof(hint));
170       if (!v || !*v)
171         {
172           sprintf (err, "version property not set on window 0x%x?",
173                    (unsigned int) window);
174           if (error_ret)
175             *error_ret = strdup (err);
176           else
177             fprintf (stderr, "%s: %s\n", progname, err);
178           return -1;
179         }
180
181       XGetClassHint(dpy, window, &hint);
182       if (!hint.res_class)
183         {
184           sprintf (err, "class hints not set on window 0x%x?",
185                    (unsigned int) window);
186           if (error_ret)
187             *error_ret = strdup (err);
188           else
189             fprintf (stderr, "%s: %s\n", progname, err);
190           return -1;
191         }
192
193       fprintf (stdout, "%s %s", hint.res_class, v);
194
195       if (command != XA_SCREENSAVER_STATUS)
196         {
197           fprintf (stdout, "\n");
198         }
199       else
200         {
201           Atom type;
202           int format;
203           unsigned long nitems, bytesafter;
204           CARD32 *data = 0;
205
206           if (XGetWindowProperty (dpy,
207                                   RootWindow (dpy, 0),
208                                   XA_SCREENSAVER_STATUS,
209                                   0, 999, False, XA_INTEGER,
210                                   &type, &format, &nitems, &bytesafter,
211                                   (unsigned char **) &data)
212               == Success
213               && type
214               && data)
215             {
216               Atom blanked;
217               time_t tt;
218               char *s;
219
220               if (type != XA_INTEGER || nitems < 3)
221                 {
222                 STATUS_LOSE:
223                   if (data) free (data);
224                   fprintf (stdout, "\n");
225                   fflush (stdout);
226                   fprintf (stderr, "bad status format on root window.\n");
227                   return -1;
228                 }
229                   
230               blanked = (Atom) data[0];
231               tt = (time_t) data[1];
232
233               if (tt <= (time_t) 666000000L) /* early 1991 */
234                 goto STATUS_LOSE;
235
236               if (blanked == XA_BLANK)
237                 fputs (": screen blanked since ", stdout);
238               else if (blanked == XA_LOCK)
239                 fputs (": screen locked since ", stdout);
240               else if (blanked == 0)
241                 /* suggestions for a better way to phrase this are welcome. */
242                 fputs (": screen non-blanked since ", stdout);
243               else
244                 /* `blanked' has an unknown value - fail. */
245                 goto STATUS_LOSE;
246
247               s = ctime(&tt);
248               if (s[strlen(s)-1] == '\n')
249                 s[strlen(s)-1] = 0;
250               fputs (s, stdout);
251
252               {
253                 int nhacks = nitems - 2;
254                 Bool any = False;
255                 int i;
256                 for (i = 0; i < nhacks; i++)
257                   if (data[i + 2] > 0)
258                     {
259                       any = True;
260                       break;
261                     }
262
263                 if (any && nhacks == 1)
264                   fprintf (stdout, " (hack #%d)\n", data[2]);
265                 else if (any)
266                   {
267                     fprintf (stdout, " (hacks: ");
268                     for (i = 0; i < nhacks; i++)
269                       {
270                         fprintf (stdout, "#%d", data[2 + i]);
271                         if (i != nhacks-1)
272                           fputs (", ", stdout);
273                       }
274                     fputs (")\n", stdout);
275                   }
276                 else
277                   fputs ("\n", stdout);
278               }
279
280               if (data) free (data);
281             }
282           else
283             {
284               if (data) free (data);
285               fprintf (stdout, "\n");
286               fflush (stdout);
287               fprintf (stderr, "no saver status on root window.\n");
288               return -1;
289             }
290         }
291
292       /* No need to read a response for these commands. */
293       return 1;
294     }
295   else
296     {
297       XEvent event;
298       long arg1 = arg;
299       long arg2 = 0;
300
301       if (arg < 0)
302         abort();
303       else if (arg == 0 && command == XA_SELECT)
304         abort();
305       else if (arg != 0 && command == XA_DEMO)
306         {
307           arg1 = 300;   /* version number of the XA_DEMO protocol, */
308           arg2 = arg;   /* since it didn't use to take an argument. */
309         }
310
311       event.xany.type = ClientMessage;
312       event.xclient.display = dpy;
313       event.xclient.window = window;
314       event.xclient.message_type = XA_SCREENSAVER;
315       event.xclient.format = 32;
316       memset (&event.xclient.data, 0, sizeof(event.xclient.data));
317       event.xclient.data.l[0] = (long) command;
318       event.xclient.data.l[1] = arg1;
319       event.xclient.data.l[2] = arg2;
320       if (! XSendEvent (dpy, window, False, 0L, &event))
321         {
322           sprintf (err, "XSendEvent(dpy, 0x%x ...) failed.\n",
323                    (unsigned int) window);
324           if (error_ret)
325             *error_ret = strdup (err);
326           else
327             fprintf (stderr, "%s: %s\n", progname, err);
328           return -1;
329         }
330     }
331   XSync (dpy, 0);
332   return 0;
333 }
334
335
336 static int
337 xscreensaver_command_response (Display *dpy, Window window,
338                                Bool verbose_p, Bool exiting_p,
339                                char **error_ret)
340 {
341   int fd = ConnectionNumber (dpy);
342   int timeout = 10;
343   int status;
344   fd_set fds;
345   struct timeval tv;
346   char err[2048];
347
348   while (1)
349     {
350       FD_ZERO(&fds);
351       FD_SET(fd, &fds);
352       memset(&tv, 0, sizeof(tv));
353       tv.tv_sec = timeout;
354       status = select (fd+1, &fds, 0, &fds, &tv);
355
356       if (status < 0)
357         {
358           char buf[1024];
359           if (error_ret)
360             {
361               sprintf (buf, "error waiting for reply");
362               *error_ret = strdup (buf);
363             }
364           else
365             {
366               sprintf (buf, "%s: error waiting for reply", progname);
367               perror (buf);
368             }
369           return status;
370         }
371       else if (status == 0)
372         {
373           sprintf (err, "no response to command.");
374           if (error_ret)
375             *error_ret = strdup (err);
376           else
377             fprintf (stderr, "%s: %s\n", progname, err);
378           return -1;
379         }
380       else
381         {
382           XEvent event;
383           XNextEvent (dpy, &event);
384           if (event.xany.type == PropertyNotify &&
385               event.xproperty.state == PropertyNewValue &&
386               event.xproperty.atom == XA_SCREENSAVER_RESPONSE)
387             {
388               Status st2;
389               Atom type;
390               int format;
391               unsigned long nitems, bytesafter;
392               char *msg = 0;
393
394               XSync (dpy, False);
395               if (old_handler) abort();
396               old_handler = XSetErrorHandler (BadWindow_ehandler);
397               st2 = XGetWindowProperty (dpy, window,
398                                         XA_SCREENSAVER_RESPONSE,
399                                         0, 1024, True,
400                                         AnyPropertyType,
401                                         &type, &format, &nitems, &bytesafter,
402                                         (unsigned char **) &msg);
403               XSync (dpy, False);
404               XSetErrorHandler (old_handler);
405               old_handler = 0;
406
407               if (got_badwindow)
408                 {
409                   if (exiting_p)
410                     return 0;
411
412                   sprintf (err, "xscreensaver window unexpectedly deleted.");
413
414                   if (error_ret)
415                     *error_ret = strdup (err);
416                   else
417                     fprintf (stderr, "%s: %s\n", progname, err);
418
419                   return -1;
420                 }
421
422               if (st2 == Success && type != None)
423                 {
424                   if (type != XA_STRING || format != 8)
425                     {
426                       sprintf (err, "unrecognized response property.");
427
428                       if (error_ret)
429                         *error_ret = strdup (err);
430                       else
431                         fprintf (stderr, "%s: %s\n", progname, err);
432
433                       if (msg) XFree (msg);
434                       return -1;
435                     }
436                   else if (!msg || (msg[0] != '+' && msg[0] != '-'))
437                     {
438                       sprintf (err, "unrecognized response message.");
439
440                       if (error_ret)
441                         *error_ret = strdup (err);
442                       else  
443                         fprintf (stderr, "%s: %s\n", progname, err);
444
445                       if (msg) XFree (msg);
446                       return -1;
447                     }
448                   else
449                     {
450                       int ret = (msg[0] == '+' ? 0 : -1);
451                       sprintf (err, "%s: %s\n", progname, msg+1);
452
453                       if (error_ret)
454                         *error_ret = strdup (err);
455                       else if (verbose_p || ret != 0)
456                         fprintf ((ret < 0 ? stderr : stdout), "%s\n", err);
457
458                       XFree (msg);
459                       return ret;
460                     }
461                 }
462             }
463         }
464     }
465 }
466
467
468 int
469 xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
470                       char **error_ret)
471 {
472   Window w = 0;
473   int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret);
474   if (status == 0)
475     status = xscreensaver_command_response (dpy, w, verbose_p,
476                                             (command == XA_EXIT),
477                                             error_ret);
478
479   fflush (stdout);
480   fflush (stderr);
481   return (status < 0 ? status : 0);
482 }
483
484
485 void
486 server_xscreensaver_version (Display *dpy,
487                              char **version_ret,
488                              char **user_ret,
489                              char **host_ret)
490 {
491   Window window = find_screensaver_window (dpy, 0);
492
493   Atom type;
494   int format;
495   unsigned long nitems, bytesafter;
496
497   if (version_ret)
498     *version_ret = 0;
499   if (user_ret)
500     *user_ret = 0;
501   if (host_ret)
502     *host_ret = 0;
503
504   if (!window)
505     return;
506
507   if (version_ret)
508     {
509       char *v = 0;
510       XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
511                           False, XA_STRING, &type, &format, &nitems,
512                           &bytesafter, (unsigned char **) &v);
513       if (v)
514         {
515           *version_ret = strdup (v);
516           XFree (v);
517         }
518     }
519
520   if (user_ret || host_ret)
521     {
522       char *id = 0;
523       const char *user = 0;
524       const char *host = 0;
525
526       XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512,
527                           False, XA_STRING, &type, &format, &nitems,
528                           &bytesafter, (unsigned char **) &id);
529       if (id && *id)
530         {
531           const char *old_tag = " on host ";
532           const char *s = strstr (id, old_tag);
533           if (s)
534             {
535               /* found ID of the form "1234 on host xyz". */
536               user = 0;
537               host = s + strlen (old_tag);
538             }
539           else
540             {
541               char *o = 0, *p = 0, *c = 0;
542               o = strchr (id, '(');
543               if (o) p = strchr (o, '@');
544               if (p) c = strchr (p, ')');
545               if (c)
546                 {
547                   /* found ID of the form "1234 (user@host)". */
548                   user = o+1;
549                   host = p+1;
550                   *p = 0;
551                   *c = 0;
552                 }
553             }
554
555         }
556
557       if (user && *user && *user != '?')
558         *user_ret = strdup (user);
559       else
560         *user_ret = 0;
561
562       if (host && *host && *host != '?')
563         *host_ret = strdup (host);
564       else
565         *host_ret = 0;
566
567       if (id)
568         XFree (id);
569     }
570 }