ed10dc64adb09844040a374aa0386a32f5a9ad26
[xscreensaver] / driver / xscreensaver-command.c
1 /* xscreensaver-command, Copyright (c) 1991-2003 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   -throttle     Temporarily switch to ``blank screen'' mode, and don't run\n\
114                 any display modes at all, until the screensaver is next\n\
115                 de-activated.  This is useful if you're using a machine\n\
116                 remotely, and you find that some display modes are using too\n\
117                 much CPU.\n\
118 \n\
119   -unthrottle   Turn `-throttle' off and resume normal behavior.\n\
120 \n\
121   -version      Prints the version of xscreensaver that is currently running\n\
122                 on the display -- that is, the actual version number of the\n\
123                 running xscreensaver background process, rather than the\n\
124                 version number of xscreensaver-command.\n\
125 \n\
126   -time         Prints the time at which the screensaver last activated or\n\
127                 deactivated (roughly, how long the user has been idle or\n\
128                 non-idle -- but not quite, since it only tells you when the\n\
129                 screen became blanked or un-blanked.)\n\
130 \n\
131   -watch        Prints a line each time the screensaver changes state: when\n\
132                 the screen blanks, locks, unblanks, or when the running hack\n\
133                 is changed.  This option never returns; it is intended for\n\
134                 by shell scripts that want to react to the screensaver in\n\
135                 some way.\n\
136 \n\
137   See the man page for more details.\n\
138   For updates, check http://www.jwz.org/xscreensaver/\n\
139 \n";
140
141 #define USAGE() do { \
142  fprintf (stderr, usage, progname, screensaver_version); exit (1); \
143  } while(0)
144
145 static int watch (Display *);
146
147 int
148 main (int argc, char **argv)
149 {
150   Display *dpy;
151   int i;
152   char *dpyname = 0;
153   Atom *cmd = 0;
154   long arg = 0L;
155   char *s;
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 = (Atom *) &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 == (Atom *) &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           CARD32 *data = 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                                   (unsigned char **) &data)
353               == Success
354               && type
355               && data)
356             {
357               time_t tt;
358               char *s;
359               Bool changed = False;
360               Bool running = False;
361
362               if (type != XA_INTEGER || nitems < 3)
363                 {
364                 STATUS_LOSE:
365                   if (last) free (last);
366                   if (data) free (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) free (last);
419               last = data;
420             }
421           else
422             {
423               if (last) free (last);
424               if (data) free (data);
425               fprintf (stderr, "%s: no saver status on root window.\n",
426                        progname);
427               return -1;
428             }
429         }
430     }
431 }