/* $OpenBSD: xidle.c,v 1.7 2018/11/11 16:01:21 kn Exp $ */ /* * Copyright (c) 2005 Federico G. Schwindt * Copyright (c) 2005 Claudio Castiglia * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef CLASS_NAME #define CLASS_NAME "XIdle" #endif #ifndef PATH_PROG #define PATH_PROG "/usr/X11R6/bin/xlock" #endif enum { north = 0x01, south = 0x02, east = 0x04, west = 0x08, none = 0x10, }; enum { XIDLE_LOCK = 1, XIDLE_DIE = 2 }; struct xinfo { Display *dpy; Window win; int coord_x; int coord_y; int saver_event; /* Only if Xss ext is available */ int saved_timeout; int saved_interval; int saved_pref_blank; int saved_allow_exp; }; struct xinfo x; static XrmOptionDescRec fopts[] = { { "-display", ".display", XrmoptionSepArg, (caddr_t)NULL }, }; static XrmOptionDescRec opts[] = { { "-area", ".area", XrmoptionSepArg, (caddr_t)NULL }, { "-delay", ".delay", XrmoptionSepArg, (caddr_t)NULL }, { "-program", ".program", XrmoptionSepArg, (caddr_t)NULL }, { "-timeout", ".timeout", XrmoptionSepArg, (caddr_t)NULL }, { "-no", ".position", XrmoptionNoArg, (caddr_t)"no" }, { "-ne", ".position", XrmoptionNoArg, (caddr_t)"ne" }, { "-nw", ".position", XrmoptionNoArg, (caddr_t)"nw" }, { "-se", ".position", XrmoptionNoArg, (caddr_t)"se" }, { "-sw", ".position", XrmoptionNoArg, (caddr_t)"sw" } }; extern char *__progname; void action(struct xinfo *, char **); void close_x(struct xinfo *); Bool getres(XrmValue *, const XrmDatabase, const char *, const char *); void init_x(struct xinfo *, int, int, int); void handler(int); void parse_opts(int, char **, Display **, int *, int *, int *, int *, char **); int str2pos(const char *); __dead void usage(void); __dead void usage() { fprintf(stderr, "Usage:\n%s %s\n", __progname, "[-area pixels] [-delay secs] [-display host:dpy] " "[-no | -ne | -nw | -se | -sw]\n [-program path] [-timeout secs]"); exit(1); } void init_x(struct xinfo *xi, int position, int area, int timeout) { XSetWindowAttributes attr; Display *dpy = xi->dpy; int error, event; int screen; screen = DefaultScreen(dpy); if (position & south) xi->coord_y = DisplayHeight(dpy, screen) - area; if (position & east) xi->coord_x = DisplayWidth(dpy, screen) - area; attr.override_redirect = True; xi->win = XCreateWindow(dpy, DefaultRootWindow(dpy), xi->coord_x, xi->coord_y, area, area, 0, 0, InputOnly, CopyFromParent, CWOverrideRedirect, &attr); if (position != none) { XSelectInput(dpy, xi->win, EnterWindowMask|StructureNotifyMask #if 0 |VisibilityChangeMask #endif ); XMapWindow(dpy, xi->win); } /* * AFAICT, we need the event number for ScreenSaverNotify * _always_ since it's the only way to distinguish whether * we've been obscured by an external locking program or * by another window and react according. */ if (XScreenSaverQueryExtension(dpy, &event, &error) == True) { xi->saver_event = event; XScreenSaverSelectInput(dpy, DefaultRootWindow(dpy), ScreenSaverNotifyMask); } else warnx("XScreenSaver extension not available.%s", timeout > 0 ? " Timeout disabled." : ""); if (timeout > 0 && xi->saver_event) { XGetScreenSaver(dpy, &xi->saved_timeout, &xi->saved_interval, &xi->saved_pref_blank, &xi->saved_allow_exp); XSetScreenSaver(dpy, timeout, 0, DontPreferBlanking, DontAllowExposures); } } void close_x(struct xinfo *xi) { XSetScreenSaver(xi->dpy, xi->saved_timeout, xi->saved_interval, xi->saved_pref_blank, xi->saved_allow_exp); XDestroyWindow(xi->dpy, xi->win); XCloseDisplay(xi->dpy); } void action(struct xinfo *xi, char **args) { int dumb; switch (fork()) { case -1: err(1, "fork"); /* NOTREACHED */ case 0: setsid(); execv(*args, args); exit(1); /* NOTREACHED */ default: wait(&dumb); XSync(xi->dpy, True); break; } } void handler(int sig) { XClientMessageEvent ev; ev.type = ClientMessage; ev.display = x.dpy; ev.window = x.win; if (sig == SIGUSR1) ev.message_type = XIDLE_LOCK; else ev.message_type = XIDLE_DIE; ev.format = 8; XSendEvent(x.dpy, x.win, False, 0L, (XEvent *)&ev); XFlush(x.dpy); } int str2pos(const char *src) { static struct { char *str; int pos; } s2p[] = { { "ne", north|east }, { "nw", north|west }, { "se", south|east }, { "sw", south|west }, { "no", none }, { NULL, 0 } }, *s; for (s = s2p; s->str != NULL; s++) if (!strcmp(src, s->str)) break; return (s->pos); } Bool getres(XrmValue *value, const XrmDatabase rdb, const char *rname, const char *cname) { char fullres[PATH_MAX], fullclass[PATH_MAX], *type; snprintf(fullres, sizeof(fullres), "%s.%s", __progname, rname); snprintf(fullclass, sizeof(fullclass), "%s.%s", CLASS_NAME, cname); return (XrmGetResource(rdb, fullres, fullclass, &type, value)); } void parse_opts(int argc, char **argv, Display **dpy, int *area, int *delay, int *timeout, int *position, char **args) { char **ap, *program = PATH_PROG; char *display, *p; XrmDatabase tdb, rdb = NULL; XrmValue value; XrmInitialize(); /* Get display to open. */ XrmParseCommand(&rdb, fopts, sizeof(fopts) / sizeof(fopts[0]), __progname, &argc, argv); display = (getres(&value, rdb, "display", "Display") == True) ? (char *)value.addr : NULL; *dpy = XOpenDisplay(display); if (!*dpy) { errx(1, "Unable to open display %s", XDisplayName(display)); /* NOTREACHED */ } /* Get server resources database. */ p = XResourceManagerString(*dpy); if (!p) { /* Get screen resources database. */ p = XScreenResourceString(ScreenOfDisplay(*dpy, DefaultScreen(*dpy))); } if (p) { tdb = XrmGetStringDatabase(p); XrmMergeDatabases(tdb, &rdb); } /* Get remaining command line values. */ XrmParseCommand(&rdb, opts, sizeof(opts) / sizeof(opts[0]), __progname, &argc, argv); if (argc > 1) { usage(); /* NOTREACHED */ } if (getres(&value, rdb, "area", "Area")) { *area = strtol((char *)value.addr, &p, 10); if (*p || *area < 1) { fail: errx(1, "illegal value -- %s", (char *)value.addr); /* NOTREACHED */ } } if (getres(&value, rdb, "delay", "Delay")) { *delay = strtol((char *)value.addr, &p, 10); if (*p || *delay < 0) goto fail; } if (getres(&value, rdb, "position", "Position")) { *position = str2pos((char *)value.addr); if (!*position) goto fail; } if (getres(&value, rdb, "timeout", "Timeout")) { *timeout = strtol((char *)value.addr, &p, 10); if (*p || *timeout < 0) goto fail; } if (getres(&value, rdb, "program", "Program")) { /* Should be the last :) */ program = (char *)value.addr; } for (ap = args; ap < &args[9] && (*ap = strsep(&program, " ")) != NULL;) { if (**ap != '\0') ap++; } *ap = NULL; } int main(int argc, char **argv) { char *args[10]; int area = 2, delay = 2, timeout = 0; int position = north|west; int fd; u_long last_serial = 0; parse_opts(argc, argv, &x.dpy, &area, &delay, &timeout, &position, args); #ifdef DEBUG printf("Area: %d\nDelay: %d\nPosition: %d\nTimeout: %d\n" "Program: \"%s\"\n", area, delay, position, timeout, args[0]); #endif init_x(&x, position, area, timeout); signal(SIGINT, handler); signal(SIGTERM, handler); signal(SIGUSR1, handler); fd = open(_PATH_DEVNULL, O_RDWR, 0); if (fd < 0) err(1, _PATH_DEVNULL); dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > 2) close(fd); if (pledge("stdio proc exec", NULL) == -1) err(1, "pledge"); for (;;) { XEvent ev; u_long mask; XNextEvent(x.dpy, &ev); #ifdef DEBUG printf("got event %d\n", ev.type); #endif switch (ev.type) { case VisibilityNotify: /* * If we got here and the current serial matches * the one from saver_event, we're being obscured * by a locking program. Disable further * screen saver events and raises. */ if (ev.xvisibility.serial == last_serial) mask = 0; else mask = ScreenSaverNotifyMask; XScreenSaverSelectInput(x.dpy, DefaultRootWindow(x.dpy), mask); if (mask) XMapRaised(x.dpy, x.win); XSync(x.dpy, True); break; case MapNotify: XMapRaised(x.dpy, x.win); break; case ClientMessage: if (ev.xclient.message_type == XIDLE_DIE) { close_x(&x); exit(0); } else if (ev.xclient.message_type == XIDLE_LOCK) action(&x, args); break; case EnterNotify: sleep(delay); XQueryPointer(x.dpy, x.win, &ev.xcrossing.root, &ev.xcrossing.window, &ev.xcrossing.x_root, &ev.xcrossing.y_root, &ev.xcrossing.x, &ev.xcrossing.y, &ev.xcrossing.state); /* Check it was for real. */ if (ev.xcrossing.x_root < x.coord_x || ev.xcrossing.y_root < x.coord_y || ev.xcrossing.x_root > x.coord_x + area || ev.xcrossing.y_root > x.coord_y + area) break; action(&x, args); break; default: if (ev.type == x.saver_event) { XScreenSaverNotifyEvent *se = (XScreenSaverNotifyEvent *)&ev; /* Take note of the serial for this event. */ last_serial = se->serial; /* * Was for real or due to terminal * switching or a locking program? */ if (timeout > 0 && se->forced == False) action(&x, args); } break; } } /* NOTREACHED */ }