1 /* $OpenBSD: xidle.c,v 1.3 2011/11/18 00:16:57 fgsch Exp $ */
2 /*
3 * Copyright (c) 2005 Federico G. Schwindt
4 * Copyright (c) 2005 Claudio Castiglia
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
19 * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include <X11/Xlib.h>
29 #include <X11/Xresource.h>
30 #include <X11/extensions/scrnsaver.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <err.h>
34 #include <limits.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 #ifndef CLASS_NAME
42 #define CLASS_NAME "XIdle"
43 #endif
44
45 #ifndef PATH_PROG
46 #define PATH_PROG "/usr/X11R6/bin/xlock"
47 #endif
48
49
50 enum {
51 north = 0x01,
52 south = 0x02,
53 east = 0x04,
54 west = 0x08
55 };
56
57 enum { XIDLE_LOCK = 1, XIDLE_DIE = 2 };
58
59 struct xinfo {
60 Display *dpy;
61 Window win;
62 int coord_x;
63 int coord_y;
64
65 int saver_event; /* Only if Xss ext is available */
66
67 int saved_timeout;
68 int saved_interval;
69 int saved_pref_blank;
70 int saved_allow_exp;
71 };
72
73 struct xinfo x;
74
75 static XrmOptionDescRec fopts[] = {
76 { "-display", ".display", XrmoptionSepArg, (caddr_t)NULL },
77 };
78
79 static XrmOptionDescRec opts[] = {
80 { "-area", ".area", XrmoptionSepArg, (caddr_t)NULL },
81 { "-delay", ".delay", XrmoptionSepArg, (caddr_t)NULL },
82 { "-program", ".program", XrmoptionSepArg, (caddr_t)NULL },
83 { "-timeout", ".timeout", XrmoptionSepArg, (caddr_t)NULL },
84
85 { "-ne", ".position", XrmoptionNoArg, (caddr_t)"ne" },
86 { "-nw", ".position", XrmoptionNoArg, (caddr_t)"nw" },
87 { "-se", ".position", XrmoptionNoArg, (caddr_t)"se" },
88 { "-sw", ".position", XrmoptionNoArg, (caddr_t)"sw" }
89 };
90
91 extern char *__progname;
92
93 void action(struct xinfo *, char **);
94 void close_x(struct xinfo *);
95 Bool getres(XrmValue *, const XrmDatabase, const char *, const char *);
96 void init_x(struct xinfo *, int, int, int);
97 void handler(int);
98 void parse_opts(int, char **, Display **, int *, int *, int *, int *,
99 char **);
100 int str2pos(const char *);
101 __dead void usage(void);
102
103
104 __dead void
usage()105 usage()
106 {
107 fprintf(stderr, "Usage:\n%s %s\n", __progname,
108 "[-area pixels] [-delay secs] [-display host:dpy] "
109 "[-ne | -nw | -se | -sw]\n [-program path] [-timeout secs]");
110 exit(1);
111 }
112
113
114 void
init_x(struct xinfo * xi,int position,int area,int timeout)115 init_x(struct xinfo *xi, int position, int area, int timeout)
116 {
117 XSetWindowAttributes attr;
118 Display *dpy = xi->dpy;
119 int error, event;
120 int screen;
121
122 screen = DefaultScreen(dpy);
123
124 if (position & south)
125 xi->coord_y = DisplayHeight(dpy, screen) - area;
126 if (position & east)
127 xi->coord_x = DisplayWidth(dpy, screen) - area;
128
129 attr.override_redirect = True;
130 xi->win = XCreateWindow(dpy, DefaultRootWindow(dpy),
131 xi->coord_x, xi->coord_y, area, area, 0, 0, InputOnly,
132 CopyFromParent, CWOverrideRedirect, &attr);
133
134 XSelectInput(dpy, xi->win, EnterWindowMask|StructureNotifyMask
135 #if 0
136 |VisibilityChangeMask
137 #endif
138 );
139 XMapWindow(dpy, xi->win);
140
141 /*
142 * AFAICT, we need the event number for ScreenSaverNotify
143 * _always_ since it's the only way to distinguish whether
144 * we've been obscured by an external locking program or
145 * by another window and react according.
146 */
147 if (XScreenSaverQueryExtension(dpy, &event, &error) == True) {
148 xi->saver_event = event;
149
150 XScreenSaverSelectInput(dpy, DefaultRootWindow(dpy),
151 ScreenSaverNotifyMask);
152 } else
153 warnx("XScreenSaver extension not available.%s",
154 timeout > 0 ? " Timeout disabled." : "");
155
156 if (timeout > 0 && xi->saver_event) {
157 XGetScreenSaver(dpy, &xi->saved_timeout, &xi->saved_interval,
158 &xi->saved_pref_blank, &xi->saved_allow_exp);
159
160 XSetScreenSaver(dpy, timeout, 0, DontPreferBlanking,
161 DontAllowExposures);
162 }
163 }
164
165
166 void
close_x(struct xinfo * xi)167 close_x(struct xinfo *xi)
168 {
169 XSetScreenSaver(xi->dpy, xi->saved_timeout, xi->saved_interval,
170 xi->saved_pref_blank, xi->saved_allow_exp);
171 XDestroyWindow(xi->dpy, xi->win);
172 XCloseDisplay(xi->dpy);
173 }
174
175
176 void
action(struct xinfo * xi,char ** args)177 action(struct xinfo *xi, char **args)
178 {
179 int dumb;
180
181 switch (fork()) {
182 case -1:
183 err(1, "fork");
184 /* NOTREACHED */
185
186 case 0:
187 execv(*args, args);
188 exit(1);
189 /* NOTREACHED */
190
191 default:
192 wait(&dumb);
193 XSync(xi->dpy, True);
194 break;
195 }
196 }
197
198
199 void
handler(int sig)200 handler(int sig)
201 {
202 XClientMessageEvent ev;
203
204 ev.type = ClientMessage;
205 ev.display = x.dpy;
206 ev.window = x.win;
207 if (sig == SIGUSR1)
208 ev.message_type = XIDLE_LOCK;
209 else
210 ev.message_type = XIDLE_DIE;
211 ev.format = 8;
212 XSendEvent(x.dpy, x.win, False, 0L, (XEvent *)&ev);
213 XFlush(x.dpy);
214 }
215
216
217 int
str2pos(const char * src)218 str2pos(const char *src)
219 {
220 static struct {
221 char *str;
222 int pos;
223 } s2p[] = {
224 { "ne", north|east },
225 { "nw", north|west },
226 { "se", south|east },
227 { "sw", south|west },
228 { NULL, 0 }
229 }, *s;
230
231 for (s = s2p; s->str != NULL; s++)
232 if (!strcmp(src, s->str))
233 break;
234 return (s->pos);
235 }
236
237
238 Bool
getres(XrmValue * value,const XrmDatabase rdb,const char * rname,const char * cname)239 getres(XrmValue *value, const XrmDatabase rdb, const char *rname,
240 const char *cname)
241 {
242 char fullres[PATH_MAX], fullclass[PATH_MAX], *type;
243
244 snprintf(fullres, sizeof(fullres), "%s.%s", __progname, rname);
245 snprintf(fullclass, sizeof(fullclass), "%s.%s", CLASS_NAME, cname);
246 return (XrmGetResource(rdb, fullres, fullclass, &type, value));
247 }
248
249
250 void
parse_opts(int argc,char ** argv,Display ** dpy,int * area,int * delay,int * timeout,int * position,char ** args)251 parse_opts(int argc, char **argv, Display **dpy, int *area, int *delay,
252 int *timeout, int *position, char **args)
253 {
254 char **ap, *program = PATH_PROG;
255 char *display, *p;
256 XrmDatabase tdb, rdb = NULL;
257 XrmValue value;
258
259 XrmInitialize();
260
261 /* Get display to open. */
262 XrmParseCommand(&rdb, fopts, sizeof(fopts) / sizeof(fopts[0]),
263 __progname, &argc, argv);
264
265 display = (getres(&value, rdb, "display", "Display") == True) ?
266 (char *)value.addr : NULL;
267
268 *dpy = XOpenDisplay(display);
269 if (!*dpy) {
270 errx(1, "Unable to open display %s", XDisplayName(display));
271 /* NOTREACHED */
272 }
273
274 /* Get server resources database. */
275 p = XResourceManagerString(*dpy);
276 if (!p) {
277 /* Get screen resources database. */
278 p = XScreenResourceString(ScreenOfDisplay(*dpy,
279 DefaultScreen(*dpy)));
280 }
281
282 if (p) {
283 tdb = XrmGetStringDatabase(p);
284 XrmMergeDatabases(tdb, &rdb);
285 }
286
287 /* Get remaining command line values. */
288 XrmParseCommand(&rdb, opts, sizeof(opts) / sizeof(opts[0]),
289 __progname, &argc, argv);
290 if (argc > 1) {
291 usage();
292 /* NOTREACHED */
293 }
294 if (getres(&value, rdb, "area", "Area")) {
295 *area = strtol((char *)value.addr, &p, 10);
296 if (*p || *area < 1) {
297 fail: errx(1, "illegal value -- %s", (char *)value.addr);
298 /* NOTREACHED */
299 }
300 }
301 if (getres(&value, rdb, "delay", "Delay")) {
302 *delay = strtol((char *)value.addr, &p, 10);
303 if (*p || *delay < 0)
304 goto fail;
305 }
306 if (getres(&value, rdb, "position", "Position")) {
307 *position = str2pos((char *)value.addr);
308 if (!*position)
309 goto fail;
310 }
311 if (getres(&value, rdb, "timeout", "Timeout")) {
312 *timeout = strtol((char *)value.addr, &p, 10);
313 if (*p || *timeout < 0)
314 goto fail;
315 }
316 if (getres(&value, rdb, "program", "Program")) {
317 /* Should be the last :) */
318 program = (char *)value.addr;
319 }
320
321 for (ap = args; ap < &args[9] &&
322 (*ap = strsep(&program, " ")) != NULL;) {
323 if (**ap != '\0')
324 ap++;
325 }
326 *ap = NULL;
327 }
328
329
330 int
main(int argc,char ** argv)331 main(int argc, char **argv)
332 {
333 char *args[10];
334 int area = 2, delay = 2, timeout = 0;
335 int position = north|west;
336 u_long last_serial = 0;
337
338 bzero(&x, sizeof(struct xinfo));
339
340 parse_opts(argc, argv, &x.dpy, &area, &delay, &timeout,
341 &position, args);
342
343 #ifdef DEBUG
344 printf("Area: %d\nDelay: %d\nPosition: %d\nTimeout: %d\n"
345 "Program: \"%s\"\n",
346 area, delay, position, timeout, args[0]);
347 #endif
348
349 init_x(&x, position, area, timeout);
350
351 signal(SIGINT, handler);
352 signal(SIGTERM, handler);
353 signal(SIGUSR1, handler);
354
355 for (;;) {
356 XEvent ev;
357 u_long mask;
358
359 XNextEvent(x.dpy, &ev);
360
361 #ifdef DEBUG
362 printf("got event %d\n", ev.type);
363 #endif
364
365 switch (ev.type) {
366 case VisibilityNotify:
367 /*
368 * If we got here and the current serial matches
369 * the one from saver_event, we're being obscured
370 * by a locking program. Disable further
371 * screen saver events and raises.
372 */
373 if (ev.xvisibility.serial == last_serial)
374 mask = 0;
375 else
376 mask = ScreenSaverNotifyMask;
377
378 XScreenSaverSelectInput(x.dpy,
379 DefaultRootWindow(x.dpy), mask);
380
381 if (mask)
382 XMapRaised(x.dpy, x.win);
383
384 XSync(x.dpy, True);
385 break;
386
387 case MapNotify:
388 XMapRaised(x.dpy, x.win);
389 break;
390
391 case ClientMessage:
392 if (ev.xclient.message_type == XIDLE_DIE) {
393 close_x(&x);
394 exit(0);
395 } else if (ev.xclient.message_type == XIDLE_LOCK)
396 action(&x, args);
397 break;
398
399 case EnterNotify:
400 sleep(delay);
401
402 XQueryPointer(x.dpy, x.win, &ev.xcrossing.root,
403 &ev.xcrossing.window,
404 &ev.xcrossing.x_root, &ev.xcrossing.y_root,
405 &ev.xcrossing.x, &ev.xcrossing.y,
406 &ev.xcrossing.state);
407
408 /* Check it was for real. */
409 if (ev.xcrossing.x_root < x.coord_x ||
410 ev.xcrossing.y_root < x.coord_y ||
411 ev.xcrossing.x_root > x.coord_x + area ||
412 ev.xcrossing.y_root > x.coord_y + area)
413 break;
414 action(&x, args);
415 break;
416
417 default:
418 if (ev.type == x.saver_event) {
419 XScreenSaverNotifyEvent *se =
420 (XScreenSaverNotifyEvent *)&ev;
421
422 /* Take note of the serial for this event. */
423 last_serial = se->serial;
424
425 /*
426 * Was for real or due to terminal
427 * switching or a locking program?
428 */
429 if (timeout > 0 && se->forced == False)
430 action(&x, args);
431 }
432 break;
433 }
434 }
435
436 /* NOTREACHED */
437 }
438