slock with DPMS and other tweaks
at jcs 419 lines 10 kB view raw
1/* See LICENSE file for license details. */ 2#define _XOPEN_SOURCE 500 3#if HAVE_SHADOW_H 4#include <shadow.h> 5#endif 6 7#include <ctype.h> 8#include <errno.h> 9#include <grp.h> 10#include <pwd.h> 11#include <stdarg.h> 12#include <stdlib.h> 13#include <stdio.h> 14#include <string.h> 15#include <unistd.h> 16#include <sys/types.h> 17#include <X11/extensions/Xrandr.h> 18#include <X11/extensions/dpms.h> 19#include <X11/keysym.h> 20#include <X11/Xlib.h> 21#include <X11/Xutil.h> 22 23#include "arg.h" 24#include "util.h" 25 26char *argv0; 27 28enum { 29 INIT, 30 INPUT, 31 FAILED, 32 NUMCOLS 33}; 34 35struct lock { 36 int screen; 37 Window root, win; 38 Pixmap pmap; 39 unsigned long colors[NUMCOLS]; 40}; 41 42struct xrandr { 43 int active; 44 int evbase; 45 int errbase; 46}; 47 48#include "config.h" 49 50static void 51die(const char *errstr, ...) 52{ 53 va_list ap; 54 55 va_start(ap, errstr); 56 vfprintf(stderr, errstr, ap); 57 va_end(ap); 58 exit(1); 59} 60 61#ifdef __linux__ 62#include <fcntl.h> 63#include <linux/oom.h> 64 65static void 66dontkillme(void) 67{ 68 FILE *f; 69 const char oomfile[] = "/proc/self/oom_score_adj"; 70 71 if (!(f = fopen(oomfile, "w"))) { 72 if (errno == ENOENT) 73 return; 74 die("slock: fopen %s: %s\n", oomfile, strerror(errno)); 75 } 76 fprintf(f, "%d", OOM_SCORE_ADJ_MIN); 77 if (fclose(f)) { 78 if (errno == EACCES) 79 die("slock: unable to disable OOM killer. " 80 "Make sure to suid or sgid slock.\n"); 81 else 82 die("slock: fclose %s: %s\n", oomfile, strerror(errno)); 83 } 84} 85#endif 86 87static const char * 88gethash(void) 89{ 90 const char *hash; 91 struct passwd *pw; 92 93 /* Check if the current user has a password entry */ 94 errno = 0; 95 if (!(pw = getpwuid(getuid()))) { 96 if (errno) 97 die("slock: getpwuid: %s\n", strerror(errno)); 98 else 99 die("slock: cannot retrieve password entry\n"); 100 } 101 hash = pw->pw_passwd; 102 103#if HAVE_SHADOW_H 104 if (!strcmp(hash, "x")) { 105 struct spwd *sp; 106 if (!(sp = getspnam(pw->pw_name))) 107 die("slock: getspnam: cannot retrieve shadow entry. " 108 "Make sure to suid or sgid slock.\n"); 109 hash = sp->sp_pwdp; 110 } 111#else 112 if (!strcmp(hash, "*")) { 113#ifdef __OpenBSD__ 114 if (!(pw = getpwuid_shadow(getuid()))) 115 die("slock: getpwnam_shadow: cannot retrieve shadow entry. " 116 "Make sure to suid or sgid slock.\n"); 117 hash = pw->pw_passwd; 118#else 119 die("slock: getpwuid: cannot retrieve shadow entry. " 120 "Make sure to suid or sgid slock.\n"); 121#endif /* __OpenBSD__ */ 122 } 123#endif /* HAVE_SHADOW_H */ 124 125 return hash; 126} 127 128static void 129readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, 130 const char *hash) 131{ 132 XRRScreenChangeNotifyEvent *rre; 133 char buf[32], passwd[256], *inputhash; 134 int num, screen, running, failure, oldc; 135 unsigned int len, color; 136 KeySym ksym; 137 XEvent ev; 138 CARD16 dstandby, dsuspend, doff; 139 140 len = 0; 141 running = 1; 142 failure = 0; 143 oldc = INIT; 144 145 /* 146 * if dpms is available, store the current values, set a low timeout, 147 * and then force the screen off. when the user starts typing, dpms 148 * will force the screen on and then after a few seconds of no typing, 149 * the low timeout will force the screen off again. we'll restore the 150 * user's previous timeouts at the end of the loop. 151 */ 152 153 if (DPMSCapable(dpy) && DPMSEnable(dpy)) { 154 /* allow the initial window paint to actually happen */ 155 usleep(100000); 156 157 DPMSGetTimeouts(dpy, &dstandby, &dsuspend, &doff); 158 DPMSSetTimeouts(dpy, 0, 0, dpmstimeout); 159 DPMSForceLevel(dpy, DPMSModeOff); 160 } 161 162 while (running && !XNextEvent(dpy, &ev)) { 163 if (ev.type == KeyPress) { 164 explicit_bzero(&buf, sizeof(buf)); 165 num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0); 166 if (IsKeypadKey(ksym)) { 167 if (ksym == XK_KP_Enter) 168 ksym = XK_Return; 169 else if (ksym >= XK_KP_0 && ksym <= XK_KP_9) 170 ksym = (ksym - XK_KP_0) + XK_0; 171 } 172 if (IsFunctionKey(ksym) || 173 IsKeypadKey(ksym) || 174 IsMiscFunctionKey(ksym) || 175 IsPFKey(ksym) || 176 IsPrivateKeypadKey(ksym)) 177 continue; 178 switch (ksym) { 179 case XK_Return: 180 passwd[len] = '\0'; 181 errno = 0; 182 if (!(inputhash = crypt(passwd, hash))) 183 fprintf(stderr, "slock: crypt: %s\n", strerror(errno)); 184 else 185 running = !!strcmp(inputhash, hash); 186 if (running) { 187 XBell(dpy, 100); 188 failure = 1; 189 } 190 explicit_bzero(&passwd, sizeof(passwd)); 191 len = 0; 192 break; 193 case XK_Escape: 194 explicit_bzero(&passwd, sizeof(passwd)); 195 len = 0; 196 break; 197 case XK_BackSpace: 198 if (len) 199 passwd[--len] = '\0'; 200 break; 201 default: 202 if (num && !iscntrl((int)buf[0]) && 203 (len + num < sizeof(passwd))) { 204 memcpy(passwd + len, buf, num); 205 len += num; 206 } 207 break; 208 } 209 color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT); 210 if (running && oldc != color) { 211 for (screen = 0; screen < nscreens; screen++) { 212 XSetWindowBackground(dpy, 213 locks[screen]->win, 214 locks[screen]->colors[color]); 215 XClearWindow(dpy, locks[screen]->win); 216 } 217 oldc = color; 218 } 219 } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) { 220 rre = (XRRScreenChangeNotifyEvent*)&ev; 221 for (screen = 0; screen < nscreens; screen++) { 222 if (locks[screen]->win == rre->window) { 223 if (rre->rotation == RR_Rotate_90 || 224 rre->rotation == RR_Rotate_270) 225 XResizeWindow(dpy, locks[screen]->win, 226 rre->height, rre->width); 227 else 228 XResizeWindow(dpy, locks[screen]->win, 229 rre->width, rre->height); 230 XClearWindow(dpy, locks[screen]->win); 231 break; 232 } 233 } 234 } else { 235 for (screen = 0; screen < nscreens; screen++) 236 XRaiseWindow(dpy, locks[screen]->win); 237 } 238 } 239 240 if (DPMSCapable(dpy) && DPMSEnable(dpy)) { 241 DPMSSetTimeouts(dpy, dstandby, dsuspend, doff); 242 XFlush(dpy); 243 } 244} 245 246static struct lock * 247lockscreen(Display *dpy, struct xrandr *rr, int screen) 248{ 249 char curs[] = {0, 0, 0, 0, 0, 0, 0, 0}; 250 int i, ptgrab, kbgrab; 251 struct lock *lock; 252 XColor color, dummy; 253 XSetWindowAttributes wa; 254 Cursor invisible; 255 256 if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock)))) 257 return NULL; 258 259 lock->screen = screen; 260 lock->root = RootWindow(dpy, lock->screen); 261 262 for (i = 0; i < NUMCOLS; i++) { 263 XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen), 264 colorname[i], &color, &dummy); 265 lock->colors[i] = color.pixel; 266 } 267 268 /* init */ 269 wa.override_redirect = 1; 270 wa.background_pixel = lock->colors[INIT]; 271 lock->win = XCreateWindow(dpy, lock->root, 0, 0, 272 DisplayWidth(dpy, lock->screen), 273 DisplayHeight(dpy, lock->screen), 274 0, DefaultDepth(dpy, lock->screen), 275 CopyFromParent, 276 DefaultVisual(dpy, lock->screen), 277 CWOverrideRedirect | CWBackPixel, &wa); 278 lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8); 279 invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap, 280 &color, &color, 0, 0); 281 XDefineCursor(dpy, lock->win, invisible); 282 283 /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */ 284 for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) { 285 if (ptgrab != GrabSuccess) { 286 ptgrab = XGrabPointer(dpy, lock->root, False, 287 ButtonPressMask | ButtonReleaseMask | 288 PointerMotionMask, GrabModeAsync, 289 GrabModeAsync, None, invisible, CurrentTime); 290 } 291 if (kbgrab != GrabSuccess) { 292 kbgrab = XGrabKeyboard(dpy, lock->root, True, 293 GrabModeAsync, GrabModeAsync, CurrentTime); 294 } 295 296 /* input is grabbed: we can lock the screen */ 297 if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) { 298 XMapRaised(dpy, lock->win); 299 if (rr->active) 300 XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask); 301 302 XSelectInput(dpy, lock->root, SubstructureNotifyMask); 303 return lock; 304 } 305 306 /* retry on AlreadyGrabbed but fail on other errors */ 307 if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) || 308 (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess)) 309 break; 310 311 usleep(100000); 312 } 313 314 /* we couldn't grab all input: fail out */ 315 if (ptgrab != GrabSuccess) 316 fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n", 317 screen); 318 if (kbgrab != GrabSuccess) 319 fprintf(stderr, "slock: unable to grab keyboard for screen %d\n", 320 screen); 321 return NULL; 322} 323 324static void 325usage(void) 326{ 327 die("usage: slock [-v] [cmd [arg ...]]\n"); 328} 329 330int 331main(int argc, char **argv) { 332 struct xrandr rr; 333 struct lock **locks; 334 struct passwd *pwd; 335 struct group *grp; 336 uid_t duid; 337 gid_t dgid; 338 const char *hash; 339 Display *dpy; 340 int s, nlocks, nscreens; 341 342 ARGBEGIN { 343 case 'v': 344 fprintf(stderr, "slock-"VERSION"\n"); 345 return 0; 346 default: 347 usage(); 348 } ARGEND 349 350 /* validate drop-user and -group */ 351 errno = 0; 352 if (!(pwd = getpwnam(user))) 353 die("slock: getpwnam %s: %s\n", user, 354 errno ? strerror(errno) : "user entry not found"); 355 duid = pwd->pw_uid; 356 errno = 0; 357 if (!(grp = getgrnam(group))) 358 die("slock: getgrnam %s: %s\n", group, 359 errno ? strerror(errno) : "group entry not found"); 360 dgid = grp->gr_gid; 361 362#ifdef __linux__ 363 dontkillme(); 364#endif 365 366 hash = gethash(); 367 errno = 0; 368 if (!crypt("", hash)) 369 die("slock: crypt: %s\n", strerror(errno)); 370 371 if (!(dpy = XOpenDisplay(NULL))) 372 die("slock: cannot open display\n"); 373 374 /* drop privileges */ 375 if (setgroups(0, NULL) < 0) 376 die("slock: setgroups: %s\n", strerror(errno)); 377 if (setgid(dgid) < 0) 378 die("slock: setgid: %s\n", strerror(errno)); 379 if (setuid(duid) < 0) 380 die("slock: setuid: %s\n", strerror(errno)); 381 382 /* check for Xrandr support */ 383 rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase); 384 385 /* get number of screens in display "dpy" and blank them */ 386 nscreens = ScreenCount(dpy); 387 if (!(locks = calloc(nscreens, sizeof(struct lock *)))) 388 die("slock: out of memory\n"); 389 for (nlocks = 0, s = 0; s < nscreens; s++) { 390 if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL) 391 nlocks++; 392 else 393 break; 394 } 395 XSync(dpy, 0); 396 397 /* did we manage to lock everything? */ 398 if (nlocks != nscreens) 399 return 1; 400 401 /* run post-lock command */ 402 if (argc > 0) { 403 switch (fork()) { 404 case -1: 405 die("slock: fork failed: %s\n", strerror(errno)); 406 case 0: 407 if (close(ConnectionNumber(dpy)) < 0) 408 die("slock: close: %s\n", strerror(errno)); 409 execvp(argv[0], argv); 410 fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno)); 411 _exit(1); 412 } 413 } 414 415 /* everything is now blank. Wait for the correct password */ 416 readpw(dpy, &rr, locks, nscreens, hash); 417 418 return 0; 419}