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