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