]> git.sur5r.net Git - i3/i3lock/blobdiff - i3lock.c
Bug fix: Call clear_input() when the password is wrong.
[i3/i3lock] / i3lock.c
index 5d1da0162d3d0b290cffc03f50816fdfcffe9f11..2d8a580e9b905dc2045dea56526c7ddfd2512ffa 100644 (file)
--- a/i3lock.c
+++ b/i3lock.c
@@ -1,7 +1,7 @@
 /*
  * vim:ts=4:sw=4:expandtab
  *
- * © 2010-2012 Michael Stapelberg
+ * © 2010-2013 Michael Stapelberg
  *
  * See LICENSE for licensing information
  *
@@ -22,6 +22,7 @@
 #include <string.h>
 #include <ev.h>
 #include <sys/mman.h>
+#include <sys/wait.h>
 #include <X11/XKBlib.h>
 #include <X11/extensions/XKBfile.h>
 #include <xkbcommon/xkbcommon.h>
@@ -60,6 +61,7 @@ static struct xkb_keymap *xkb_keymap;
 
 cairo_surface_t *img = NULL;
 bool tile = false;
+bool ignore_empty_password = false;
 
 /* isutf, u8_dec © 2005 Jeff Bezanson, public domain */
 #define isutf(c) (((c) & 0xC0) != 0x80)
