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