slock

simple X display locker utility

git clone https://9o.is/git/slock.git

commit 7afc5518aa5a0f912947afa32b3c30f621a3be63
parent 36d211ccf95e48fdaf376740e19ee4a2e985df3f
Author: Jul <jul@9o.is>
Date:   Thu,  5 Feb 2026 05:57:57 -0500

prevent unlocking until timer expires with -t flag

Diffstat:
Mconfig.def.h | 4++++
Mconfig.h | 4++++
Mconfig.mk | 5+++--
Mslock.1 | 10++++++++++
Mslock.c | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -10,3 +10,7 @@ static const char *colorname[NUMCOLS] = { /* treat a cleared input like a wrong password (color) */ static const int failonclear = 1; + +static const char *font = "monospace-16"; +static const char *fontcolor = "white"; +static const char *timer_message = "Locked:"; diff --git a/config.h b/config.h @@ -10,3 +10,7 @@ static const char *colorname[NUMCOLS] = { /* treat a cleared input like a wrong password (color) */ static const int failonclear = 1; + +static const char *font = "Fira Mono-18"; +static const char *fontcolor = "#E3E3E3"; +static const char *timer_message = "LOCKED!"; diff --git a/config.mk b/config.mk @@ -9,10 +9,11 @@ MANPREFIX = ${PREFIX}/share/man X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib +FT2INC = /usr/include/freetype2 # includes and libs -INCS = -I. -I/usr/include -I${X11INC} -LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lXext -lXrandr +INCS = -I. -I/usr/include -I${X11INC} -I${FT2INC} +LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lXext -lXrandr -lXft # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H diff --git a/slock.1 b/slock.1 @@ -7,6 +7,7 @@ .Sh SYNOPSIS .Nm .Op Fl v +.Op Fl t Ar seconds .Op Ar cmd Op Ar arg ... .Sh DESCRIPTION .Nm @@ -17,6 +18,10 @@ is executed after the screen has been locked. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl t Ar seconds +Lock the screen for a minimum of +.Ar seconds +before allowing password entry. The screen will prevent unlocking until the timer expires. .It Fl v Print version information to stdout and exit. .El @@ -25,7 +30,12 @@ Print version information to stdout and exit. .Sh EXAMPLES $ .Nm +Lock the screen normally. +.Pp +$ +.Nm /usr/sbin/s2ram +Lock the screen before suspending to RAM. .Sh SECURITY CONSIDERATIONS To make sure a locked screen can not be bypassed by switching VTs or killing the X server with Ctrl+Alt+Backspace, it is recommended diff --git a/slock.c b/slock.c @@ -12,6 +12,7 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <time.h> #include <unistd.h> #include <spawn.h> #include <sys/types.h> @@ -19,6 +20,7 @@ #include <X11/keysym.h> #include <X11/Xlib.h> #include <X11/Xutil.h> +#include <X11/Xft/Xft.h> #include "arg.h" #include "util.h" @@ -37,6 +39,9 @@ struct lock { Window root, win; Pixmap pmap; unsigned long colors[NUMCOLS]; + XftColor xftcolor; + XftDraw *xftdraw; + XftFont *xftfont; }; struct xrandr { @@ -48,6 +53,84 @@ struct xrandr { #include "config.h" static void +drawtext(Display *dpy, struct lock *lock, const char *text) +{ + int w, h, x, y; + XGlyphInfo extents; + + if (!lock->xftdraw || !lock->xftfont) + return; + + XftTextExtentsUtf8(dpy, lock->xftfont, + (const XftChar8 *)text, strlen(text), &extents); + + w = extents.width; + h = lock->xftfont->ascent + lock->xftfont->descent; + x = (DisplayWidth(dpy, lock->screen) - w) / 2; + y = (DisplayHeight(dpy, lock->screen) - h) / 2 + lock->xftfont->ascent; + + XftDrawStringUtf8(lock->xftdraw, &lock->xftcolor, lock->xftfont, x, y, + (const XftChar8 *)text, strlen(text)); +} + +static void +initxftfont(Display *dpy, struct lock *lock) +{ + lock->xftdraw = XftDrawCreate(dpy, lock->win, + DefaultVisual(dpy, lock->screen), + DefaultColormap(dpy, lock->screen)); + + lock->xftfont = XftFontOpenName(dpy, lock->screen, font); + + if (!lock->xftfont) { + lock->xftfont = XftFontOpen(dpy, lock->screen, + XFT_FAMILY, XftTypeString, "fixed", + XFT_SIZE, XftTypeDouble, 16.0, + NULL); + } + + XftColorAllocName(dpy, DefaultVisual(dpy, lock->screen), + DefaultColormap(dpy, lock->screen), fontcolor, &lock->xftcolor); +} + +static void +locktimer(Display *dpy, struct lock **locks, int nscreens, int timeout) +{ + char timer_text[32]; + int m, s, screen, remaining; + time_t lock_time; + XEvent ev; + + lock_time = time(NULL); + remaining = timeout - (time(NULL) - lock_time); + + for (screen = 0; screen < nscreens; screen++) + XRaiseWindow(dpy, locks[screen]->win); + + while (XPending(dpy)) + XNextEvent(dpy, &ev); + + while (remaining > 0) { + for (screen = 0; screen < nscreens; screen++) { + m = remaining / 60; + s = remaining % 60; + snprintf(timer_text, sizeof(timer_text), "%s %d:%02d remaining", timer_message, m, s); + XClearWindow(dpy, locks[screen]->win); + drawtext(dpy, locks[screen], timer_text); + } + + while (XPending(dpy)) + XNextEvent(dpy, &ev); + + usleep(50000); // 50ms + remaining = timeout - (time(NULL) - lock_time); + } + + for (screen = 0; screen < nscreens; screen++) + XClearWindow(dpy, locks[screen]->win); +} + +static void die(const char *errstr, ...) { va_list ap; @@ -279,6 +362,7 @@ lockscreen(Display *dpy, struct xrandr *rr, int screen) if (rr->active) XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask); + initxftfont(dpy, lock); XSelectInput(dpy, lock->root, SubstructureNotifyMask); return lock; } @@ -304,7 +388,7 @@ lockscreen(Display *dpy, struct xrandr *rr, int screen) static void usage(void) { - die("usage: slock [-v] [cmd [arg ...]]\n"); + die("usage: slock [-v] [-t seconds] [cmd [arg ...]]\n"); } int @@ -318,11 +402,15 @@ main(int argc, char **argv) { const char *hash; Display *dpy; int s, nlocks, nscreens; + int timeout = 0; ARGBEGIN { case 'v': puts("slock-"VERSION); return 0; + case 't': + timeout = atoi(EARGF(usage())); + break; default: usage(); } ARGEND @@ -389,8 +477,21 @@ main(int argc, char **argv) { } } + if (timeout > 0) + locktimer(dpy, locks, nscreens, timeout); + /* everything is now blank. Wait for the correct password */ readpw(dpy, &rr, locks, nscreens, hash); + /* Cleanup Xft resources */ + for (s = 0; s < nscreens; s++) { + if (locks[s]) { + if (locks[s]->xftfont) + XftFontClose(dpy, locks[s]->xftfont); + if (locks[s]->xftdraw) + XftDrawDestroy(locks[s]->xftdraw); + } + } + return 0; }