- if(!(dpy = XOpenDisplay(0)))
- die("slock: cannot open display\n");
- screen = DefaultScreen(dpy);
- root = RootWindow(dpy, screen);
-
- if (fork() != 0)
- return 0;
-
- /* init */
- wa.override_redirect = 1;
- wa.background_pixel = WhitePixel(dpy, screen);
- w = XCreateWindow(dpy, root, 0, 0, DisplayWidth(dpy, screen), DisplayHeight(dpy, screen),
- 0, DefaultDepth(dpy, screen), CopyFromParent,
- DefaultVisual(dpy, screen), CWOverrideRedirect | CWBackPixel, &wa);
- XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "black", &black, &dummy);
- pmap = XCreateBitmapFromData(dpy, w, curs, 8, 8);
- invisible = XCreatePixmapCursor(dpy, pmap, pmap, &black, &black, 0, 0);
- XDefineCursor(dpy, w, invisible);
- XMapRaised(dpy, w);
- for(len = 1000; len; len--) {
- if(XGrabPointer(dpy, root, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
- GrabModeAsync, GrabModeAsync, None, invisible, CurrentTime) == GrabSuccess)
- break;
- usleep(1000);
- }
- if((running = running && (len > 0))) {
- for(len = 1000; len; len--) {
- if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)
- == GrabSuccess)
- break;
- usleep(1000);
- }
- running = (len > 0);
- }
- len = 0;
- XSync(dpy, False);
-
- /* main event loop */
- while(running && !XNextEvent(dpy, &ev)) {
- if(ev.type == KeyPress) {
- buf[0] = 0;
- num = XLookupString(&ev.xkey, buf, sizeof buf, &ksym, 0);
- if(IsKeypadKey(ksym)) {
- if(ksym == XK_KP_Enter)
- ksym = XK_Return;
- else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
- ksym = (ksym - XK_KP_0) + XK_0;
- }
- if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
- || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
- || IsPrivateKeypadKey(ksym))
- continue;
- switch(ksym) {
- case XK_Return:
- passwd[len] = 0;
-#ifdef HAVE_BSD_AUTH
- running = !auth_userokay(getlogin(), NULL, "auth-xlock", passwd);
-#else
- running = strcmp(crypt(passwd, pws), pws);
+ if (n < 2)
+ return;
+
+ /* store it in the password array as UTF-8 */
+ memcpy(password+input_position, buffer, n-1);
+ input_position += n-1;
+ DEBUG("current password = %.*s\n", input_position, password);
+
+ unlock_state = STATE_KEY_ACTIVE;
+ redraw_screen();
+ unlock_state = STATE_KEY_PRESSED;
+
+ struct ev_timer *timeout = calloc(sizeof(struct ev_timer), 1);
+ if (timeout) {
+ ev_timer_init(timeout, redraw_timeout, 0.25, 0.);
+ ev_timer_start(main_loop, timeout);
+ }
+
+ stop_clear_indicator_timeout();
+}
+
+/*
+ * A visibility notify event will be received when the visibility (= can the
+ * user view the complete window) changes, so for example when a popup overlays
+ * some area of the i3lock window.
+ *
+ * In this case, we raise our window on top so that the popup (or whatever is
+ * hiding us) gets hidden.
+ *
+ */
+static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
+ if (event->state != XCB_VISIBILITY_UNOBSCURED) {
+ uint32_t values[] = { XCB_STACK_MODE_ABOVE };
+ xcb_configure_window(conn, event->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
+ xcb_flush(conn);
+ }
+}
+
+/*
+ * Called when the keyboard mapping changes. We update our symbols.
+ *
+ */
+static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
+ /* We ignore errors — if the new keymap cannot be loaded it’s better if the
+ * screen stays locked and the user intervenes by using killall i3lock. */
+ (void)load_keymap();
+}
+
+/*
+ * Called when the properties on the root window change, e.g. when the screen
+ * resolution changes. If so we update the window to cover the whole screen
+ * and also redraw the image, if any.
+ *
+ */
+void handle_screen_resize(void) {
+ xcb_get_geometry_cookie_t geomc;
+ xcb_get_geometry_reply_t *geom;
+ geomc = xcb_get_geometry(conn, screen->root);
+ if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
+ return;
+
+ if (last_resolution[0] == geom->width &&
+ last_resolution[1] == geom->height) {
+ free(geom);
+ return;
+ }
+
+ last_resolution[0] = geom->width;
+ last_resolution[1] = geom->height;
+
+ free(geom);
+
+ redraw_screen();
+
+ uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ xcb_configure_window(conn, win, mask, last_resolution);
+ xcb_flush(conn);
+
+ xinerama_query_screens();
+ redraw_screen();
+}
+
+/*
+ * Callback function for PAM. We only react on password request callbacks.
+ *
+ */
+static int conv_callback(int num_msg, const struct pam_message **msg,
+ struct pam_response **resp, void *appdata_ptr)
+{
+ if (num_msg == 0)
+ return 1;
+
+ /* PAM expects an array of responses, one for each message */
+ if ((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL) {
+ perror("calloc");
+ return 1;
+ }
+
+ for (int c = 0; c < num_msg; c++) {
+ if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF &&
+ msg[c]->msg_style != PAM_PROMPT_ECHO_ON)
+ continue;
+
+ /* return code is currently not used but should be set to zero */
+ resp[c]->resp_retcode = 0;
+ if ((resp[c]->resp = strdup(password)) == NULL) {
+ perror("strdup");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
+ *
+ */
+static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
+ /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+}
+
+/*
+ * Flush before blocking (and waiting for new events)
+ *
+ */
+static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ xcb_flush(conn);
+}
+
+/*
+ * Instead of polling the X connection socket we leave this to
+ * xcb_poll_for_event() which knows better than we can ever know.
+ *
+ */
+static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+ xcb_generic_event_t *event;
+
+ while ((event = xcb_poll_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t*)event;
+ if (debug_mode)
+ fprintf(stderr, "X11 Error received! sequence 0x%x, error_code = %d\n",
+ error->sequence, error->error_code);
+ free(event);
+ continue;
+ }
+
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
+ switch (type) {
+ case XCB_KEY_PRESS:
+ handle_key_press((xcb_key_press_event_t*)event);
+ break;
+
+ case XCB_KEY_RELEASE:
+ handle_key_release((xcb_key_release_event_t*)event);
+
+ /* If this was the backspace or escape key we are back at an
+ * empty input, so turn off the screen if DPMS is enabled */
+ if (dpms && input_position == 0)
+ dpms_turn_off_screen(conn);
+
+ break;
+
+ case XCB_VISIBILITY_NOTIFY:
+ handle_visibility_notify((xcb_visibility_notify_event_t*)event);
+ break;
+
+ case XCB_MAP_NOTIFY:
+ if (!dont_fork) {
+ /* After the first MapNotify, we never fork again. We don’t
+ * expect to get another MapNotify, but better be sure… */
+ dont_fork = true;
+
+ /* In the parent process, we exit */
+ if (fork() != 0)
+ exit(0);
+
+ ev_loop_fork(EV_DEFAULT);
+ }
+ break;
+
+ case XCB_MAPPING_NOTIFY:
+ handle_mapping_notify((xcb_mapping_notify_event_t*)event);
+ break;
+
+ case XCB_CONFIGURE_NOTIFY:
+ handle_screen_resize();
+ break;
+ }
+
+ free(event);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ char *username;
+ char *image_path = NULL;
+ int ret;
+ struct pam_conv conv = {conv_callback, NULL};
+ int curs_choice = CURS_NONE;
+ int o;
+ int optind = 0;
+ struct option longopts[] = {
+ {"version", no_argument, NULL, 'v'},
+ {"nofork", no_argument, NULL, 'n'},
+ {"beep", no_argument, NULL, 'b'},
+ {"dpms", no_argument, NULL, 'd'},
+ {"color", required_argument, NULL, 'c'},
+ {"pointer", required_argument, NULL , 'p'},
+ {"debug", no_argument, NULL, 0},
+ {"help", no_argument, NULL, 'h'},
+ {"no-unlock-indicator", no_argument, NULL, 'u'},
+ {"image", required_argument, NULL, 'i'},
+ {"tiling", no_argument, NULL, 't'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ if ((username = getenv("USER")) == NULL)
+ errx(1, "USER environment variable not set, please set it.\n");
+
+ while ((o = getopt_long(argc, argv, "hvnbdc:p:ui:t", longopts, &optind)) != -1) {
+ switch (o) {
+ case 'v':
+ errx(EXIT_SUCCESS, "version " VERSION " © 2010-2012 Michael Stapelberg");
+ case 'n':
+ dont_fork = true;
+ break;
+ case 'b':
+ beep = true;
+ break;
+ case 'd':
+ dpms = true;
+ break;
+ case 'c': {
+ char *arg = optarg;
+
+ /* Skip # if present */
+ if (arg[0] == '#')
+ arg++;
+
+ if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1)
+ errx(1, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb\n");
+
+ break;
+ }
+ case 'u':
+ unlock_indicator = false;
+ break;
+ case 'i':
+ image_path = strdup(optarg);
+ break;
+ case 't':
+ tile = true;
+ break;
+ case 'p':
+ if (!strcmp(optarg, "win")) {
+ curs_choice = CURS_WIN;
+ } else if (!strcmp(optarg, "default")) {
+ curs_choice = CURS_DEFAULT;
+ } else {
+ errx(1, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".\n");
+ }
+ break;
+ case 0:
+ if (strcmp(longopts[optind].name, "debug") == 0)
+ debug_mode = true;
+ break;
+ default:
+ errx(1, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
+ " [-i image.png] [-t]"
+ );
+ }
+ }
+
+ /* We need (relatively) random numbers for highlighting a random part of
+ * the unlock indicator upon keypresses. */
+ srand(time(NULL));
+
+ /* Initialize PAM */
+ ret = pam_start("i3lock", username, &conv, &pam_handle);
+ if (ret != PAM_SUCCESS)
+ errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret));
+
+/* Using mlock() as non-super-user seems only possible in Linux. Users of other
+ * operating systems should use encrypted swap/no swap (or remove the ifdef and
+ * run i3lock as super-user). */
+#if defined(__linux__)
+ /* Lock the area where we store the password in memory, we don’t want it to
+ * be swapped to disk. Since Linux 2.6.9, this does not require any
+ * privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */
+ if (mlock(password, sizeof(password)) != 0)
+ err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK");