http://ftp.x.org/contrib/applications/xscreensaver-2.34.tar.gz
[xscreensaver] / driver / xscreensaver-command.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 #define STANDALONE
14
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/time.h>
22 #include <sys/types.h>
23
24 #ifdef HAVE_UNISTD_H
25 # include <unistd.h>
26 #endif
27
28 #include <X11/Xproto.h>         /* for CARD32 */
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31 #include <X11/Xutil.h>          /* for XGetClassHint() */
32 #include <X11/Xos.h>
33
34 #include <X11/Intrinsic.h>      /* only needed to get through xscreensaver.h */
35
36 #include "version.h"
37
38 #ifdef STANDALONE
39   static char *progname;
40   static Atom XA_VROOT;
41   static Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
42   static Atom XA_SCREENSAVER_TIME, XA_SELECT;
43 #else  /* !STANDALONE */
44 # include "xscreensaver.h"
45 #endif /* !STANDALONE */
46
47
48 #ifdef _VROOT_H_
49 ERROR! you must not include vroot.h in this file
50 #endif
51
52
53
54 static Window
55 find_screensaver_window (Display *dpy, char **version)
56 {
57   int i;
58   Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
59   Window root2, parent, *kids;
60   unsigned int nkids;
61
62   if (version) *version = 0;
63
64   if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
65     abort ();
66   if (root != root2)
67     abort ();
68   if (parent)
69     abort ();
70   if (! (kids && nkids))
71     abort ();
72   for (i = 0; i < nkids; i++)
73     {
74       Atom type;
75       int format;
76       unsigned long nitems, bytesafter;
77       char *v;
78
79       if (XGetWindowProperty (dpy, kids[i],
80                               XA_SCREENSAVER_VERSION,
81                               0, 200, False, XA_STRING,
82                               &type, &format, &nitems, &bytesafter,
83                               (unsigned char **) &v)
84           == Success
85           && type != None)
86         {
87           if (version)
88             *version = v;
89           return kids[i];
90         }
91     }
92   fprintf (stderr, "%s: no screensaver is running on display %s\n", progname,
93            DisplayString (dpy));
94   return 0;
95 }
96
97
98 static int
99 send_xscreensaver_command (Display *dpy, Atom command, long argument,
100                            Window *window_ret)
101 {
102   char *v = 0;
103   Window window = find_screensaver_window (dpy, &v);
104   XWindowAttributes xgwa;
105
106   if (window_ret)
107     *window_ret = window;
108
109   if (!window)
110     return -1;
111
112   /* Select for property change events, so that we can read the response. */
113   XGetWindowAttributes (dpy, window, &xgwa);
114   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
115
116   if (command == XA_SCREENSAVER_TIME ||
117       command == XA_SCREENSAVER_VERSION)
118     {
119       XClassHint hint;
120       memset (&hint, 0, sizeof(hint));
121       if (!v || !*v)
122         {
123           fprintf (stderr, "%s: version property not set on window 0x%x?\n",
124                    progname, (unsigned int) window);
125           return -1;
126         }
127
128       XGetClassHint(dpy, window, &hint);
129       if (!hint.res_class)
130         {
131           fprintf (stderr, "%s: class hints not set on window 0x%x?\n",
132                    progname, (unsigned int) window);
133           return -1;
134         }
135
136       fprintf (stdout, "%s %s", hint.res_class, v);
137
138       if (command != XA_SCREENSAVER_TIME)
139         {
140           fprintf (stdout, "\n");
141         }
142       else
143         {
144           Atom type;
145           int format;
146           unsigned long nitems, bytesafter;
147           unsigned char *data = 0;
148           Bool active_p = False;
149
150           if (XGetWindowProperty (dpy, window, XA_VROOT,
151                                   0, 0, False, XA_WINDOW,
152                                   &type, &format, &nitems, &bytesafter,
153                                   &data)
154               == Success
155               && type != None)
156             active_p = True;
157
158           if (data) free (data);
159           data = 0;
160
161           if (XGetWindowProperty (dpy, window,
162                                   XA_SCREENSAVER_TIME,
163                                   0, 1, False, XA_INTEGER,
164                                   &type, &format, &nitems, &bytesafter,
165                                   &data)
166               == Success
167               && type == XA_INTEGER
168               && data)
169             {
170               CARD32 time32 = *((CARD32 *)data);
171               time_t tt = (time_t) time32;
172
173               if (active_p)
174                 fprintf (stdout, ": screen blanked since ");
175               else
176                 /* suggestions for a better way to phrase this are welcome. */
177                 fprintf (stdout, ": screen non-blanked since ");
178               fprintf (stdout, "%s", ctime(&tt));
179               if (data) free (data);
180             }
181           else
182             {
183               if (data) free (data);
184               fprintf (stdout, "\n");
185               fflush (stdout);
186               fprintf (stderr, "%s: no time on window 0x%x (%s %s).\n",
187                        progname, (unsigned int) window,
188                        hint.res_class, (v ? v : "???"));
189               return -1;
190             }
191         }
192
193       /* No need to read a response for these commands. */
194       return 1;
195     }
196   else
197     {
198       XEvent event;
199       long arg1 = (command == XA_SELECT ? argument : 0L);
200       event.xany.type = ClientMessage;
201       event.xclient.display = dpy;
202       event.xclient.window = window;
203       event.xclient.message_type = XA_SCREENSAVER;
204       event.xclient.format = 32;
205       memset (&event.xclient.data, 0, sizeof(event.xclient.data));
206       event.xclient.data.l[0] = (long) command;
207       event.xclient.data.l[1] = arg1;
208       if (! XSendEvent (dpy, window, False, 0L, &event))
209         {
210           fprintf (stderr, "%s: XSendEvent(dpy, 0x%x ...) failed.\n",
211                    progname, (unsigned int) window);
212           return -1;
213         }
214     }
215   XSync (dpy, 0);
216   return 0;
217 }
218
219
220 static XErrorHandler old_handler = 0;
221 static Bool got_badwindow = False;
222 static int
223 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
224 {
225   /* When we notice a window being created, we spawn a timer that waits
226      30 seconds or so, and then selects events on that window.  This error
227      handler is used so that we can cope with the fact that the window
228      may have been destroyed <30 seconds after it was created.
229    */
230   if (error->error_code == BadWindow)
231     {
232       got_badwindow = True;
233       return 0;
234     }
235   else
236     {
237       fprintf (stderr, "%s: ", progname);
238       return (*old_handler) (dpy, error);
239     }
240 }
241
242
243 static int
244 xscreensaver_command_response (Display *dpy, Window window)
245 {
246   int fd = ConnectionNumber (dpy);
247   int timeout = 10;
248   int status;
249   fd_set fds;
250   struct timeval tv;
251
252   while (1)
253     {
254       FD_ZERO(&fds);
255       FD_SET(fd, &fds);
256       memset(&tv, 0, sizeof(tv));
257       tv.tv_sec = timeout;
258       status = select (fd+1, &fds, 0, &fds, &tv);
259
260       if (status < 0)
261         {
262           char buf[1024];
263           sprintf (buf, "%s: waiting for reply", progname);
264           perror (buf);
265           return status;
266         }
267       else if (status == 0)
268         {
269           fprintf (stderr, "%s: no response to command.\n", progname);
270           return -1;
271         }
272       else
273         {
274           XEvent event;
275           XNextEvent (dpy, &event);
276           switch (event.xany.type) {
277           case PropertyNotify:
278             if (event.xproperty.state == PropertyNewValue &&
279                 event.xproperty.atom == XA_SCREENSAVER_RESPONSE)
280               {
281                 Status st2;
282                 Atom type;
283                 int format;
284                 unsigned long nitems, bytesafter;
285                 char *msg = 0;
286
287                 old_handler = XSetErrorHandler (BadWindow_ehandler);
288                 XSync (dpy, False);
289
290                 st2 = XGetWindowProperty (dpy, window,
291                                           XA_SCREENSAVER_RESPONSE,
292                                           0, 1024, True,
293                                           AnyPropertyType,
294                                           &type, &format, &nitems, &bytesafter,
295                                           (unsigned char **) &msg);
296
297                 if (got_badwindow)
298                   {
299                     fprintf (stdout,
300                              "%s: xscreensaver window has been deleted.\n",
301                              progname);
302                     return 0;
303                   }
304
305                 if (st2 == Success && type != None)
306                   {
307                     if (type != XA_STRING || format != 8)
308                       {
309                         fprintf (stderr,
310                                  "%s: unrecognized response property.\n",
311                                  progname);
312                         if (msg) XFree (msg);
313                         return -1;
314                       }
315                     else if (!msg || (msg[0] != '+' && msg[0] != '-'))
316                       {
317                         fprintf (stderr,
318                                  "%s: unrecognized response message.\n",
319                                  progname);
320                         if (msg) XFree (msg);
321                         return -1;
322                       }
323                     else
324                       {
325                         int ret = (msg[0] == '+' ? 0 : -1);
326                         fprintf ((ret < 0 ? stderr : stdout),
327                                  "%s: %s\n",
328                                  progname,
329                                  msg+1);
330                         XFree (msg);
331                         return ret;
332                       }
333                   }
334               }
335             break;
336
337           default:
338             fprintf (stderr, "%s: got unexpected response event %d.\n",
339                      progname, event.xany.type);
340             return -1;
341             break;
342           }
343         }
344     }
345 }
346
347
348 int
349 xscreensaver_command (Display *dpy, Atom command, long argument)
350 {
351   Window w = 0;
352   int status = send_xscreensaver_command (dpy, command, argument, &w);
353   if (status == 0)
354     status = xscreensaver_command_response (dpy, w);
355   fflush (stdout);
356   fflush (stderr);
357   return status;
358 }
359
360 \f
361 #ifdef STANDALONE
362 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
363 static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_PREFS, XA_LOCK;
364
365 static char *progname;
366 static char *screensaver_version;
367 static char *usage = "\n\
368 usage: %s -<option>\n\
369 \n\
370   This program provides external control of a running xscreensaver process.\n\
371   Version %s, copyright (c) 1991-1998 Jamie Zawinski <jwz@jwz.org>.\n\
372 \n\
373   The xscreensaver program is a daemon that runs in the background.\n\
374   You control a running xscreensaver process by sending it messages\n\
375   with this program, xscreensaver-command.  See the man pages for\n\
376   details.  These are the arguments understood by xscreensaver-command:\n\
377 \n\
378   -demo         Ask the xscreensaver process to enter interactive demo mode.\n\
379 \n\
380   -prefs        Ask the xscreensaver process to bring up the preferences\n\
381                 panel.\n\
382 \n\
383   -activate     Turn on the screensaver (blank the screen), as if the user\n\
384                 had been idle for long enough.\n\
385 \n\
386   -deactivate   Turns off the screensaver (un-blank the screen), as if user\n\
387                 activity had been detected.\n\
388 \n\
389   -cycle        If the screensaver is active (the screen is blanked), then\n\
390                 stop the current graphics demo and run a new one (chosen\n\
391                 randomly.)\n\
392 \n\
393   -next         Like either -activate or -cycle, depending on which is more\n\
394                 appropriate, except that the graphics hack that will be run\n\
395                 is the next one in the list, instead of a randomly-chosen\n\
396                 one.  In other words, repeatedly executing -next will cause\n\
397                 the xscreensaver process to invoke each graphics demo\n\
398                 sequentially.  (Though using the -demo option is probably\n\
399                 an easier way to accomplish that.)\n\
400 \n\
401   -prev         Like -next, but goes in the other direction.\n\
402 \n\
403   -select <N>   Like -activate, but runs the Nth element in the list of\n\
404                 hacks.  By knowing what is in the `programs' list, and in\n\
405                 what order, you can use this to activate the screensaver\n\
406                 with a particular graphics demo.  (The first element in the\n\
407                 list is numbered 1, not 0.)\n\
408 \n\
409   -exit         Causes the xscreensaver process to exit gracefully.  This is\n\
410                 roughly the same as killing the process with `kill', but it\n\
411                 is easier, since you don't need to first figure out the pid.\n\
412                 (Note that one must *never* kill xscreensaver with -9!)\n\
413 \n\
414   -restart      Causes the screensaver process to exit and then restart with\n\
415                 the same command line arguments as last time.  Do this after\n\
416                 you've changed your X resource settings, to cause\n\
417                 xscreensaver to notice the changes.\n\
418 \n\
419   -lock         Tells the running xscreensaver process to lock the screen\n\
420                 immediately.  This is like -activate, but forces locking as\n\
421                 well, even if locking is not the default.\n\
422 \n\
423   -version      Prints the version of xscreensaver that is currently running\n\
424                 on the display -- that is, the actual version number of the\n\
425                 running xscreensaver background process, rather than the\n\
426                 version number of xscreensaver-command.\n\
427 \n\
428   -time         Prints the time at which the screensaver last activated or\n\
429                 deactivated (roughly, how long the user has been idle or\n\
430                 non-idle -- but not quite, since it only tells you when the\n\
431                 screen became blanked or un-blanked.)\n\
432 \n\
433   See the man page for more details.\n\
434   For updates, check http://www.jwz.org/xscreensaver/\n\
435 \n";
436
437 #define USAGE() \
438  { fprintf (stderr, usage, progname, screensaver_version); exit (1); }
439
440 int
441 main (int argc, char **argv)
442 {
443   Display *dpy;
444   int i;
445   char *dpyname = 0;
446   Atom *cmd = 0;
447   long arg = 0L;
448
449   progname = argv[0];
450   screensaver_version = (char *) malloc (5);
451   memcpy (screensaver_version, screensaver_id + 17, 4);
452   screensaver_version [4] = 0;
453
454   for (i = 1; i < argc; i++)
455     {
456       const char *s = argv [i];
457       int L;
458       if (s[0] == '-' && s[1] == '-') s++;
459       L = strlen (s);
460       if (L < 2) USAGE ();
461       if (!strncmp (s, "-display", L))          dpyname = argv [++i];
462       else if (cmd) USAGE ()
463       else if (!strncmp (s, "-activate", L))   cmd = &XA_ACTIVATE;
464       else if (!strncmp (s, "-deactivate", L)) cmd = &XA_DEACTIVATE;
465       else if (!strncmp (s, "-cycle", L))      cmd = &XA_CYCLE;
466       else if (!strncmp (s, "-next", L))       cmd = &XA_NEXT;
467       else if (!strncmp (s, "-prev", L))       cmd = &XA_PREV;
468       else if (!strncmp (s, "-select", L))     cmd = &XA_SELECT;
469       else if (!strncmp (s, "-exit", L))       cmd = &XA_EXIT;
470       else if (!strncmp (s, "-restart", L))    cmd = &XA_RESTART;
471       else if (!strncmp (s, "-demo", L))       cmd = &XA_DEMO;
472       else if (!strncmp (s, "-preferences",L)) cmd = &XA_PREFS;
473       else if (!strncmp (s, "-prefs",L))       cmd = &XA_PREFS;
474       else if (!strncmp (s, "-lock", L))       cmd = &XA_LOCK;
475       else if (!strncmp (s, "-version", L))    cmd = &XA_SCREENSAVER_VERSION;
476       else if (!strncmp (s, "-time", L))       cmd = &XA_SCREENSAVER_TIME;
477       else USAGE ();
478
479       if (cmd == &XA_SELECT)
480         {
481           char junk;
482           i++;
483           if (i >= argc ||
484               (1 != sscanf(argv[i], " %ld %c", &arg, &junk)))
485             USAGE ();
486         }
487     }
488   if (!cmd)
489     USAGE ();
490
491   if (!dpyname) dpyname = (char *) getenv ("DISPLAY");
492   dpy = XOpenDisplay (dpyname);
493   if (!dpy)
494     {
495       fprintf (stderr, "%s: can't open display %s\n", progname,
496                (dpyname ? dpyname : "(null)"));
497       exit (1);
498     }
499
500   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
501   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
502   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
503   XA_SCREENSAVER_TIME = XInternAtom (dpy, "_SCREENSAVER_TIME", False);
504   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
505   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
506   XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
507   XA_RESTART = XInternAtom (dpy, "RESTART", False);
508   XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
509   XA_NEXT = XInternAtom (dpy, "NEXT", False);
510   XA_PREV = XInternAtom (dpy, "PREV", False);
511   XA_SELECT = XInternAtom (dpy, "SELECT", False);
512   XA_EXIT = XInternAtom (dpy, "EXIT", False);
513   XA_DEMO = XInternAtom (dpy, "DEMO", False);
514   XA_PREFS = XInternAtom (dpy, "PREFS", False);
515   XA_LOCK = XInternAtom (dpy, "LOCK", False);
516
517   XSync (dpy, 0);
518
519   if (*cmd == XA_ACTIVATE || *cmd == XA_LOCK ||
520       *cmd == XA_NEXT || *cmd == XA_PREV || *cmd == XA_SELECT)
521     /* People never guess that KeyRelease deactivates the screen saver too,
522        so if we're issuing an activation command, wait a second. */
523     sleep (1);
524
525   i = xscreensaver_command (dpy, *cmd, arg);
526   if (i < 0) exit (i);
527   else exit (0);
528 }
529
530 #endif /* STANDALONE */