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