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