slock.c (15986B)
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/Xinerama.h> 19#include <X11/extensions/dpms.h> 20#include <X11/keysym.h> 21#include <X11/Xlib.h> 22#include <X11/Xutil.h> 23#include <Imlib2.h> 24 25#include "arg.h" 26#include "util.h" 27 28char *argv0; 29 30/* global count to prevent repeated error messages */ 31int count_error = 0; 32 33enum { 34 INIT, 35 INPUT, 36 FAILED, 37 NUMCOLS 38}; 39 40struct lock { 41 int screen; 42 Window root, win; 43 Pixmap pmap; 44 Pixmap bgmap; 45 unsigned long colors[NUMCOLS]; 46}; 47 48struct xrandr { 49 int active; 50 int evbase; 51 int errbase; 52}; 53 54#include "config.h" 55 56Imlib_Image image; 57 58static void 59die(const char *errstr, ...) 60{ 61 va_list ap; 62 63 va_start(ap, errstr); 64 vfprintf(stderr, errstr, ap); 65 va_end(ap); 66 exit(1); 67} 68 69#ifdef __linux__ 70#include <fcntl.h> 71#include <linux/oom.h> 72 73static void 74dontkillme(void) 75{ 76 FILE *f; 77 const char oomfile[] = "/proc/self/oom_score_adj"; 78 79 if (!(f = fopen(oomfile, "w"))) { 80 if (errno == ENOENT) 81 return; 82 die("slock: fopen %s: %s\n", oomfile, strerror(errno)); 83 } 84 fprintf(f, "%d", OOM_SCORE_ADJ_MIN); 85 if (fclose(f)) { 86 if (errno == EACCES) 87 die("slock: unable to disable OOM killer. " 88 "Make sure to suid or sgid slock.\n"); 89 else 90 die("slock: fclose %s: %s\n", oomfile, strerror(errno)); 91 } 92} 93#endif 94 95static void 96writemessage(Display *dpy, Window win, int screen, const char* message) 97{ 98 int msg_len, max_line_len, max_line_width, xoffset, yoffset, screen_width, 99 screen_height, i, j, k, line_count, tab_replace, tab_size, font_height; 100 XGCValues gr_values; 101 XFontStruct *fontinfo; 102 XColor fg_color, bg_color, dummy; 103 XineramaScreenInfo *xsi; 104 GC fg_gc, bg_gc; 105 fontinfo = XLoadQueryFont(dpy, font_name); 106 107 if (fontinfo == NULL) { 108 if (count_error == 0) { 109 fprintf(stderr, "slock: Unable to load font \"%s\"\n", font_name); 110 fprintf(stderr, "slock: Try listing fonts with 'slock -f'\n"); 111 count_error++; 112 } 113 return; 114 } 115 116 tab_size = 4 * XTextWidth(fontinfo, " ", 1); 117 118 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), 119 text_fg_color, &fg_color, &dummy); 120 121 gr_values.font = fontinfo->fid; 122 gr_values.foreground = fg_color.pixel; 123 fg_gc = XCreateGC(dpy, win, GCFont+GCForeground, &gr_values); 124 125 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), 126 text_bg_color, &bg_color, &dummy); 127 128 gr_values.foreground = bg_color.pixel; 129 bg_gc = XCreateGC(dpy, win, GCFont+GCForeground, &gr_values); 130 131 /* To prevent "Uninitialized" warnings. */ 132 xsi = NULL; 133 134 msg_len = strlen(message); 135 136 /* Calculate max line length, j : last line, k : longest line */ 137 max_line_len = 0; 138 line_count = 0; 139 for (i = j = k = 0; i <= msg_len; i++) { 140 if (i == msg_len || message[i] == '\n') { 141 if (i - j > max_line_len) { 142 max_line_len = i - j; 143 k = j; 144 } 145 line_count++; 146 j = i + 1; 147 } 148 } 149 150 max_line_width = XTextWidth(fontinfo, message + k, max_line_len); 151 152 if (XineramaIsActive(dpy)) { 153 xsi = XineramaQueryScreens(dpy, &i); 154 screen_width = xsi[0].width; 155 screen_height = xsi[0].height; 156 } else { 157 screen_width = DisplayWidth(dpy, screen); 158 screen_height = DisplayHeight(dpy, screen); 159 } 160 161 font_height = fontinfo->ascent + fontinfo->descent; 162 yoffset = screen_height * 3 / 7 - line_count * font_height / 3; 163 xoffset = (screen_width - max_line_width) / 2; 164 165 /* Draw background rect */ 166 XFillRectangle(dpy, win, bg_gc, xoffset - 1, yoffset - fontinfo->ascent, 167 max_line_width + 2, line_count * font_height); 168 169 /* Print line by line, j : last line, k : line counter */ 170 for (i = j = k = 0; i <= msg_len; i++) { 171 if (i == msg_len || message[i] == '\n') { 172 tab_replace = 0; 173 while (message[j] == '\t' && j < i) { 174 tab_replace++; 175 j++; 176 } 177 178 XDrawString(dpy, win, fg_gc, xoffset + tab_size * tab_replace, 179 yoffset + 20 * k, message + j, i - j); 180 while (i < msg_len && message[i] == '\n') { 181 i++; 182 j = i; 183 k++; 184 } 185 } 186 } 187 188 /* xsi should not be NULL anyway if Xinerama is active, but to be safe */ 189 if (XineramaIsActive(dpy) && xsi != NULL) 190 XFree(xsi); 191} 192 193 194 195static const char * 196gethash(void) 197{ 198 const char *hash; 199 struct passwd *pw; 200 201 /* Check if the current user has a password entry */ 202 errno = 0; 203 if (!(pw = getpwuid(getuid()))) { 204 if (errno) 205 die("slock: getpwuid: %s\n", strerror(errno)); 206 else 207 die("slock: cannot retrieve password entry\n"); 208 } 209 hash = pw->pw_passwd; 210 211#if HAVE_SHADOW_H 212 if (!strcmp(hash, "x")) { 213 struct spwd *sp; 214 if (!(sp = getspnam(pw->pw_name))) 215 die("slock: getspnam: cannot retrieve shadow entry. " 216 "Make sure to suid or sgid slock.\n"); 217 hash = sp->sp_pwdp; 218 } 219#else 220 if (!strcmp(hash, "*")) { 221#ifdef __OpenBSD__ 222 if (!(pw = getpwuid_shadow(getuid()))) 223 die("slock: getpwnam_shadow: cannot retrieve shadow entry. " 224 "Make sure to suid or sgid slock.\n"); 225 hash = pw->pw_passwd; 226#else 227 die("slock: getpwuid: cannot retrieve shadow entry. " 228 "Make sure to suid or sgid slock.\n"); 229#endif /* __OpenBSD__ */ 230 } 231#endif /* HAVE_SHADOW_H */ 232 233 return hash; 234} 235 236static void 237readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, 238 const char *hash) 239{ 240 XRRScreenChangeNotifyEvent *rre; 241 char buf[32], passwd[256], *inputhash; 242 int num, screen, running, failure, oldc; 243 unsigned int len, color; 244 KeySym ksym; 245 XEvent ev; 246 247 len = 0; 248 running = 1; 249 failure = 0; 250 oldc = INIT; 251 252 while (running && !XNextEvent(dpy, &ev)) { 253 if (ev.type == KeyPress) { 254 explicit_bzero(&buf, sizeof(buf)); 255 num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0); 256 if (IsKeypadKey(ksym)) { 257 if (ksym == XK_KP_Enter) 258 ksym = XK_Return; 259 else if (ksym >= XK_KP_0 && ksym <= XK_KP_9) 260 ksym = (ksym - XK_KP_0) + XK_0; 261 } 262 if (IsFunctionKey(ksym) || 263 IsKeypadKey(ksym) || 264 IsMiscFunctionKey(ksym) || 265 IsPFKey(ksym) || 266 IsPrivateKeypadKey(ksym)) 267 continue; 268 switch (ksym) { 269 case XK_Return: 270 passwd[len] = '\0'; 271 errno = 0; 272 if (!(inputhash = crypt(passwd, hash))) 273 fprintf(stderr, "slock: crypt: %s\n", strerror(errno)); 274 else 275 running = !!strcmp(inputhash, hash); 276 if (running) { 277 XBell(dpy, 100); 278 failure = 1; 279 } 280 explicit_bzero(&passwd, sizeof(passwd)); 281 len = 0; 282 break; 283 case XK_Escape: 284 explicit_bzero(&passwd, sizeof(passwd)); 285 len = 0; 286 break; 287 case XK_BackSpace: 288 if (len) 289 passwd[--len] = '\0'; 290 break; 291 default: 292 if (controlkeyclear && iscntrl((int)buf[0])) 293 continue; 294 if (num && (len + num < sizeof(passwd))) { 295 memcpy(passwd + len, buf, num); 296 len += num; 297 } 298 break; 299 } 300 color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT); 301 if (running) { 302 for (screen = 0; screen < nscreens; screen++) { 303 if(locks[screen]->bgmap) 304 XSetWindowBackgroundPixmap(dpy, locks[screen]->win, locks[screen]->bgmap); 305 else 306 XSetWindowBackground(dpy, locks[screen]->win, locks[screen]->colors[0]); 307 XClearWindow(dpy, locks[screen]->win); 308 if (len) { 309 char* passwd_mask = malloc(len + 1); 310 memset(passwd_mask, '*', len); 311 passwd_mask[len] = '\0'; 312 writemessage(dpy, locks[screen]->win, screen, passwd_mask); 313 free(passwd_mask); 314 } else { 315 writemessage(dpy, locks[screen]->win, screen, lock_message); 316 } 317 } 318 oldc = color; 319 } 320 } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) { 321 rre = (XRRScreenChangeNotifyEvent*)&ev; 322 for (screen = 0; screen < nscreens; screen++) { 323 if (locks[screen]->win == rre->window) { 324 if (rre->rotation == RR_Rotate_90 || 325 rre->rotation == RR_Rotate_270) 326 XResizeWindow(dpy, locks[screen]->win, 327 rre->height, rre->width); 328 else 329 XResizeWindow(dpy, locks[screen]->win, 330 rre->width, rre->height); 331 XClearWindow(dpy, locks[screen]->win); 332 break; 333 } 334 } 335 } else { 336 for (screen = 0; screen < nscreens; screen++) 337 XRaiseWindow(dpy, locks[screen]->win); 338 } 339 } 340} 341 342static struct lock * 343lockscreen(Display *dpy, struct xrandr *rr, int screen) 344{ 345 char curs[] = {0, 0, 0, 0, 0, 0, 0, 0}; 346 int i, ptgrab, kbgrab; 347 struct lock *lock; 348 XColor color, dummy; 349 XSetWindowAttributes wa; 350 Cursor invisible; 351 352 if (dpy == NULL || screen < 0 || !(lock = calloc(1, sizeof(struct lock)))) 353 return NULL; 354 355 lock->screen = screen; 356 lock->root = RootWindow(dpy, lock->screen); 357 358 if(image) 359 { 360 lock->bgmap = XCreatePixmap(dpy, lock->root, DisplayWidth(dpy, lock->screen), DisplayHeight(dpy, lock->screen), DefaultDepth(dpy, lock->screen)); 361 imlib_context_set_image(image); 362 imlib_context_set_display(dpy); 363 imlib_context_set_visual(DefaultVisual(dpy, lock->screen)); 364 imlib_context_set_colormap(DefaultColormap(dpy, lock->screen)); 365 imlib_context_set_drawable(lock->bgmap); 366 imlib_render_image_on_drawable(0, 0); 367 imlib_free_image(); 368 } 369 for (i = 0; i < NUMCOLS; i++) { 370 XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen), 371 colorname[i], &color, &dummy); 372 lock->colors[i] = color.pixel; 373 } 374 375 /* init */ 376 wa.override_redirect = 1; 377 wa.background_pixel = lock->colors[INIT]; 378 lock->win = XCreateWindow(dpy, lock->root, 0, 0, 379 DisplayWidth(dpy, lock->screen), 380 DisplayHeight(dpy, lock->screen), 381 0, DefaultDepth(dpy, lock->screen), 382 CopyFromParent, 383 DefaultVisual(dpy, lock->screen), 384 CWOverrideRedirect | CWBackPixel, &wa); 385 if(lock->bgmap) 386 XSetWindowBackgroundPixmap(dpy, lock->win, lock->bgmap); 387 lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8); 388 invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap, 389 &color, &color, 0, 0); 390 XDefineCursor(dpy, lock->win, invisible); 391 392 /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */ 393 for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) { 394 if (ptgrab != GrabSuccess) { 395 ptgrab = XGrabPointer(dpy, lock->root, False, 396 ButtonPressMask | ButtonReleaseMask | 397 PointerMotionMask, GrabModeAsync, 398 GrabModeAsync, None, invisible, CurrentTime); 399 } 400 if (kbgrab != GrabSuccess) { 401 kbgrab = XGrabKeyboard(dpy, lock->root, True, 402 GrabModeAsync, GrabModeAsync, CurrentTime); 403 } 404 405 /* input is grabbed: we can lock the screen */ 406 if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) { 407 XMapRaised(dpy, lock->win); 408 if (rr->active) 409 XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask); 410 411 XSelectInput(dpy, lock->root, SubstructureNotifyMask); 412 return lock; 413 } 414 415 /* retry on AlreadyGrabbed but fail on other errors */ 416 if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) || 417 (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess)) 418 break; 419 420 usleep(100000); 421 } 422 423 /* we couldn't grab all input: fail out */ 424 if (ptgrab != GrabSuccess) 425 fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n", 426 screen); 427 if (kbgrab != GrabSuccess) 428 fprintf(stderr, "slock: unable to grab keyboard for screen %d\n", 429 screen); 430 return NULL; 431} 432 433static void 434usage(void) 435{ 436 die("usage: slock [-v] [-f] [-m message] [cmd [arg ...]]\n"); 437} 438 439int 440main(int argc, char **argv) { 441 struct xrandr rr; 442 struct lock **locks; 443 struct passwd *pwd; 444 struct group *grp; 445 uid_t duid; 446 gid_t dgid; 447 const char *hash; 448 Display *dpy; 449 int i, s, nlocks, nscreens; 450 int count_fonts; 451 char **font_names; 452 CARD16 standby, suspend, off; 453 454 ARGBEGIN { 455 case 'v': 456 fprintf(stderr, "slock-"VERSION"\n"); 457 return 0; 458 case 'm': 459 lock_message = EARGF(usage()); 460 break; 461 case 'f': 462 if (!(dpy = XOpenDisplay(NULL))) 463 die("slock: cannot open display\n"); 464 font_names = XListFonts(dpy, "*", 10000 /* list 10000 fonts*/, &count_fonts); 465 for (i=0; i<count_fonts; i++) { 466 fprintf(stderr, "%s\n", *(font_names+i)); 467 } 468 return 0; 469 default: 470 usage(); 471 } ARGEND 472 473 /* validate drop-user and -group */ 474 errno = 0; 475 if (!(pwd = getpwnam(user))) 476 die("slock: getpwnam %s: %s\n", user, 477 errno ? strerror(errno) : "user entry not found"); 478 duid = pwd->pw_uid; 479 errno = 0; 480 if (!(grp = getgrnam(group))) 481 die("slock: getgrnam %s: %s\n", group, 482 errno ? strerror(errno) : "group entry not found"); 483 dgid = grp->gr_gid; 484 485#ifdef __linux__ 486 dontkillme(); 487#endif 488 489 hash = gethash(); 490 errno = 0; 491 if (!crypt("", hash)) 492 die("slock: crypt: %s\n", strerror(errno)); 493 494 if (!(dpy = XOpenDisplay(NULL))) 495 die("slock: cannot open display\n"); 496 497 /* drop privileges */ 498 if (setgroups(0, NULL) < 0) 499 die("slock: setgroups: %s\n", strerror(errno)); 500 if (setgid(dgid) < 0) 501 die("slock: setgid: %s\n", strerror(errno)); 502 if (setuid(duid) < 0) 503 die("slock: setuid: %s\n", strerror(errno)); 504 505 /*Create screenshot Image*/ 506 Screen *scr = ScreenOfDisplay(dpy, DefaultScreen(dpy)); 507 image = imlib_create_image(scr->width,scr->height); 508 imlib_context_set_image(image); 509 imlib_context_set_display(dpy); 510 imlib_context_set_visual(DefaultVisual(dpy,0)); 511 imlib_context_set_drawable(RootWindow(dpy,XScreenNumberOfScreen(scr))); 512 imlib_copy_drawable_to_image(0,0,0,scr->width,scr->height,0,0,1); 513 514#ifdef BLUR 515 516 /*Blur function*/ 517 imlib_image_blur(blurRadius); 518#endif // BLUR 519 520#ifdef PIXELATION 521 /*Pixelation*/ 522 int width = scr->width; 523 int height = scr->height; 524 525 for(int y = 0; y < height; y += pixelSize) 526 { 527 for(int x = 0; x < width; x += pixelSize) 528 { 529 int red = 0; 530 int green = 0; 531 int blue = 0; 532 533 Imlib_Color pixel; 534 Imlib_Color* pp; 535 pp = &pixel; 536 for(int j = 0; j < pixelSize && j < height; j++) 537 { 538 for(int i = 0; i < pixelSize && i < width; i++) 539 { 540 imlib_image_query_pixel(x+i,y+j,pp); 541 red += pixel.red; 542 green += pixel.green; 543 blue += pixel.blue; 544 } 545 } 546 red /= (pixelSize*pixelSize); 547 green /= (pixelSize*pixelSize); 548 blue /= (pixelSize*pixelSize); 549 imlib_context_set_color(red,green,blue,pixel.alpha); 550 imlib_image_fill_rectangle(x,y,pixelSize,pixelSize); 551 red = 0; 552 green = 0; 553 blue = 0; 554 } 555 } 556 557 558#endif 559 /* check for Xrandr support */ 560 rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase); 561 562 /* get number of screens in display "dpy" and blank them */ 563 nscreens = ScreenCount(dpy); 564 if (!(locks = calloc(nscreens, sizeof(struct lock *)))) 565 die("slock: out of memory\n"); 566 for (nlocks = 0, s = 0; s < nscreens; s++) { 567 if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL) { 568 writemessage(dpy, locks[s]->win, s, lock_message); 569 nlocks++; 570 } else { 571 break; 572 } 573 } 574 XSync(dpy, 0); 575 576 /* did we manage to lock everything? */ 577 if (nlocks != nscreens) 578 return 1; 579 580 /* DPMS magic to disable the monitor */ 581 if (!DPMSCapable(dpy)) 582 die("slock: DPMSCapable failed\n"); 583 if (!DPMSEnable(dpy)) 584 die("slock: DPMSEnable failed\n"); 585 if (!DPMSGetTimeouts(dpy, &standby, &suspend, &off)) 586 die("slock: DPMSGetTimeouts failed\n"); 587 if (!standby || !suspend || !off) 588 die("slock: at least one DPMS variable is zero\n"); 589 if (!DPMSSetTimeouts(dpy, monitortime, monitortime, monitortime)) 590 die("slock: DPMSSetTimeouts failed\n"); 591 592 XSync(dpy, 0); 593 594 /* run post-lock command */ 595 if (argc > 0) { 596 switch (fork()) { 597 case -1: 598 die("slock: fork failed: %s\n", strerror(errno)); 599 case 0: 600 if (close(ConnectionNumber(dpy)) < 0) 601 die("slock: close: %s\n", strerror(errno)); 602 execvp(argv[0], argv); 603 fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno)); 604 _exit(1); 605 } 606 } 607 608 /* everything is now blank. Wait for the correct password */ 609 readpw(dpy, &rr, locks, nscreens, hash); 610 611 /* reset DPMS values to inital ones */ 612 DPMSSetTimeouts(dpy, standby, suspend, off); 613 XSync(dpy, 0); 614 615 return 0; 616}