]> git.sur5r.net Git - i3/i3lock/blob - i3lock.c
d136ad2e46b05089b9ed47cb628bfae3f58fd6a4
[i3/i3lock] / i3lock.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3lock - an improved version of slock
5  *
6  * i3lock © 2009 Michael Stapelberg and contributors
7  * i3lock © 2009 Jan-Erik Rediger
8  * slock  © 2006-2008 Anselm R Garbe
9  *
10  * See file LICENSE for license information.
11  *
12  * Note that on any error (calloc is out of memory for example)
13  * we do not do anything so that the user can fix the error by
14  * himself (kill X to get more free memory or stop some other
15  * program using SSH/console).
16  *
17  */
18 #define _XOPEN_SOURCE 500
19
20 #include <ctype.h>
21 #include <stdarg.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <stdint.h>
25 #include <string.h>
26 #include <math.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <X11/keysym.h>
30 #include <X11/Xlib.h>
31 #include <X11/Xutil.h>
32 #include <X11/xpm.h>
33 #include <X11/extensions/dpms.h>
34 #include <stdbool.h>
35 #include <getopt.h>
36 #include <err.h>
37
38 #include <security/pam_appl.h>
39
40 static char passwd[256];
41
42 /*
43  * displays an xpm image tiled over the whole screen
44  * (the image will be visible on all screens
45  * when using a multi monitor setup)
46  *
47  */
48 static void tile_image(XpmImage *image, int disp_height, int disp_width,
49                        Display *dpy, Pixmap pix, Window w, GC gc)
50 {
51         int rows = (int)ceil(disp_height / (float)image->height),
52             cols = (int)ceil(disp_width / (float)image->width);
53
54         for (int y = 0; y < rows; y++) {
55                 for (int x = 0; x < cols; x++) {
56                         XCopyArea(dpy, pix, w, gc, 0, 0,
57                                   image->width, image->height,
58                                   image->width * x, image->height * y);
59                 }
60         }
61 }
62
63 /*
64  * Returns the colorpixel to use for the given hex color (think of HTML).
65  *
66  * The hex_color may not start with #, for example FF00FF works.
67  *
68  * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
69  * This has to be done by the caller.
70  *
71  */
72 static uint32_t get_colorpixel(char *hex) {
73         char strgroups[3][3] = {{hex[0], hex[1], '\0'},
74                                 {hex[2], hex[3], '\0'},
75                                 {hex[4], hex[5], '\0'}};
76         uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
77                              (strtol(strgroups[1], NULL, 16)),
78                              (strtol(strgroups[2], NULL, 16))};
79
80         return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
81 }
82
83 /*
84  * Check if given file can be opened => exists
85  *
86  */
87 static bool file_exists(const char *filename)
88 {
89         FILE * file = fopen(filename, "r");
90         if(file)
91         {
92                 fclose(file);
93                 return true;
94         }
95         return false;
96 }
97
98 /*
99  * Puts the given XPM error code to stderr
100  *
101  */
102 static void print_xpm_error(int err)
103 {
104         switch (err) {
105                 case XpmColorError:
106                         fprintf(stderr, "XPM: Could not parse or alloc requested color\n");
107                         break;
108                 case XpmOpenFailed:
109                         fprintf(stderr, "XPM: Cannot open file\n");
110                         break;
111                 case XpmFileInvalid:
112                         fprintf(stderr, "XPM: invalid XPM file\n");
113                         break;
114                 case XpmNoMemory:
115                         fprintf(stderr, "XPM: Not enough memory\n");
116                         break;
117                 case XpmColorFailed:
118                         fprintf(stderr, "XPM: Color not found\n");
119                         break;
120         }
121 }
122
123
124 /*
125  * Callback function for PAM. We only react on password request callbacks.
126  *
127  */
128 static int conv_callback(int num_msg, const struct pam_message **msg,
129                          struct pam_response **resp, void *appdata_ptr)
130 {
131         if (num_msg == 0)
132                 return 1;
133
134         /* PAM expects an arry of responses, one for each message */
135         if ((*resp = calloc(num_msg, sizeof(struct pam_message))) == NULL) {
136                 perror("calloc");
137                 return 1;
138         }
139
140         for (int c = 0; c < num_msg; c++) {
141                 if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF &&
142                     msg[c]->msg_style != PAM_PROMPT_ECHO_ON)
143                         continue;
144
145                 /* return code is currently not used but should be set to zero */
146                 resp[c]->resp_retcode = 0;
147                 if ((resp[c]->resp = strdup(passwd)) == NULL) {
148                         perror("strdup");
149                         return 1;
150                 }
151         }
152
153         return 0;
154 }
155
156 int main(int argc, char *argv[])
157 {
158         char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
159         char buf[32];
160         char *username;
161         int num, screen;
162
163         unsigned int len;
164         bool running = true;
165
166         /* By default, fork, don’t beep and don’t turn off monitor */
167         bool dont_fork = false;
168         bool beep = false;
169         bool dpms = false;
170         bool xpm_image = false;
171         bool tiling = false;
172         char xpm_image_path[256];
173         char color[7] = "ffffff"; // white
174         Cursor invisible;
175         Display *dpy;
176         KeySym ksym;
177         Pixmap pmap;
178         Window root, w;
179         XColor black, dummy;
180         XEvent ev;
181         XSetWindowAttributes wa;
182
183         pam_handle_t *handle;
184         struct pam_conv conv = {conv_callback, NULL};
185
186         char opt;
187         int optind = 0;
188         static struct option long_options[] = {
189                 {"version", no_argument, NULL, 'v'},
190                 {"nofork", no_argument, NULL, 'n'},
191                 {"beep", no_argument, NULL, 'b'},
192                 {"dpms", no_argument, NULL, 'd'},
193                 {"image", required_argument, NULL, 'i'},
194                 {"color", required_argument, NULL, 'c'},
195                 {"tiling", no_argument, NULL, 't'},
196                 {NULL, no_argument, NULL, 0}
197         };
198
199         while ((opt = getopt_long(argc, argv, "vnbdi:c:t", long_options, &optind)) != -1) {
200                 switch (opt) {
201                         case 'v':
202                                 errx(0, "i3lock-"VERSION", © 2009 Michael Stapelberg\n"
203                                     "based on slock, which is © 2006-2008 Anselm R Garbe\n");
204                         case 'n':
205                                 dont_fork = true;
206                                 break;
207                         case 'b':
208                                 beep = true;
209                                 break;
210                         case 'd':
211                                 dpms = true;
212                                 break;
213                         case 'i':
214                                 strncpy(xpm_image_path, optarg, 255);
215                                 xpm_image = true;
216                                 break;
217                         case 'c':
218                         {
219                                 char *arg = optarg;
220                                 /* Skip # if present */
221                                 if (arg[0] == '#')
222                                         arg++;
223
224                                 if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1)
225                                         errx(1, "color is invalid, color must be given in 6-byte format: rrggbb\n");
226
227                                 break;
228                         }
229                         case 't':
230                                 tiling = true;
231                                 break;
232                         default:
233                                 errx(1, "i3lock: Unknown option. Syntax: i3lock [-v] [-n] [-b] [-d] [-i image.xpm] [-c color] [-t]\n");
234                 }
235         }
236
237         if ((username = getenv("USER")) == NULL)
238                 errx(1, "USER environment variable not set, please set it.\n");
239
240         int ret = pam_start("i3lock", username, &conv, &handle);
241         if (ret != PAM_SUCCESS)
242                 errx(1, "PAM: %s\n", pam_strerror(handle, ret));
243
244         if(!(dpy = XOpenDisplay(0)))
245                 errx(1, "i3lock: cannot open display\n");
246         screen = DefaultScreen(dpy);
247         root = RootWindow(dpy, screen);
248
249         if (!dont_fork) {
250                 if (fork() != 0)
251                         return 0;
252         }
253
254         /* init */
255         wa.override_redirect = 1;
256         wa.background_pixel = get_colorpixel(color);
257         w = XCreateWindow(dpy, root, 0, 0, DisplayWidth(dpy, screen), DisplayHeight(dpy, screen),
258                         0, DefaultDepth(dpy, screen), CopyFromParent,
259                         DefaultVisual(dpy, screen), CWOverrideRedirect | CWBackPixel, &wa);
260         XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "black", &black, &dummy);
261         pmap = XCreateBitmapFromData(dpy, w, curs, 8, 8);
262         invisible = XCreatePixmapCursor(dpy, pmap, pmap, &black, &black, 0, 0);
263         XDefineCursor(dpy, w, invisible);
264         XMapRaised(dpy, w);
265
266         if (xpm_image && file_exists(xpm_image_path)) {
267                 GC gc = XDefaultGC(dpy, 0);
268                 int depth = DefaultDepth(dpy, screen);
269                 int disp_width = DisplayWidth(dpy, screen);
270                 int disp_height = DisplayHeight(dpy, screen);
271                 Pixmap pix = XCreatePixmap(dpy, w, disp_width, disp_height, depth);
272                 XpmImage xpm_image;
273                 XpmInfo xpm_info;
274
275                 int err = XpmReadFileToXpmImage(xpm_image_path, &xpm_image, &xpm_info);
276                 if (err != 0) {
277                         print_xpm_error(err);
278                         return 1;
279                 }
280
281                 err = XpmCreatePixmapFromXpmImage(dpy, w, &xpm_image, &pix, 0, 0);
282                 if (err != 0) {
283                         print_xpm_error(err);
284                         return 1;
285                 }
286
287                 if (tiling)
288                         tile_image(&xpm_image, disp_height, disp_width, dpy, pix, w, gc);
289                 else
290                         XCopyArea(dpy, pix, w, gc, 0, 0, disp_width, disp_height, 0, 0);
291         }
292
293         for(len = 1000; len; len--) {
294                 if(XGrabPointer(dpy, root, False, ButtonPressMask | ButtonReleaseMask,
295                         GrabModeAsync, GrabModeAsync, None, invisible, CurrentTime) == GrabSuccess)
296                         break;
297                 usleep(1000);
298         }
299         if((running = running && (len > 0))) {
300                 for(len = 1000; len; len--) {
301                         if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)
302                                 == GrabSuccess)
303                                 break;
304                         usleep(1000);
305                 }
306                 running = (len > 0);
307         }
308         len = 0;
309         XSync(dpy, False);
310
311         /* main event loop */
312         while(running && !XNextEvent(dpy, &ev)) {
313                 if (len == 0 && dpms && DPMSCapable(dpy)) {
314                         DPMSEnable(dpy);
315                         DPMSForceLevel(dpy, DPMSModeOff);
316                 }
317
318                 if(ev.type != KeyPress)
319                         continue;
320
321                 buf[0] = 0;
322                 num = XLookupString(&ev.xkey, buf, sizeof buf, &ksym, 0);
323                 if(IsKeypadKey(ksym)) {
324                         if(ksym == XK_KP_Enter)
325                                 ksym = XK_Return;
326                         else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
327                                 ksym = (ksym - XK_KP_0) + XK_0;
328                 }
329                 if(IsFunctionKey(ksym) ||
330                    IsKeypadKey(ksym) ||
331                    IsMiscFunctionKey(ksym) ||
332                    IsPFKey(ksym) ||
333                    IsPrivateKeypadKey(ksym))
334                         continue;
335                 switch(ksym) {
336                 case XK_Return:
337                         passwd[len] = 0;
338                         if ((ret = pam_authenticate(handle, 0)) == PAM_SUCCESS)
339                                 running = false;
340                         else {
341                                 fprintf(stderr, "PAM: %s\n", pam_strerror(handle, ret));
342                                 if (beep)
343                                         XBell(dpy, 100);
344                         }
345                         len = 0;
346                         break;
347                 case XK_Escape:
348                         len = 0;
349                         break;
350                 case XK_BackSpace:
351                         if (len > 0)
352                                 len--;
353                         break;
354                 default:
355                         if(num && !iscntrl((int) buf[0]) && (len + num < sizeof passwd)) {
356                                 memcpy(passwd + len, buf, num);
357                                 len += num;
358                         }
359                         break;
360                 }
361         }
362         XUngrabPointer(dpy, CurrentTime);
363         XFreePixmap(dpy, pmap);
364         XDestroyWindow(dpy, w);
365         XCloseDisplay(dpy);
366         return 0;
367 }