31d4580cef1886604958892e6142226310eebcc1
[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 #include <sys/select.h>
22
23 #ifdef HAVE_UNISTD_H
24 # include <unistd.h>
25 #endif
26
27 #include <X11/Xproto.h>         /* for CARD32 */
28 #include <X11/Xlib.h>
29 #include <X11/Xatom.h>
30 #include <X11/Xutil.h>          /* for XGetClassHint() */
31 #include <X11/Xos.h>
32
33 #include "remote.h"
34
35 #ifdef _VROOT_H_
36 ERROR! you must not include vroot.h in this file
37 #endif
38
39 extern char *progname;
40 extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE;
41 extern Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_TIME;
42 extern Atom XA_VROOT, XA_SELECT, XA_DEMO;
43
44
45 static XErrorHandler old_handler = 0;
46 static Bool got_badwindow = False;
47 static int
48 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
49 {
50   /* When we notice a window being created, we spawn a timer that waits
51      30 seconds or so, and then selects events on that window.  This error
52      handler is used so that we can cope with the fact that the window
53      may have been destroyed <30 seconds after it was created.
54    */
55   if (error->error_code == BadWindow)
56     {
57       got_badwindow = True;
58       return 0;
59     }
60   else
61     {
62       fprintf (stderr, "%s: ", progname);
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     abort ();
87   for (i = 0; i < nkids; i++)
88     {
89       Atom type;
90       int format;
91       unsigned long nitems, bytesafter;
92       char *v;
93
94       if (XGetWindowProperty (dpy, kids[i],
95                               XA_SCREENSAVER_VERSION,
96                               0, 200, False, XA_STRING,
97                               &type, &format, &nitems, &bytesafter,
98                               (unsigned char **) &v)
99           == Success
100           && type != None)
101         {
102           if (version)
103             *version = v;
104           return kids[i];
105         }
106     }
107   fprintf (stderr, "%s: no screensaver is running on display %s\n", progname,
108            DisplayString (dpy));
109   return 0;
110 }
111
112
113 static int
114 send_xscreensaver_command (Display *dpy, Atom command, long arg,
115                            Window *window_ret)
116 {
117   char *v = 0;
118   Window window = find_screensaver_window (dpy, &v);
119   XWindowAttributes xgwa;
120
121   if (window_ret)
122     *window_ret = window;
123
124   if (!window)
125     return -1;
126
127   /* Select for property change events, so that we can read the response. */
128   XGetWindowAttributes (dpy, window, &xgwa);
129   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
130
131   if (command == XA_SCREENSAVER_TIME ||
132       command == XA_SCREENSAVER_VERSION)
133     {
134       XClassHint hint;
135       memset (&hint, 0, sizeof(hint));
136       if (!v || !*v)
137         {
138           fprintf (stderr, "%s: version property not set on window 0x%x?\n",
139                    progname, (unsigned int) window);
140           return -1;
141         }
142
143       XGetClassHint(dpy, window, &hint);
144       if (!hint.res_class)
145         {
146           fprintf (stderr, "%s: class hints not set on window 0x%x?\n",
147                    progname, (unsigned int) window);
148           return -1;
149         }
150
151       fprintf (stdout, "%s %s", hint.res_class, v);
152
153       if (command != XA_SCREENSAVER_TIME)
154         {
155           fprintf (stdout, "\n");
156         }
157       else
158         {
159           Atom type;
160           int format;
161           unsigned long nitems, bytesafter;
162           unsigned char *data = 0;
163           Bool active_p = False;
164
165           if (XGetWindowProperty (dpy, window, XA_VROOT,
166                                   0, 0, False, XA_WINDOW,
167                                   &type, &format, &nitems, &bytesafter,
168                                   &data)
169               == Success
170               && type != None)
171             active_p = True;
172
173           if (data) free (data);
174           data = 0;
175
176           if (XGetWindowProperty (dpy, window,
177                                   XA_SCREENSAVER_TIME,
178                                   0, 1, False, XA_INTEGER,
179                                   &type, &format, &nitems, &bytesafter,
180                                   &data)
181               == Success
182               && type == XA_INTEGER
183               && data)
184             {
185               CARD32 time32 = *((CARD32 *)data);
186               time_t tt = (time_t) time32;
187
188               if (active_p)
189                 fprintf (stdout, ": screen blanked since ");
190               else
191                 /* suggestions for a better way to phrase this are welcome. */
192                 fprintf (stdout, ": screen non-blanked since ");
193               fprintf (stdout, "%s", ctime(&tt));
194               if (data) free (data);
195             }
196           else
197             {
198               if (data) free (data);
199               fprintf (stdout, "\n");
200               fflush (stdout);
201               fprintf (stderr, "%s: no time on window 0x%x (%s %s).\n",
202                        progname, (unsigned int) window,
203                        hint.res_class, (v ? v : "???"));
204               return -1;
205             }
206         }
207
208       /* No need to read a response for these commands. */
209       return 1;
210     }
211   else
212     {
213       XEvent event;
214       long arg1 = arg;
215       long arg2 = 0;
216
217       if (arg < 0)
218         abort();
219       else if (arg == 0 && command == XA_SELECT)
220         abort();
221       else if (arg != 0 && command == XA_DEMO)
222         {
223           arg1 = 300;   /* version number of the XA_DEMO protocol, */
224           arg2 = arg;   /* since it didn't use to take an argument. */
225         }
226
227       event.xany.type = ClientMessage;
228       event.xclient.display = dpy;
229       event.xclient.window = window;
230       event.xclient.message_type = XA_SCREENSAVER;
231       event.xclient.format = 32;
232       memset (&event.xclient.data, 0, sizeof(event.xclient.data));
233       event.xclient.data.l[0] = (long) command;
234       event.xclient.data.l[1] = arg1;
235       event.xclient.data.l[2] = arg2;
236       if (! XSendEvent (dpy, window, False, 0L, &event))
237         {
238           fprintf (stderr, "%s: XSendEvent(dpy, 0x%x ...) failed.\n",
239                    progname, (unsigned int) window);
240           return -1;
241         }
242     }
243   XSync (dpy, 0);
244   return 0;
245 }
246
247
248 static int
249 xscreensaver_command_response (Display *dpy, Window window, Bool verbose_p)
250 {
251   int fd = ConnectionNumber (dpy);
252   int timeout = 10;
253   int status;
254   fd_set fds;
255   struct timeval tv;
256
257   while (1)
258     {
259       FD_ZERO(&fds);
260       FD_SET(fd, &fds);
261       memset(&tv, 0, sizeof(tv));
262       tv.tv_sec = timeout;
263       status = select (fd+1, &fds, 0, &fds, &tv);
264
265       if (status < 0)
266         {
267           char buf[1024];
268           sprintf (buf, "%s: waiting for reply", progname);
269           perror (buf);
270           return status;
271         }
272       else if (status == 0)
273         {
274           fprintf (stderr, "%s: no response to command.\n", progname);
275           return -1;
276         }
277       else
278         {
279           XEvent event;
280           XNextEvent (dpy, &event);
281           if (event.xany.type == PropertyNotify &&
282               event.xproperty.state == PropertyNewValue &&
283               event.xproperty.atom == XA_SCREENSAVER_RESPONSE)
284             {
285               Status st2;
286               Atom type;
287               int format;
288               unsigned long nitems, bytesafter;
289               char *msg = 0;
290
291               old_handler = XSetErrorHandler (BadWindow_ehandler);
292               XSync (dpy, False);
293
294               st2 = XGetWindowProperty (dpy, window,
295                                         XA_SCREENSAVER_RESPONSE,
296                                         0, 1024, True,
297                                         AnyPropertyType,
298                                         &type, &format, &nitems, &bytesafter,
299                                         (unsigned char **) &msg);
300
301               if (got_badwindow)
302                 {
303                   fprintf (stdout,
304                            "%s: xscreensaver window has been deleted.\n",
305                            progname);
306                   return 0;
307                 }
308
309               if (st2 == Success && type != None)
310                 {
311                   if (type != XA_STRING || format != 8)
312                     {
313                       fprintf (stderr,
314                                "%s: unrecognized response property.\n",
315                                progname);
316                       if (msg) XFree (msg);
317                       return -1;
318                     }
319                   else if (!msg || (msg[0] != '+' && msg[0] != '-'))
320                     {
321                       fprintf (stderr,
322                                "%s: unrecognized response message.\n",
323                                progname);
324                       if (msg) XFree (msg);
325                       return -1;
326                     }
327                   else
328                     {
329                       int ret = (msg[0] == '+' ? 0 : -1);
330                       if (verbose_p || ret != 0)
331                         fprintf ((ret < 0 ? stderr : stdout),
332                                  "%s: %s\n",
333                                  progname,
334                                  msg+1);
335                       XFree (msg);
336                       return ret;
337                     }
338                 }
339             }
340         }
341     }
342 }
343
344
345 int
346 xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p)
347 {
348   Window w = 0;
349   int status = send_xscreensaver_command (dpy, command, arg, &w);
350   if (status == 0)
351     status = xscreensaver_command_response (dpy, w, verbose_p);
352   fflush (stdout);
353   fflush (stderr);
354   return status;
355 }
356
357
358 void
359 server_xscreensaver_version (Display *dpy,
360                              char **version_ret,
361                              char **user_ret,
362                              char **host_ret)
363 {
364   Window window = find_screensaver_window (dpy, 0);
365
366   Atom type;
367   int format;
368   unsigned long nitems, bytesafter;
369
370   if (version_ret)
371     *version_ret = 0;
372   if (host_ret)
373     *host_ret = 0;
374
375   if (!window)
376     return;
377
378   if (version_ret)
379     {
380       char *v = 0;
381       XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
382                           False, XA_STRING, &type, &format, &nitems,
383                           &bytesafter, (unsigned char **) &v);
384       if (v)
385         {
386           *version_ret = strdup (v);
387           XFree (v);
388         }
389     }
390
391   if (user_ret || host_ret)
392     {
393       char *id = 0;
394       const char *user = 0;
395       const char *host = 0;
396
397       XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512,
398                           False, XA_STRING, &type, &format, &nitems,
399                           &bytesafter, (unsigned char **) &id);
400       if (id && *id)
401         {
402           const char *old_tag = " on host ";
403           const char *s = strstr (id, old_tag);
404           if (s)
405             {
406               /* found ID of the form "1234 on host xyz". */
407               user = 0;
408               host = s + strlen (old_tag);
409             }
410           else
411             {
412               char *o = 0, *p = 0, *c = 0;
413               o = strchr (id, '(');
414               if (o) p = strchr (o, '@');
415               if (p) c = strchr (p, ')');
416               if (c)
417                 {
418                   /* found ID of the form "1234 (user@host)". */
419                   user = o+1;
420                   host = p+1;
421                   *p = 0;
422                   *c = 0;
423                 }
424             }
425
426         }
427
428       if (user && *user && *user != '?')
429         *user_ret = strdup (user);
430       else
431         *user_ret = 0;
432
433       if (host && *host && *host != '?')
434         *host_ret = strdup (host);
435       else
436         *host_ret = 0;
437
438       if (id)
439         XFree (id);
440     }
441 }