f068b54cc1e267fab2f69db64fad3e491700b42d
[xscreensaver] / driver / xscreensaver-command.c
1 /* xscreensaver-command, Copyright (c) 1991-2004 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_UNISTD_H
23 # include <unistd.h>
24 #endif
25
26 /* #include <X11/Xproto.h>      / * for CARD32 */
27 #include <X11/Xlib.h>
28 #include <X11/Xatom.h>
29 #include <X11/Xutil.h>          /* for XGetClassHint() */
30 #include <X11/Xos.h>
31
32 #include <X11/Intrinsic.h>      /* only needed to get through xscreensaver.h */
33
34
35 /* You might think that to read an array of 32-bit quantities out of a
36    server-side property, you would pass an array of 32-bit data quantities
37    into XGetWindowProperty().  You would be wrong.  You have to use an array
38    of longs, even if long is 64 bits (using 32 of each 64.)
39  */
40 typedef long PROP32;
41
42 #include "remote.h"
43 #include "version.h"
44
45 #ifdef _VROOT_H_
46 ERROR! you must not include vroot.h in this file
47 #endif
48
49 char *progname;
50
51 Atom XA_VROOT;
52 Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE;
53 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT;
54 Atom XA_BLANK, XA_LOCK;
55 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
56 static Atom XA_RESTART, XA_PREFS, XA_THROTTLE, XA_UNTHROTTLE;
57
58 static char *screensaver_version;
59 # ifdef __GNUC__
60   __extension__   /* don't warn about "string length is greater than the
61                      length ISO C89 compilers are required to support" in the
62                      usage string... */
63 # endif
64 static char *usage = "\n\
65 usage: %s -<option>\n\
66 \n\
67   This program provides external control of a running xscreensaver process.\n\
68   Version %s, copyright (c) 1991-2006 Jamie Zawinski <jwz@jwz.org>.\n\
69 \n\
70   The xscreensaver program is a daemon that runs in the background.\n\
71   You control a running xscreensaver process by sending it messages\n\
72   with this program, xscreensaver-command.  See the man pages for\n\
73   details.  These are the arguments understood by xscreensaver-command:\n\
74 \n\
75   -demo         Ask the xscreensaver process to enter interactive demo mode.\n\
76 \n\
77   -prefs        Ask the xscreensaver process to bring up the preferences\n\
78                 panel.\n\
79 \n\
80   -activate     Turn on the screensaver (blank the screen), as if the user\n\
81                 had been idle for long enough.\n\
82 \n\
83   -deactivate   Turns off the screensaver (un-blank the screen), as if user\n\
84                 activity had been detected.\n\
85 \n\
86   -cycle        If the screensaver is active (the screen is blanked), then\n\
87                 stop the current graphics demo and run a new one (chosen\n\
88                 randomly.)\n\
89 \n\
90   -next         Like either -activate or -cycle, depending on which is more\n\
91                 appropriate, except that the graphics hack that will be run\n\
92                 is the next one in the list, instead of a randomly-chosen\n\
93                 one.  In other words, repeatedly executing -next will cause\n\
94                 the xscreensaver process to invoke each graphics demo\n\
95                 sequentially.  (Though using the -demo option is probably\n\
96                 an easier way to accomplish that.)\n\
97 \n\
98   -prev         Like -next, but goes in the other direction.\n\
99 \n\
100   -select <N>   Like -activate, but runs the Nth element in the list of\n\
101                 hacks.  By knowing what is in the `programs' list, and in\n\
102                 what order, you can use this to activate the screensaver\n\
103                 with a particular graphics demo.  (The first element in the\n\
104                 list is numbered 1, not 0.)\n\
105 \n\
106   -exit         Causes the xscreensaver process to exit gracefully.\n\
107                 This does nothing if the display is currently locked.\n\
108                 (Note that one must *never* kill xscreensaver with -9!)\n\
109 \n\
110   -restart      Causes the screensaver process to exit and then restart with\n\
111                 the same command line arguments as last time.  Do this after\n\
112                 you've changed your X resource settings, to cause\n\
113                 xscreensaver to notice the changes.\n\
114 \n\
115   -lock         Tells the running xscreensaver process to lock the screen\n\
116                 immediately.  This is like -activate, but forces locking as\n\
117                 well, even if locking is not the default.  If the saver is\n\
118                 already active, this causes it to be locked as well.\n\
119 \n\
120   -version      Prints the version of xscreensaver that is currently running\n\
121                 on the display -- that is, the actual version number of the\n\
122                 running xscreensaver background process, rather than the\n\
123                 version number of xscreensaver-command.\n\
124 \n\
125   -time         Prints the time at which the screensaver last activated or\n\
126                 deactivated (roughly, how long the user has been idle or\n\
127                 non-idle -- but not quite, since it only tells you when the\n\
128                 screen became blanked or un-blanked.)\n\
129 \n\
130   -watch        Prints a line each time the screensaver changes state: when\n\
131                 the screen blanks, locks, unblanks, or when the running hack\n\
132                 is changed.  This option never returns; it is intended for\n\
133                 by shell scripts that want to react to the screensaver in\n\
134                 some way.\n\
135 \n\
136   See the man page for more details.\n\
137   For updates, check http://www.jwz.org/xscreensaver/\n\
138 \n";
139
140 /* Note: The "-throttle" command is deprecated -- it predates the XDPMS
141    extension.  Instead of using -throttle, users should instead just
142    power off the monitor (e.g., "xset dpms force off".)  In a few
143    minutes, the xscreensaver daemon will notice that the monitor is
144    off, and cease running hacks.
145  */
146
147 #define USAGE() do { \
148  fprintf (stderr, usage, progname, screensaver_version); exit (1); \
149  } while(0)
150
151 static int watch (Display *);
152
153 int
154 main (int argc, char **argv)
155 {
156   Display *dpy;
157   int i;
158   char *dpyname = 0;
159   Atom *cmd = 0;
160   long arg = 0L;
161   char *s;
162   Atom XA_WATCH = 0;  /* kludge: not really an atom */
163
164   progname = argv[0];
165   s = strrchr (progname, '/');
166   if (s) progname = s+1;
167
168   screensaver_version = (char *) malloc (5);
169   memcpy (screensaver_version, screensaver_id + 17, 4);
170   screensaver_version [4] = 0;
171
172   for (i = 1; i < argc; i++)
173     {
174       const char *s = argv [i];
175       int L;
176       if (s[0] == '-' && s[1] == '-') s++;
177       L = strlen (s);
178       if (L < 2) USAGE ();
179       if (!strncmp (s, "-display", L))          dpyname = argv [++i];
180       else if (cmd) USAGE();
181       else if (!strncmp (s, "-activate", L))   cmd = &XA_ACTIVATE;
182       else if (!strncmp (s, "-deactivate", L)) cmd = &XA_DEACTIVATE;
183       else if (!strncmp (s, "-cycle", L))      cmd = &XA_CYCLE;
184       else if (!strncmp (s, "-next", L))       cmd = &XA_NEXT;
185       else if (!strncmp (s, "-prev", L))       cmd = &XA_PREV;
186       else if (!strncmp (s, "-select", L))     cmd = &XA_SELECT;
187       else if (!strncmp (s, "-exit", L))       cmd = &XA_EXIT;
188       else if (!strncmp (s, "-restart", L))    cmd = &XA_RESTART;
189       else if (!strncmp (s, "-demo", L))       cmd = &XA_DEMO;
190       else if (!strncmp (s, "-preferences",L)) cmd = &XA_PREFS;
191       else if (!strncmp (s, "-prefs",L))       cmd = &XA_PREFS;
192       else if (!strncmp (s, "-lock", L))       cmd = &XA_LOCK;
193       else if (!strncmp (s, "-throttle", L))   cmd = &XA_THROTTLE;
194       else if (!strncmp (s, "-unthrottle", L)) cmd = &XA_UNTHROTTLE;
195       else if (!strncmp (s, "-version", L))    cmd = &XA_SCREENSAVER_VERSION;
196       else if (!strncmp (s, "-time", L))       cmd = &XA_SCREENSAVER_STATUS;
197       else if (!strncmp (s, "-watch", L))      cmd = &XA_WATCH;
198       else USAGE ();
199
200       if (cmd == &XA_SELECT || cmd == &XA_DEMO)
201         {
202           long a;
203           char c;
204           if (i+1 < argc && (1 == sscanf(argv[i+1], " %ld %c", &a, &c)))
205             {
206               arg = a;
207               i++;
208             }
209         }
210     }
211
212   if (!cmd)
213     USAGE ();
214
215   if (arg < 0)
216     /* no command may have a negative argument. */
217     USAGE();
218   else if (arg == 0)
219     {
220       /* SELECT must have a non-zero argument. */
221       if (cmd == &XA_SELECT)
222         USAGE();
223     }
224   else /* arg > 0 */
225     {
226       /* no command other than SELECT and DEMO may have a non-zero argument. */
227       if (cmd != &XA_DEMO && cmd != &XA_SELECT)
228         USAGE();
229     }
230
231
232
233   /* For backward compatibility: -demo with no arguments used to send a
234      "DEMO 0" ClientMessage to the xscreensaver process, which brought up
235      the built-in demo mode dialog.  Now that the demo mode dialog is no
236      longer built in, we bring it up by just running the "xscreensaver-demo"
237      program.
238
239      Note that "-DEMO <n>" still sends a ClientMessage.
240    */
241   if (cmd == &XA_PREFS ||
242       (cmd == &XA_DEMO && arg == 0))
243     {
244       char buf [512];
245       char *new_argv[] = { "xscreensaver-demo", 0, 0, 0, 0, 0 };
246       int ac = 1;
247
248       if (dpyname)
249         {
250           new_argv[ac++] = "-display";
251           new_argv[ac++] = dpyname;
252         }
253
254       if (cmd == &XA_PREFS)
255         new_argv[ac++] = "-prefs";
256
257       fflush(stdout);
258       fflush(stderr);
259       execvp (new_argv[0], new_argv);   /* shouldn't return */
260
261       sprintf (buf, "%s: could not exec %s", progname, new_argv[0]);
262       perror(buf);
263       fflush(stdout);
264       fflush(stderr);
265       exit (-1);
266     }
267
268
269
270   if (!dpyname) dpyname = (char *) getenv ("DISPLAY");
271
272   if (!dpyname)
273     {
274       dpyname = ":0.0";
275       fprintf (stderr,
276                "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
277                progname, dpyname);
278     }
279
280   dpy = XOpenDisplay (dpyname);
281   if (!dpy)
282     {
283       fprintf (stderr, "%s: can't open display %s\n", progname,
284                (dpyname ? dpyname : "(null)"));
285       exit (1);
286     }
287
288   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
289   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
290   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
291   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
292   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
293   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
294   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
295   XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
296   XA_RESTART = XInternAtom (dpy, "RESTART", False);
297   XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
298   XA_NEXT = XInternAtom (dpy, "NEXT", False);
299   XA_PREV = XInternAtom (dpy, "PREV", False);
300   XA_SELECT = XInternAtom (dpy, "SELECT", False);
301   XA_EXIT = XInternAtom (dpy, "EXIT", False);
302   XA_DEMO = XInternAtom (dpy, "DEMO", False);
303   XA_PREFS = XInternAtom (dpy, "PREFS", False);
304   XA_LOCK = XInternAtom (dpy, "LOCK", False);
305   XA_BLANK = XInternAtom (dpy, "BLANK", False);
306   XA_THROTTLE = XInternAtom (dpy, "THROTTLE", False);
307   XA_UNTHROTTLE = XInternAtom (dpy, "UNTHROTTLE", False);
308
309   XSync (dpy, 0);
310
311   if (cmd == &XA_WATCH)
312     {
313       i = watch (dpy);
314       exit (i);
315     }
316
317   if (*cmd == XA_ACTIVATE || *cmd == XA_LOCK ||
318       *cmd == XA_NEXT || *cmd == XA_PREV || *cmd == XA_SELECT)
319     /* People never guess that KeyRelease deactivates the screen saver too,
320        so if we're issuing an activation command, wait a second. */
321     sleep (1);
322
323   i = xscreensaver_command (dpy, *cmd, arg, True, NULL);
324   if (i < 0) exit (i);
325   else exit (0);
326 }
327
328
329 static int
330 watch (Display *dpy)
331 {
332   char *v = 0;
333   Window window = RootWindow (dpy, 0);
334   XWindowAttributes xgwa;
335   XEvent event;
336   PROP32 *last = 0;
337
338   if (v) free (v);
339   XGetWindowAttributes (dpy, window, &xgwa);
340   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
341
342   while (1)
343     {
344       XNextEvent (dpy, &event);
345       if (event.xany.type == PropertyNotify &&
346           event.xproperty.state == PropertyNewValue &&
347           event.xproperty.atom == XA_SCREENSAVER_STATUS)
348         {
349           Atom type;
350           int format;
351           unsigned long nitems, bytesafter;
352           unsigned char *dataP = 0;
353
354           if (XGetWindowProperty (dpy,
355                                   RootWindow (dpy, 0),  /* always screen #0 */
356                                   XA_SCREENSAVER_STATUS,
357                                   0, 999, False, XA_INTEGER,
358                                   &type, &format, &nitems, &bytesafter,
359                                   &dataP)
360               == Success
361               && type
362               && dataP)
363             {
364               time_t tt;
365               char *s;
366               Bool changed = False;
367               Bool running = False;
368               PROP32 *data = (PROP32 *) dataP;
369
370               if (type != XA_INTEGER || nitems < 3)
371                 {
372                 STATUS_LOSE:
373                   if (last) XFree (last);
374                   if (data) XFree (data);
375                   fprintf (stderr, "%s: bad status format on root window.\n",
376                            progname);
377                   return -1;
378                 }
379                   
380               tt = (time_t) data[1];
381               if (tt <= (time_t) 666000000L) /* early 1991 */
382                 goto STATUS_LOSE;
383
384               s = ctime(&tt);
385               if (s[strlen(s)-1] == '\n')
386                 s[strlen(s)-1] = 0;
387
388               if (!last || data[0] != last[0])
389                 {
390                   /* State changed. */
391                   if (data[0] == XA_BLANK)
392                     printf ("BLANK %s\n", s);
393                   else if (data[0] == XA_LOCK)
394                     printf ("LOCK %s\n", s);
395                   else if (data[0] == 0)
396                     printf ("UNBLANK %s\n", s);
397                   else
398                     goto STATUS_LOSE;
399                 }
400
401               if (!last)
402                 changed = True;
403               else
404                 {
405                   int i;
406                   for (i = 2; i < nitems; i++)
407                     {
408                       if (data[i] != last[i])
409                         changed = True;
410                       if (data[i])
411                         running = True;
412                     }
413                 }
414
415               if (running && changed)
416                 {
417                   int i;
418                   fprintf (stdout, "RUN");
419                   for (i = 2; i < nitems; i++)
420                     fprintf (stdout, " %d", (int) data[i]);
421                   fprintf (stdout, "\n");
422                 }
423
424               fflush (stdout);
425
426               if (last) XFree (last);
427               last = data;
428             }
429           else
430             {
431               if (last) XFree (last);
432               if (dataP) XFree (dataP);
433               fprintf (stderr, "%s: no saver status on root window.\n",
434                        progname);
435               return -1;
436             }
437         }
438     }
439 }