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