From http://www.jwz.org/xscreensaver/xscreensaver-5.21.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 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, 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     sleep (1);
329
330   i = xscreensaver_command (dpy, *cmd, arg, True, NULL);
331   if (i < 0) exit (i);
332   else exit (0);
333 }
334
335
336 static int
337 watch (Display *dpy)
338 {
339   char *v = 0;
340   Window window = RootWindow (dpy, 0);
341   XWindowAttributes xgwa;
342   XEvent event;
343   PROP32 *last = 0;
344
345   if (v) free (v);
346   XGetWindowAttributes (dpy, window, &xgwa);
347   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
348
349   while (1)
350     {
351       XNextEvent (dpy, &event);
352       if (event.xany.type == PropertyNotify &&
353           event.xproperty.state == PropertyNewValue &&
354           event.xproperty.atom == XA_SCREENSAVER_STATUS)
355         {
356           Atom type;
357           int format;
358           unsigned long nitems, bytesafter;
359           unsigned char *dataP = 0;
360
361           if (XGetWindowProperty (dpy,
362                                   RootWindow (dpy, 0),  /* always screen #0 */
363                                   XA_SCREENSAVER_STATUS,
364                                   0, 999, False, XA_INTEGER,
365                                   &type, &format, &nitems, &bytesafter,
366                                   &dataP)
367               == Success
368               && type
369               && dataP)
370             {
371               time_t tt;
372               char *s;
373               Bool changed = False;
374               Bool running = False;
375               PROP32 *data = (PROP32 *) dataP;
376
377               if (type != XA_INTEGER || nitems < 3)
378                 {
379                 STATUS_LOSE:
380                   if (last) XFree (last);
381                   if (data) XFree (data);
382                   fprintf (stderr, "%s: bad status format on root window.\n",
383                            progname);
384                   return -1;
385                 }
386                   
387               tt = (time_t) data[1];
388               if (tt <= (time_t) 666000000L) /* early 1991 */
389                 goto STATUS_LOSE;
390
391               s = ctime(&tt);
392               if (s[strlen(s)-1] == '\n')
393                 s[strlen(s)-1] = 0;
394
395               if (!last || data[0] != last[0])
396                 {
397                   /* State changed. */
398                   if (data[0] == XA_BLANK)
399                     printf ("BLANK %s\n", s);
400                   else if (data[0] == XA_LOCK)
401                     printf ("LOCK %s\n", s);
402                   else if (data[0] == 0)
403                     printf ("UNBLANK %s\n", s);
404                   else
405                     goto STATUS_LOSE;
406                 }
407
408               if (!last)
409                 changed = True;
410               else
411                 {
412                   int i;
413                   for (i = 2; i < nitems; i++)
414                     {
415                       if (data[i] != last[i])
416                         changed = True;
417                       if (data[i])
418                         running = True;
419                     }
420                 }
421
422               if (running && changed)
423                 {
424                   int i;
425                   fprintf (stdout, "RUN");
426                   for (i = 2; i < nitems; i++)
427                     fprintf (stdout, " %d", (int) data[i]);
428                   fprintf (stdout, "\n");
429                 }
430
431               fflush (stdout);
432
433               if (last) XFree (last);
434               last = data;
435             }
436           else
437             {
438               if (last) XFree (last);
439               if (dataP) XFree (dataP);
440               fprintf (stderr, "%s: no saver status on root window.\n",
441                        progname);
442               return -1;
443             }
444         }
445     }
446 }