slock

Simple X display locker (fork)
git clone https://git.sinitax.com/sinitax/slock
Log | Files | Refs | README | LICENSE | Upstream | sfeed.txt

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}