From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / driver / xscreensaver-command.c
1 /* xscreensaver-command, Copyright (c) 1991-2013 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-%s 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.  You shouldn't\n\
112                 really need to do this, since xscreensaver notices when the\n\
113                 .xscreensaver file has changed and re-reads it as needed.\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 https://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, year); 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   char year[5];
164
165   progname = argv[0];
166   s = strrchr (progname, '/');
167   if (s) progname = s+1;
168
169   screensaver_version = (char *) malloc (5);
170   memcpy (screensaver_version, screensaver_id + 17, 4);
171   screensaver_version [4] = 0;
172
173   s = strchr (screensaver_id, '-');
174   s = strrchr (s, '-');
175   s++;
176   strncpy (year, s, 4);
177   year[4] = 0;
178
179   for (i = 1; i < argc; i++)
180     {
181       const char *s = argv [i];
182       int L;
183       if (s[0] == '-' && s[1] == '-') s++;
184       L = strlen (s);
185       if (L < 2) USAGE ();
186       if (!strncmp (s, "-display", L))          dpyname = argv [++i];
187       else if (cmd) USAGE();
188       else if (!strncmp (s, "-activate", L))   cmd = &XA_ACTIVATE;
189       else if (!strncmp (s, "-deactivate", L)) cmd = &XA_DEACTIVATE;
190       else if (!strncmp (s, "-cycle", L))      cmd = &XA_CYCLE;
191       else if (!strncmp (s, "-next", L))       cmd = &XA_NEXT;
192       else if (!strncmp (s, "-prev", L))       cmd = &XA_PREV;
193       else if (!strncmp (s, "-select", L))     cmd = &XA_SELECT;
194       else if (!strncmp (s, "-exit", L))       cmd = &XA_EXIT;
195       else if (!strncmp (s, "-restart", L))    cmd = &XA_RESTART;
196       else if (!strncmp (s, "-demo", L))       cmd = &XA_DEMO;
197       else if (!strncmp (s, "-preferences",L)) cmd = &XA_PREFS;
198       else if (!strncmp (s, "-prefs",L))       cmd = &XA_PREFS;
199       else if (!strncmp (s, "-lock", L))       cmd = &XA_LOCK;
200       else if (!strncmp (s, "-throttle", L))   cmd = &XA_THROTTLE;
201       else if (!strncmp (s, "-unthrottle", L)) cmd = &XA_UNTHROTTLE;
202       else if (!strncmp (s, "-version", L))    cmd = &XA_SCREENSAVER_VERSION;
203       else if (!strncmp (s, "-time", L))       cmd = &XA_SCREENSAVER_STATUS;
204       else if (!strncmp (s, "-watch", L))      cmd = &XA_WATCH;
205       else USAGE ();
206
207       if (cmd == &XA_SELECT || cmd == &XA_DEMO)
208         {
209           long a;
210           char c;
211           if (i+1 < argc && (1 == sscanf(argv[i+1], " %ld %c", &a, &c)))
212             {
213               arg = a;
214               i++;
215             }
216         }
217     }
218
219   if (!cmd)
220     USAGE ();
221
222   if (arg < 0)
223     /* no command may have a negative argument. */
224     USAGE();
225   else if (arg == 0)
226     {
227       /* SELECT must have a non-zero argument. */
228       if (cmd == &XA_SELECT)
229         USAGE();
230     }
231   else /* arg > 0 */
232     {
233       /* no command other than SELECT and DEMO may have a non-zero argument. */
234       if (cmd != &XA_DEMO && cmd != &XA_SELECT)
235         USAGE();
236     }
237
238
239
240   /* For backward compatibility: -demo with no arguments used to send a
241      "DEMO 0" ClientMessage to the xscreensaver process, which brought up
242      the built-in demo mode dialog.  Now that the demo mode dialog is no
243      longer built in, we bring it up by just running the "xscreensaver-demo"
244      program.
245
246      Note that "-DEMO <n>" still sends a ClientMessage.
247    */
248   if (cmd == &XA_PREFS ||
249       (cmd == &XA_DEMO && arg == 0))
250     {
251       char buf [512];
252       char *new_argv[] = { "xscreensaver-demo", 0, 0, 0, 0, 0 };
253       int ac = 1;
254
255       if (dpyname)
256         {
257           new_argv[ac++] = "-display";
258           new_argv[ac++] = dpyname;
259         }
260
261       if (cmd == &XA_PREFS)
262         new_argv[ac++] = "-prefs";
263
264       fflush(stdout);
265       fflush(stderr);
266       execvp (new_argv[0], new_argv);   /* shouldn't return */
267
268       sprintf (buf, "%s: could not exec %s", progname, new_argv[0]);
269       perror(buf);
270       fflush(stdout);
271       fflush(stderr);
272       exit (-1);
273     }
274
275
276
277   if (!dpyname) dpyname = (char *) getenv ("DISPLAY");
278
279   if (!dpyname)
280     {
281       dpyname = ":0.0";
282       fprintf (stderr,
283                "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
284                progname, dpyname);
285     }
286
287   dpy = XOpenDisplay (dpyname);
288   if (!dpy)
289     {
290       fprintf (stderr, "%s: can't open display %s\n", progname,
291                (dpyname ? dpyname : "(null)"));
292       exit (1);
293     }
294
295   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
296   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
297   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
298   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
299   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
300   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
301   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
302   XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
303   XA_RESTART = XInternAtom (dpy, "RESTART", False);
304   XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
305   XA_NEXT = XInternAtom (dpy, "NEXT", False);
306   XA_PREV = XInternAtom (dpy, "PREV", False);
307   XA_SELECT = XInternAtom (dpy, "SELECT", False);
308   XA_EXIT = XInternAtom (dpy, "EXIT", False);
309   XA_DEMO = XInternAtom (dpy, "DEMO", False);
310   XA_PREFS = XInternAtom (dpy, "PREFS", False);
311   XA_LOCK = XInternAtom (dpy, "LOCK", False);
312   XA_BLANK = XInternAtom (dpy, "BLANK", False);
313   XA_THROTTLE = XInternAtom (dpy, "THROTTLE", False);
314   XA_UNTHROTTLE = XInternAtom (dpy, "UNTHROTTLE", False);
315
316   XSync (dpy, 0);
317
318   if (cmd == &XA_WATCH)
319     {
320       i = watch (dpy);
321       exit (i);
322     }
323
324   if (*cmd == XA_ACTIVATE || *cmd == XA_LOCK ||
325       *cmd == XA_NEXT || *cmd == XA_PREV || *cmd == XA_SELECT)
326     /* People never guess that KeyRelease deactivates the screen saver too,
327        so if we're issuing an activation command, wait a second.
328        No need to do this if stdin is not a tty, meaning we're not being
329        run from the command line.
330      */
331     if (isatty(0))
332       sleep (1);
333
334   i = xscreensaver_command (dpy, *cmd, arg, True, NULL);
335   if (i < 0) exit (i);
336   else exit (0);
337 }
338
339
340 static int
341 watch (Display *dpy)
342 {
343   char *v = 0;
344   Window window = RootWindow (dpy, 0);
345   XWindowAttributes xgwa;
346   XEvent event;
347   PROP32 *last = 0;
348
349   if (v) free (v);
350   XGetWindowAttributes (dpy, window, &xgwa);
351   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
352
353   while (1)
354     {
355       XNextEvent (dpy, &event);
356       if (event.xany.type == PropertyNotify &&
357           event.xproperty.state == PropertyNewValue &&
358           event.xproperty.atom == XA_SCREENSAVER_STATUS)
359         {
360           Atom type;
361           int format;
362           unsigned long nitems, bytesafter;
363           unsigned char *dataP = 0;
364
365           if (XGetWindowProperty (dpy,
366                                   RootWindow (dpy, 0),  /* always screen #0 */
367                                   XA_SCREENSAVER_STATUS,
368                                   0, 999, False, XA_INTEGER,
369                                   &type, &format, &nitems, &bytesafter,
370                                   &dataP)
371               == Success
372               && type
373               && dataP)
374             {
375               time_t tt;
376               char *s;
377               Bool changed = False;
378               Bool running = False;
379               PROP32 *data = (PROP32 *) dataP;
380
381               if (type != XA_INTEGER || nitems < 3)
382                 {
383                 STATUS_LOSE:
384                   if (last) XFree (last);
385                   if (data) XFree (data);
386                   fprintf (stderr, "%s: bad status format on root window.\n",
387                            progname);
388                   return -1;
389                 }
390                   
391               tt = (time_t) data[1];
392               if (tt <= (time_t) 666000000L) /* early 1991 */
393                 goto STATUS_LOSE;
394
395               s = ctime(&tt);
396               if (s[strlen(s)-1] == '\n')
397                 s[strlen(s)-1] = 0;
398
399               if (!last || data[0] != last[0])
400                 {
401                   /* State changed. */
402                   if (data[0] == XA_BLANK)
403                     printf ("BLANK %s\n", s);
404                   else if (data[0] == XA_LOCK)
405                     printf ("LOCK %s\n", s);
406                   else if (data[0] == 0)
407                     printf ("UNBLANK %s\n", s);
408                   else
409                     goto STATUS_LOSE;
410                 }
411
412               if (!last)
413                 changed = True;
414               else
415                 {
416                   int i;
417                   for (i = 2; i < nitems; i++)
418                     {
419                       if (data[i] != last[i])
420                         changed = True;
421                       if (data[i])
422                         running = True;
423                     }
424                 }
425
426               if (running && changed)
427                 {
428                   int i;
429                   fprintf (stdout, "RUN");
430                   for (i = 2; i < nitems; i++)
431                     fprintf (stdout, " %d", (int) data[i]);
432                   fprintf (stdout, "\n");
433                 }
434
435               fflush (stdout);
436
437               if (last) XFree (last);
438               last = data;
439             }
440           else
441             {
442               if (last) XFree (last);
443               if (dataP) XFree (dataP);
444               fprintf (stderr, "%s: no saver status on root window.\n",
445                        progname);
446               return -1;
447             }
448         }
449     }
450 }