Xinqi Bao's Git

patch: colormessage
[slock.git] / slock.c
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/keysym.h>
20 #include <X11/Xlib.h>
21 #include <X11/Xutil.h>
22
23 #include "arg.h"
24 #include "util.h"
25
26 char *argv0;
27
28 /* global count to prevent repeated error messages */
29 int count_error = 0;
30
31 enum {
32 INIT,
33 INPUT,
34 FAILED,
35 NUMCOLS
36 };
37
38 struct lock {
39 int screen;
40 Window root, win;
41 Pixmap pmap;
42 unsigned long colors[NUMCOLS];
43 };
44
45 struct xrandr {
46 int active;
47 int evbase;
48 int errbase;
49 };
50
51 #include "config.h"
52
53 static void
54 die(const char *errstr, ...)
55 {
56 va_list ap;
57
58 va_start(ap, errstr);
59 vfprintf(stderr, errstr, ap);
60 va_end(ap);
61 exit(1);
62 }
63
64 #ifdef __linux__
65 #include <fcntl.h>
66 #include <linux/oom.h>
67
68 static void
69 dontkillme(void)
70 {
71 FILE *f;
72 const char oomfile[] = "/proc/self/oom_score_adj";
73
74 if (!(f = fopen(oomfile, "w"))) {
75 if (errno == ENOENT)
76 return;
77 die("slock: fopen %s: %s\n", oomfile, strerror(errno));
78 }
79 fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
80 if (fclose(f)) {
81 if (errno == EACCES)
82 die("slock: unable to disable OOM killer. "
83 "Make sure to suid or sgid slock.\n");
84 else
85 die("slock: fclose %s: %s\n", oomfile, strerror(errno));
86 }
87 }
88 #endif
89
90 static int
91 readescapedint(const char *str, int *i) {
92 int n = 0;
93 if (str[*i])
94 ++*i;
95 while(str[*i] && str[*i] != ';' && str[*i] != 'm') {
96 n = 10 * n + str[*i] - '0';
97 ++*i;
98 }
99 return n;
100 }
101
102 static void
103 writemessage(Display *dpy, Window win, int screen)
104 {
105 int len, line_len, width, height, s_width, s_height, i, k, tab_size, r, g, b, escaped_int, curr_line_len;
106 XGCValues gr_values;
107 XFontStruct *fontinfo;
108 XColor color, dummy;
109 XineramaScreenInfo *xsi;
110 GC gc;
111 fontinfo = XLoadQueryFont(dpy, font_name);
112
113 if (fontinfo == NULL) {
114 if (count_error == 0) {
115 fprintf(stderr, "slock: Unable to load font \"%s\"\n", font_name);
116 fprintf(stderr, "slock: Try listing fonts with 'slock -f'\n");
117 count_error++;
118 }
119 return;
120 }
121
122 tab_size = 8 * XTextWidth(fontinfo, " ", 1);
123
124 XAllocNamedColor(dpy, DefaultColormap(dpy, screen),
125 text_color, &color, &dummy);
126
127 gr_values.font = fontinfo->fid;
128 gr_values.foreground = color.pixel;
129 gc=XCreateGC(dpy,win,GCFont+GCForeground, &gr_values);
130
131 /* To prevent "Uninitialized" warnings. */
132 xsi = NULL;
133
134 /*
135 * Start formatting and drawing text
136 */
137
138 len = strlen(message);
139
140 /* Max max line length (cut at '\n') */
141 line_len = curr_line_len = 0;
142 k = 0;
143 for (i = 0; i < len; i++) {
144 if (message[i] == '\n') {
145 curr_line_len = 0;
146 k++;
147 } else if (message[i] == 0x1b) {
148 while (i < len && message[i] != 'm') {
149 i++;
150 }
151 if (i == len)
152 die("slock: unclosed escape sequence\n");
153 } else {
154 curr_line_len += XTextWidth(fontinfo, message + i, 1);
155 if (curr_line_len > line_len)
156 line_len = curr_line_len;
157 }
158 }
159 /* If there is only one line */
160 if (line_len == 0)
161 line_len = len;
162
163 if (XineramaIsActive(dpy)) {
164 xsi = XineramaQueryScreens(dpy, &i);
165 s_width = xsi[0].width;
166 s_height = xsi[0].height;
167 } else {
168 s_width = DisplayWidth(dpy, screen);
169 s_height = DisplayHeight(dpy, screen);
170 }
171 height = s_height*3/7 - (k*20)/3;
172 width = (s_width - line_len)/2;
173
174 line_len = 0;
175 /* print the text while parsing 24 bit color ANSI escape codes*/
176 for (i = k = 0; i < len; i++) {
177 switch (message[i]) {
178 case '\n':
179 line_len = 0;
180 while (message[i + 1] == '\t') {
181 line_len += tab_size;
182 i++;
183 }
184 k++;
185 break;
186 case 0x1b:
187 i++;
188 if (message[i] == '[') {
189 escaped_int = readescapedint(message, &i);
190 if (escaped_int == 39)
191 continue;
192 if (escaped_int != 38)
193 die("slock: unknown escape sequence%d\n", escaped_int);
194 if (readescapedint(message, &i) != 2)
195 die("slock: only 24 bit color supported\n");
196 r = readescapedint(message, &i) & 0xff;
197 g = readescapedint(message, &i) & 0xff;
198 b = readescapedint(message, &i) & 0xff;
199 XSetForeground(dpy, gc, r << 16 | g << 8 | b);
200 } else
201 die("slock: unknown escape sequence\n");
202 break;
203 default:
204 XDrawString(dpy, win, gc, width + line_len, height + 20 * k, message + i, 1);
205 line_len += XTextWidth(fontinfo, message + i, 1);
206 }
207 }
208
209 /* xsi should not be NULL anyway if Xinerama is active, but to be safe */
210 if (XineramaIsActive(dpy) && xsi != NULL)
211 XFree(xsi);
212 }
213
214
215
216 static const char *
217 gethash(void)
218 {
219 const char *hash;
220 struct passwd *pw;
221
222 /* Check if the current user has a password entry */
223 errno = 0;
224 if (!(pw = getpwuid(getuid()))) {
225 if (errno)
226 die("slock: getpwuid: %s\n", strerror(errno));
227 else
228 die("slock: cannot retrieve password entry\n");
229 }
230 hash = pw->pw_passwd;
231
232 #if HAVE_SHADOW_H
233 if (!strcmp(hash, "x")) {
234 struct spwd *sp;
235 if (!(sp = getspnam(pw->pw_name)))
236 die("slock: getspnam: cannot retrieve shadow entry. "
237 "Make sure to suid or sgid slock.\n");
238 hash = sp->sp_pwdp;
239 }
240 #else
241 if (!strcmp(hash, "*")) {
242 #ifdef __OpenBSD__
243 if (!(pw = getpwuid_shadow(getuid())))
244 die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
245 "Make sure to suid or sgid slock.\n");
246 hash = pw->pw_passwd;
247 #else
248 die("slock: getpwuid: cannot retrieve shadow entry. "
249 "Make sure to suid or sgid slock.\n");
250 #endif /* __OpenBSD__ */
251 }
252 #endif /* HAVE_SHADOW_H */
253
254 return hash;
255 }
256
257 static void
258 readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens,
259 const char *hash)
260 {
261 XRRScreenChangeNotifyEvent *rre;
262 char buf[32], passwd[256], *inputhash;
263 int num, screen, running, failure, oldc;
264 unsigned int len, color;
265 KeySym ksym;
266 XEvent ev;
267
268 len = 0;
269 running = 1;
270 failure = 0;
271 oldc = INIT;
272
273 while (running && !XNextEvent(dpy, &ev)) {
274 if (ev.type == KeyPress) {
275 explicit_bzero(&buf, sizeof(buf));
276 num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
277 if (IsKeypadKey(ksym)) {
278 if (ksym == XK_KP_Enter)
279 ksym = XK_Return;
280 else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
281 ksym = (ksym - XK_KP_0) + XK_0;
282 }
283 if (IsFunctionKey(ksym) ||
284 IsKeypadKey(ksym) ||
285 IsMiscFunctionKey(ksym) ||
286 IsPFKey(ksym) ||
287 IsPrivateKeypadKey(ksym))
288 continue;
289 switch (ksym) {
290 case XK_Return:
291 passwd[len] = '\0';
292 errno = 0;
293 if (!(inputhash = crypt(passwd, hash)))
294 fprintf(stderr, "slock: crypt: %s\n", strerror(errno));
295 else
296 running = !!strcmp(inputhash, hash);
297 if (running) {
298 XBell(dpy, 100);
299 failure = 1;
300 }
301 explicit_bzero(&passwd, sizeof(passwd));
302 len = 0;
303 break;
304 case XK_Escape:
305 explicit_bzero(&passwd, sizeof(passwd));
306 len = 0;
307 break;
308 case XK_BackSpace:
309 if (len)
310 passwd[--len] = '\0';
311 break;
312 default:
313 if (num && !iscntrl((int)buf[0]) &&
314 (len + num < sizeof(passwd))) {
315 memcpy(passwd + len, buf, num);
316 len += num;
317 }
318 break;
319 }
320 color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT);
321 if (running && oldc != color) {
322 for (screen = 0; screen < nscreens; screen++) {
323 XSetWindowBackground(dpy,
324 locks[screen]->win,
325 locks[screen]->colors[color]);
326 XClearWindow(dpy, locks[screen]->win);
327 writemessage(dpy, locks[screen]->win, screen);
328 }
329 oldc = color;
330 }
331 } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
332 rre = (XRRScreenChangeNotifyEvent*)&ev;
333 for (screen = 0; screen < nscreens; screen++) {
334 if (locks[screen]->win == rre->window) {
335 if (rre->rotation == RR_Rotate_90 ||
336 rre->rotation == RR_Rotate_270)
337 XResizeWindow(dpy, locks[screen]->win,
338 rre->height, rre->width);
339 else
340 XResizeWindow(dpy, locks[screen]->win,
341 rre->width, rre->height);
342 XClearWindow(dpy, locks[screen]->win);
343 break;
344 }
345 }
346 } else {
347 for (screen = 0; screen < nscreens; screen++)
348 XRaiseWindow(dpy, locks[screen]->win);
349 }
350 }
351 }
352
353 static struct lock *
354 lockscreen(Display *dpy, struct xrandr *rr, int screen)
355 {
356 char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
357 int i, ptgrab, kbgrab;
358 struct lock *lock;
359 XColor color, dummy;
360 XSetWindowAttributes wa;
361 Cursor invisible;
362
363 if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
364 return NULL;
365
366 lock->screen = screen;
367 lock->root = RootWindow(dpy, lock->screen);
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 lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
386 invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
387 &color, &color, 0, 0);
388 XDefineCursor(dpy, lock->win, invisible);
389
390 /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
391 for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
392 if (ptgrab != GrabSuccess) {
393 ptgrab = XGrabPointer(dpy, lock->root, False,
394 ButtonPressMask | ButtonReleaseMask |
395 PointerMotionMask, GrabModeAsync,
396 GrabModeAsync, None, invisible, CurrentTime);
397 }
398 if (kbgrab != GrabSuccess) {
399 kbgrab = XGrabKeyboard(dpy, lock->root, True,
400 GrabModeAsync, GrabModeAsync, CurrentTime);
401 }
402
403 /* input is grabbed: we can lock the screen */
404 if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
405 XMapRaised(dpy, lock->win);
406 if (rr->active)
407 XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
408
409 XSelectInput(dpy, lock->root, SubstructureNotifyMask);
410 return lock;
411 }
412
413 /* retry on AlreadyGrabbed but fail on other errors */
414 if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
415 (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
416 break;
417
418 usleep(100000);
419 }
420
421 /* we couldn't grab all input: fail out */
422 if (ptgrab != GrabSuccess)
423 fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
424 screen);
425 if (kbgrab != GrabSuccess)
426 fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
427 screen);
428 return NULL;
429 }
430
431 static void
432 usage(void)
433 {
434 die("usage: slock [-v] [-f] [-m message] [cmd [arg ...]]\n");
435 }
436
437 int
438 main(int argc, char **argv) {
439 struct xrandr rr;
440 struct lock **locks;
441 struct passwd *pwd;
442 struct group *grp;
443 uid_t duid;
444 gid_t dgid;
445 const char *hash;
446 Display *dpy;
447 int i, s, nlocks, nscreens;
448 int count_fonts;
449 char **font_names;
450
451 ARGBEGIN {
452 case 'v':
453 fprintf(stderr, "slock-"VERSION"\n");
454 return 0;
455 case 'm':
456 message = EARGF(usage());
457 break;
458 case 'f':
459 if (!(dpy = XOpenDisplay(NULL)))
460 die("slock: cannot open display\n");
461 font_names = XListFonts(dpy, "*", 10000 /* list 10000 fonts*/, &count_fonts);
462 for (i=0; i<count_fonts; i++) {
463 fprintf(stderr, "%s\n", *(font_names+i));
464 }
465 return 0;
466 default:
467 usage();
468 } ARGEND
469
470 /* validate drop-user and -group */
471 errno = 0;
472 if (!(pwd = getpwnam(user)))
473 die("slock: getpwnam %s: %s\n", user,
474 errno ? strerror(errno) : "user entry not found");
475 duid = pwd->pw_uid;
476 errno = 0;
477 if (!(grp = getgrnam(group)))
478 die("slock: getgrnam %s: %s\n", group,
479 errno ? strerror(errno) : "group entry not found");
480 dgid = grp->gr_gid;
481
482 #ifdef __linux__
483 dontkillme();
484 #endif
485
486 hash = gethash();
487 errno = 0;
488 if (!crypt("", hash))
489 die("slock: crypt: %s\n", strerror(errno));
490
491 if (!(dpy = XOpenDisplay(NULL)))
492 die("slock: cannot open display\n");
493
494 /* drop privileges */
495 if (setgroups(0, NULL) < 0)
496 die("slock: setgroups: %s\n", strerror(errno));
497 if (setgid(dgid) < 0)
498 die("slock: setgid: %s\n", strerror(errno));
499 if (setuid(duid) < 0)
500 die("slock: setuid: %s\n", strerror(errno));
501
502 /* check for Xrandr support */
503 rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
504
505 /* get number of screens in display "dpy" and blank them */
506 nscreens = ScreenCount(dpy);
507 if (!(locks = calloc(nscreens, sizeof(struct lock *))))
508 die("slock: out of memory\n");
509 for (nlocks = 0, s = 0; s < nscreens; s++) {
510 if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL) {
511 writemessage(dpy, locks[s]->win, s);
512 nlocks++;
513 } else {
514 break;
515 }
516 }
517 XSync(dpy, 0);
518
519 /* did we manage to lock everything? */
520 if (nlocks != nscreens)
521 return 1;
522
523 /* run post-lock command */
524 if (argc > 0) {
525 switch (fork()) {
526 case -1:
527 die("slock: fork failed: %s\n", strerror(errno));
528 case 0:
529 if (close(ConnectionNumber(dpy)) < 0)
530 die("slock: close: %s\n", strerror(errno));
531 execvp(argv[0], argv);
532 fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno));
533 _exit(1);
534 }
535 }
536
537 /* everything is now blank. Wait for the correct password */
538 readpw(dpy, &rr, locks, nscreens, hash);
539
540 return 0;
541 }