507da6b73cb9878e8756b37e729c859133b380a5
[xscreensaver] / driver / remote.c
1 /* xscreensaver-command, Copyright (c) 1991-2005 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 int
361 xscreensaver_command_response (Display *dpy, Window window,
362                                Bool verbose_p, Bool exiting_p,
363                                char **error_ret)
364 {
365   int fd = ConnectionNumber (dpy);
366   int timeout = 10;
367   int status;
368   fd_set fds;
369   struct timeval tv;
370   char err[2048];
371
372   while (1)
373     {
374       FD_ZERO(&fds);
375       FD_SET(fd, &fds);
376       memset(&tv, 0, sizeof(tv));
377       tv.tv_sec = timeout;
378       status = select (fd+1, &fds, 0, &fds, &tv);
379
380       if (status < 0)
381         {
382           char buf[1024];
383           if (error_ret)
384             {
385               sprintf (buf, "error waiting for reply");
386               *error_ret = strdup (buf);
387             }
388           else
389             {
390               sprintf (buf, "%s: error waiting for reply", progname);
391               perror (buf);
392             }
393           return status;
394         }
395       else if (status == 0)
396         {
397           sprintf (err, "no response to command.");
398           if (error_ret)
399             *error_ret = strdup (err);
400           else
401             fprintf (stderr, "%s: %s\n", progname, err);
402           return -1;
403         }
404       else
405         {
406           XEvent event;
407           XNextEvent (dpy, &event);
408           if (event.xany.type == PropertyNotify &&
409               event.xproperty.state == PropertyNewValue &&
410               event.xproperty.atom == XA_SCREENSAVER_RESPONSE)
411             {
412               Status st2;
413               Atom type;
414               int format;
415               unsigned long nitems, bytesafter;
416               unsigned char *msg = 0;
417
418               XSync (dpy, False);
419               if (old_handler) abort();
420               old_handler = XSetErrorHandler (BadWindow_ehandler);
421               st2 = XGetWindowProperty (dpy, window,
422                                         XA_SCREENSAVER_RESPONSE,
423                                         0, 1024, True,
424                                         AnyPropertyType,
425                                         &type, &format, &nitems, &bytesafter,
426                                         &msg);
427               XSync (dpy, False);
428               XSetErrorHandler (old_handler);
429               old_handler = 0;
430
431               if (got_badwindow)
432                 {
433                   if (exiting_p)
434                     return 0;
435
436                   sprintf (err, "xscreensaver window unexpectedly deleted.");
437
438                   if (error_ret)
439                     *error_ret = strdup (err);
440                   else
441                     fprintf (stderr, "%s: %s\n", progname, err);
442
443                   return -1;
444                 }
445
446               if (st2 == Success && type != None)
447                 {
448                   if (type != XA_STRING || format != 8)
449                     {
450                       sprintf (err, "unrecognized response property.");
451
452                       if (error_ret)
453                         *error_ret = strdup (err);
454                       else
455                         fprintf (stderr, "%s: %s\n", progname, err);
456
457                       if (msg) XFree (msg);
458                       return -1;
459                     }
460                   else if (!msg || (msg[0] != '+' && msg[0] != '-'))
461                     {
462                       sprintf (err, "unrecognized response message.");
463
464                       if (error_ret)
465                         *error_ret = strdup (err);
466                       else  
467                         fprintf (stderr, "%s: %s\n", progname, err);
468
469                       if (msg) XFree (msg);
470                       return -1;
471                     }
472                   else
473                     {
474                       int ret = (msg[0] == '+' ? 0 : -1);
475                       sprintf (err, "%s: %s\n", progname, (char *) msg+1);
476
477                       if (error_ret)
478                         *error_ret = strdup (err);
479                       else if (verbose_p || ret != 0)
480                         fprintf ((ret < 0 ? stderr : stdout), "%s\n", err);
481
482                       XFree (msg);
483                       return ret;
484                     }
485                 }
486             }
487         }
488     }
489 }
490
491
492 int
493 xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
494                       char **error_ret)
495 {
496   Window w = 0;
497   int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret);
498   if (status == 0)
499     status = xscreensaver_command_response (dpy, w, verbose_p,
500                                             (command == XA_EXIT),
501                                             error_ret);
502
503   fflush (stdout);
504   fflush (stderr);
505   return (status < 0 ? status : 0);
506 }
507
508
509 void
510 server_xscreensaver_version (Display *dpy,
511                              char **version_ret,
512                              char **user_ret,
513                              char **host_ret)
514 {
515   Window window = find_screensaver_window (dpy, 0);
516
517   Atom type;
518   int format;
519   unsigned long nitems, bytesafter;
520
521   if (version_ret)
522     *version_ret = 0;
523   if (user_ret)
524     *user_ret = 0;
525   if (host_ret)
526     *host_ret = 0;
527
528   if (!window)
529     return;
530
531   if (version_ret)
532     {
533       unsigned char *v = 0;
534       XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
535                           False, XA_STRING, &type, &format, &nitems,
536                           &bytesafter, &v);
537       if (v)
538         {
539           *version_ret = strdup ((char *) v);
540           XFree (v);
541         }
542     }
543
544   if (user_ret || host_ret)
545     {
546       unsigned char *id = 0;
547       const char *user = 0;
548       const char *host = 0;
549
550       XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512,
551                           False, XA_STRING, &type, &format, &nitems,
552                           &bytesafter, &id);
553       if (id && *id)
554         {
555           const char *old_tag = " on host ";
556           const char *s = strstr ((char *) id, old_tag);
557           if (s)
558             {
559               /* found ID of the form "1234 on host xyz". */
560               user = 0;
561               host = s + strlen (old_tag);
562             }
563           else
564             {
565               char *o = 0, *p = 0, *c = 0;
566               o = strchr ((char *) id, '(');
567               if (o) p = strchr (o, '@');
568               if (p) c = strchr (p, ')');
569               if (c)
570                 {
571                   /* found ID of the form "1234 (user@host)". */
572                   user = o+1;
573                   host = p+1;
574                   *p = 0;
575                   *c = 0;
576                 }
577             }
578
579         }
580
581       if (user && *user && *user != '?')
582         *user_ret = strdup (user);
583       else
584         *user_ret = 0;
585
586       if (host && *host && *host != '?')
587         *host_ret = strdup (host);
588       else
589         *host_ret = 0;
590
591       if (id)
592         XFree (id);
593     }
594 }