@@ -80,6 +82,9 @@ void u8_dec(char *s, int *i) {
  * Ideally, xkbcommon would ship something like this itself, but as of now
  * (version 0.2.0), it doesn’t.
  *
+ * TODO: Once xcb-xkb is enabled by default and released, we should port this
+ * code to xcb-xkb. See also https://github.com/xkbcommon/libxkbcommon/issues/1
+ *
  */
 static bool load_keymap(void) {
     bool ret = false;
@@ -126,6 +131,16 @@ static bool load_keymap(void) {
         goto out;
     }
 
+    /* Get the initial modifier state to be in sync with the X server.
+     * See https://github.com/xkbcommon/libxkbcommon/issues/1 for why we ignore
+     * the base and latched fields. */
+    XkbStateRec state_rec;
+    XkbGetState(display, XkbUseCoreKbd, &state_rec);
+
+    xkb_state_update_mask(new_state,
+        0, 0, state_rec.locked_mods,
+        0, 0, state_rec.locked_group);
+
     if (xkb_state != NULL)
         xkb_state_unref(xkb_state);
     xkb_state = new_state;
@@ -172,29 +187,25 @@ static void clear_pam_wrong(EV_P_ ev_timer *w, int revents) {
     clear_pam_wrong_timeout = NULL;
 }
 
-static void input_done(void) {
-    if (input_position == 0)
-        return;
-
-    if (clear_pam_wrong_timeout) {
-        ev_timer_stop(main_loop, clear_pam_wrong_timeout);
-        free(clear_pam_wrong_timeout);
-        clear_pam_wrong_timeout = NULL;
-    }
+static void clear_input(void) {
+    input_position = 0;
+    clear_password_memory();
+    password[input_position] = '\0';
 
-    pam_state = STATE_PAM_VERIFY;
+    /* Hide the unlock indicator after a bit if the password buffer is
+     * empty. */
+    start_clear_indicator_timeout();
+    unlock_state = STATE_BACKSPACE_ACTIVE;
     redraw_screen();
+    unlock_state = STATE_KEY_PRESSED;
+}
 
-    if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
-        DEBUG("successfully authenticated\n");
-        clear_password_memory();
-        exit(0);
-    }
-
+static void auth_failed(void) {
     if (debug_mode)
         fprintf(stderr, "Authentication failure\n");
 
     pam_state = STATE_PAM_WRONG;
+    clear_input();
     redraw_screen();
 
     /* Clear this state after 2 seconds (unless the user enters another
@@ -216,6 +227,53 @@ static void input_done(void) {
     }
 }
 
+static void child_cb(EV_P_ ev_child *child_watcher, int revents) {
+    if (child_watcher->rstatus != 0) {
+        DEBUG("Authentication successfull\n");
+        clear_password_memory();
+
+        exit(0);
+    } else {
+        auth_failed();
+    }
+    ev_child_stop(main_loop, child_watcher);
+    free(child_watcher);
+}
+
+static void input_done(void) {
+    if (pam_state == STATE_PAM_VERIFY) {
+        return;
+    }
+
+    if (clear_pam_wrong_timeout) {
+        ev_timer_stop(main_loop, clear_pam_wrong_timeout);
+        free(clear_pam_wrong_timeout);
+        clear_pam_wrong_timeout = NULL;
+    }
+
+    pam_state = STATE_PAM_VERIFY;
+    redraw_screen();
+
+    /* fork to unblock pam_authenticate */
+    pid_t cpid = fork();
+    if (cpid == 0) {
+        exit(pam_authenticate(pam_handle, 0) == PAM_SUCCESS);
+    } else if (cpid > 0) {
+        struct ev_child *child_watcher = calloc(sizeof(struct ev_io), 1);
+        ev_child_init(child_watcher, child_cb, cpid, 0);
+        ev_child_set(child_watcher, cpid, 0);
+        ev_child_start(EV_DEFAULT_ child_watcher);
+    } else if (cpid < 0) {
+        DEBUG("Could not fork");
+        if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
+            DEBUG("successfully authenticated\n");
+            clear_password_memory();
+            exit(0);
+        }
+        auth_failed();
+    }
+}
+
 /*
  * Called when the user releases a key. We need to leave the Mode_switch
  * state when the user releases the Mode_switch key.
@@ -242,8 +300,10 @@ static void handle_key_press(xcb_key_press_event_t *event) {
     xkb_keysym_t ksym;
     char buffer[128];
     int n;
+    bool ctrl;
 
     ksym = xkb_state_key_get_one_sym(xkb_state, event->detail);
+    ctrl = xkb_state_mod_name_is_active(xkb_state, "Control", XKB_STATE_MODS_DEPRESSED);
     xkb_state_update_key(xkb_state, event->detail, XKB_KEY_DOWN);
 
     /* The buffer will be null-terminated, so n >= 2 for 1 actual character. */
@@ -254,19 +314,26 @@ static void handle_key_press(xcb_key_press_event_t *event) {
     case XKB_KEY_Return:
     case XKB_KEY_KP_Enter:
     case XKB_KEY_XF86ScreenSaver:
+        if (ignore_empty_password && input_position == 0) {
+            clear_input();
+            return;
+        }
         password[input_position] = '\0';
+        unlock_state = STATE_KEY_PRESSED;
+        redraw_screen();
         input_done();
-    case XKB_KEY_Escape:
-        input_position = 0;
-        clear_password_memory();
-        password[input_position] = '\0';
+        return;
 
-        /* Hide the unlock indicator after a bit if the password buffer is
-         * empty. */
-        start_clear_indicator_timeout();
-        unlock_state = STATE_BACKSPACE_ACTIVE;
-        redraw_screen();
-        unlock_state = STATE_KEY_PRESSED;
+    case XKB_KEY_u:
+        if (ctrl) {
+            DEBUG("C-u pressed\n");
+            clear_input();
+            return;
+        }
+        break;
+
+    case XKB_KEY_Escape:
+        clear_input();
         return;
 
     case XKB_KEY_BackSpace:
@@ -306,7 +373,7 @@ static void handle_key_press(xcb_key_press_event_t *event) {
     /* 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", password);
+    DEBUG("current password = %.*s\n", input_position, password);
 
     unlock_state = STATE_KEY_ACTIVE;
     redraw_screen();
@@ -479,6 +546,8 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
                     /* In the parent process, we exit */
                     if (fork() != 0)
                         exit(0);
+
+                    ev_loop_fork(EV_DEFAULT);
                 }
                 break;
 
@@ -515,13 +584,14 @@ int main(int argc, char *argv[]) {
         {"no-unlock-indicator", no_argument, NULL, 'u'},
         {"image", required_argument, NULL, 'i'},
         {"tiling", no_argument, NULL, 't'},
+        {"ignore-empty-password", no_argument, NULL, 'e'},
         {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) {
+    while ((o = getopt_long(argc, argv, "hvnbdc:p:ui:te", longopts, &optind)) != -1) {
         switch (o) {
         case 'v':
             errx(EXIT_SUCCESS, "version " VERSION " © 2010-2012 Michael Stapelberg");
@@ -542,7 +612,7 @@ int main(int argc, char *argv[]) {
                 arg++;
 
             if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1)
-                errx(1, "color is invalid, color must be given in 6-byte format: rrggbb\n");
+                errx(1, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb\n");
 
             break;
         }
@@ -564,13 +634,16 @@ int main(int argc, char *argv[]) {
                 errx(1, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".\n");
             }
             break;
+        case 'e':
+            ignore_empty_password = true;
+            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]"
+            " [-i image.png] [-t] [-e]"
             );
         }
     }
@@ -639,8 +712,8 @@ int main(int argc, char *argv[]) {
         img = cairo_image_surface_create_from_png(image_path);
         /* In case loading failed, we just pretend no -i was specified. */
         if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
-            fprintf(stderr, "Could not load image \"%s\": cairo surface status %d\n",
-                    image_path, cairo_surface_status(img));
+            fprintf(stderr, "Could not load image \"%s\": %s\n",
+                    image_path, cairo_status_to_string(cairo_surface_status(img)));
             img = NULL;
         }
     }
@@ -655,6 +728,11 @@ int main(int argc, char *argv[]) {
     cursor = create_cursor(conn, screen, win, curs_choice);
 
     grab_pointer_and_keyboard(conn, screen, cursor);
+    /* Load the keymap again to sync the current modifier state. Since we first
+     * loaded the keymap, there might have been changes, but starting from now,
+     * we should get all key presses/releases due to having grabbed the
+     * keyboard. */
+    (void)load_keymap();
 
     if (dpms)
         dpms_turn_off_screen(conn);