684fd4e347af27042914b61806357d21fd7a9ebb
[xscreensaver] / driver / xscreensaver-command.c
1 /* xscreensaver-command, Copyright (c) 1991-2000
2  *  by Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdio.h>
18 #include <stdlib.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-2000 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
151   progname = argv[0];
152   screensaver_version = (char *) malloc (5);
153   memcpy (screensaver_version, screensaver_id + 17, 4);
154   screensaver_version [4] = 0;
155
156   for (i = 1; i < argc; i++)
157     {
158       const char *s = argv [i];
159       int L;
160       if (s[0] == '-' && s[1] == '-') s++;
161       L = strlen (s);
162       if (L < 2) USAGE ();
163       if (!strncmp (s, "-display", L))          dpyname = argv [++i];
164       else if (cmd) USAGE();
165       else if (!strncmp (s, "-activate", L))   cmd = &XA_ACTIVATE;
166       else if (!strncmp (s, "-deactivate", L)) cmd = &XA_DEACTIVATE;
167       else if (!strncmp (s, "-cycle", L))      cmd = &XA_CYCLE;
168       else if (!strncmp (s, "-next", L))       cmd = &XA_NEXT;
169       else if (!strncmp (s, "-prev", L))       cmd = &XA_PREV;
170       else if (!strncmp (s, "-select", L))     cmd = &XA_SELECT;
171       else if (!strncmp (s, "-exit", L))       cmd = &XA_EXIT;
172       else if (!strncmp (s, "-restart", L))    cmd = &XA_RESTART;
173       else if (!strncmp (s, "-demo", L))       cmd = &XA_DEMO;
174       else if (!strncmp (s, "-preferences",L)) cmd = &XA_PREFS;
175       else if (!strncmp (s, "-prefs",L))       cmd = &XA_PREFS;
176       else if (!strncmp (s, "-lock", L))       cmd = &XA_LOCK;
177       else if (!strncmp (s, "-throttle", L))   cmd = &XA_THROTTLE;
178       else if (!strncmp (s, "-unthrottle", L)) cmd = &XA_UNTHROTTLE;
179       else if (!strncmp (s, "-version", L))    cmd = &XA_SCREENSAVER_VERSION;
180       else if (!strncmp (s, "-time", L))       cmd = &XA_SCREENSAVER_STATUS;
181       else if (!strncmp (s, "-watch", L))      cmd = (Atom *) &watch;
182       else USAGE ();
183
184       if (cmd == &XA_SELECT || cmd == &XA_DEMO)
185         {
186           long a;
187           char c;
188           if (i+1 < argc && (1 == sscanf(argv[i+1], " %ld %c", &a, &c)))
189             {
190               arg = a;
191               i++;
192             }
193         }
194     }
195
196   if (!cmd)
197     USAGE ();
198
199   if (arg < 0)
200     /* no command may have a negative argument. */
201     USAGE();
202   else if (arg == 0)
203     {
204       /* SELECT must have a non-zero argument. */
205       if (cmd == &XA_SELECT)
206         USAGE();
207     }
208   else /* arg > 0 */
209     {
210       /* no command other than SELECT and DEMO may have a non-zero argument. */
211       if (cmd != &XA_DEMO && cmd != &XA_SELECT)
212         USAGE();
213     }
214
215
216
217   /* For backward compatibility: -demo with no arguments used to send a
218      "DEMO 0" ClientMessage to the xscreensaver process, which brought up
219      the built-in demo mode dialog.  Now that the demo mode dialog is no
220      longer built in, we bring it up by just running the "xscreensaver-demo"
221      program.
222
223      Note that "-DEMO <n>" still sends a ClientMessage.
224    */
225   if (cmd == &XA_PREFS ||
226       (cmd == &XA_DEMO && arg == 0))
227     {
228       char buf [512];
229       char *new_argv[] = { "xscreensaver-demo", 0, 0, 0, 0, 0 };
230       int ac = 1;
231
232       if (dpyname)
233         {
234           new_argv[ac++] = "-display";
235           new_argv[ac++] = dpyname;
236         }
237
238       if (cmd == &XA_PREFS)
239         new_argv[ac++] = "-prefs";
240
241       fflush(stdout);
242       fflush(stderr);
243       execvp (new_argv[0], new_argv);   /* shouldn't return */
244
245       sprintf (buf, "%s: could not exec %s", progname, new_argv[0]);
246       perror(buf);
247       fflush(stdout);
248       fflush(stderr);
249       exit (-1);
250     }
251
252
253
254   if (!dpyname) dpyname = (char *) getenv ("DISPLAY");
255   dpy = XOpenDisplay (dpyname);
256   if (!dpy)
257     {
258       fprintf (stderr, "%s: can't open display %s\n", progname,
259                (dpyname ? dpyname : "(null)"));
260       exit (1);
261     }
262
263   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
264   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
265   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
266   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
267   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
268   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
269   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
270   XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
271   XA_RESTART = XInternAtom (dpy, "RESTART", False);
272   XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
273   XA_NEXT = XInternAtom (dpy, "NEXT", False);
274   XA_PREV = XInternAtom (dpy, "PREV", False);
275   XA_SELECT = XInternAtom (dpy, "SELECT", False);
276   XA_EXIT = XInternAtom (dpy, "EXIT", False);
277   XA_DEMO = XInternAtom (dpy, "DEMO", False);
278   XA_PREFS = XInternAtom (dpy, "PREFS", False);
279   XA_LOCK = XInternAtom (dpy, "LOCK", False);
280   XA_BLANK = XInternAtom (dpy, "BLANK", False);
281   XA_THROTTLE = XInternAtom (dpy, "THROTTLE", False);
282   XA_UNTHROTTLE = XInternAtom (dpy, "UNTHROTTLE", False);
283
284   XSync (dpy, 0);
285
286   if (cmd == (Atom *) &watch)
287     {
288       i = watch (dpy);
289       exit (i);
290     }
291
292   if (*cmd == XA_ACTIVATE || *cmd == XA_LOCK ||
293       *cmd == XA_NEXT || *cmd == XA_PREV || *cmd == XA_SELECT)
294     /* People never guess that KeyRelease deactivates the screen saver too,
295        so if we're issuing an activation command, wait a second. */
296     sleep (1);
297
298   i = xscreensaver_command (dpy, *cmd, arg, True, NULL);
299   if (i < 0) exit (i);
300   else exit (0);
301 }
302
303
304 static int
305 watch (Display *dpy)
306 {
307   char *v = 0;
308   Window window = RootWindow (dpy, 0);
309   XWindowAttributes xgwa;
310   XEvent event;
311   CARD32 *last = 0;
312
313   if (v) free (v);
314   XGetWindowAttributes (dpy, window, &xgwa);
315   XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
316
317   while (1)
318     {
319       XNextEvent (dpy, &event);
320       if (event.xany.type == PropertyNotify &&
321           event.xproperty.state == PropertyNewValue &&
322           event.xproperty.atom == XA_SCREENSAVER_STATUS)
323         {
324           Atom type;
325           int format;
326           unsigned long nitems, bytesafter;
327           CARD32 *data = 0;
328
329           if (XGetWindowProperty (dpy,
330                                   RootWindow (dpy, 0),  /* always screen #0 */
331                                   XA_SCREENSAVER_STATUS,
332                                   0, 999, False, XA_INTEGER,
333                                   &type, &format, &nitems, &bytesafter,
334                                   (unsigned char **) &data)
335               == Success
336               && type
337               && data)
338             {
339               time_t tt;
340               char *s;
341               Bool changed = False;
342               Bool running = False;
343
344               if (type != XA_INTEGER || nitems < 3)
345                 {
346                 STATUS_LOSE:
347                   if (last) free (last);
348                   if (data) free (data);
349                   fprintf (stderr, "%s: bad status format on root window.\n",
350                            progname);
351                   return -1;
352                 }
353                   
354               tt = (time_t) data[1];
355               if (tt <= (time_t) 666000000L) /* early 1991 */
356                 goto STATUS_LOSE;
357
358               s = ctime(&tt);
359               if (s[strlen(s)-1] == '\n')
360                 s[strlen(s)-1] = 0;
361
362               if (!last || data[0] != last[0])
363                 {
364                   /* State changed. */
365                   if (data[0] == XA_BLANK)
366                     printf ("BLANK %s\n", s);
367                   else if (data[0] == XA_LOCK)
368                     printf ("LOCK %s\n", s);
369                   else if (data[0] == 0)
370                     printf ("UNBLANK %s\n", s);
371                   else
372                     goto STATUS_LOSE;
373                 }
374
375               if (!last)
376                 changed = True;
377               else
378                 {
379                   int i;
380                   for (i = 2; i < nitems; i++)
381                     {
382                       if (data[i] != last[i])
383                         changed = True;
384                       if (data[i])
385                         running = True;
386                     }
387                 }
388
389               if (running && changed)
390                 {
391                   int i;
392                   fprintf (stdout, "RUN", s);
393                   for (i = 2; i < nitems; i++)
394                     fprintf (stdout, " %d", (int) data[i]);
395                   fprintf (stdout, "\n");
396                 }
397
398               fflush (stdout);
399
400               if (last) free (last);
401               last = data;
402             }
403           else
404             {
405               if (last) free (last);
406               if (data) free (data);
407               fprintf (stderr, "%s: no saver status on root window.\n",
408                        progname);
409               return -1;
410             }
411         }
412     }
413 }