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