/*
* vim:ts=4:sw=4:expandtab
*
- * © 2010-2012 Michael Stapelberg
+ * © 2010-2013 Michael Stapelberg
*
* See LICENSE for licensing information
*
#include "unlock_indicator.h"
#include "xinerama.h"
+#define TSTAMP_N_SECS(n) (n * 1.0)
+#define TSTAMP_N_MINS(n) (60 * TSTAMP_N_SECS(n))
+#define START_TIMER(timer_obj, timeout, callback) \
+ timer_obj = start_timer(timer_obj, timeout, callback)
+#define STOP_TIMER(timer_obj) \
+ timer_obj = stop_timer(timer_obj)
+
+typedef void (*ev_callback_t)(EV_P_ ev_timer *w, int revents);
+
/* We need this for libxkbfile */
static Display *display;
char color[7] = "ffffff";
+int inactivity_timeout = 30;
uint32_t last_resolution[2];
xcb_window_t win;
static xcb_cursor_t cursor;
static bool dont_fork = false;
struct ev_loop *main_loop;
static struct ev_timer *clear_pam_wrong_timeout;
+static struct ev_timer *clear_indicator_timeout;
+static struct ev_timer *dpms_timeout;
+static struct ev_timer *discard_passwd_timeout;
extern unlock_state_t unlock_state;
extern pam_state_t pam_state;
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)
(void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i));
}
+static void turn_monitors_on(void) {
+ if (dpms)
+ dpms_set_mode(conn, XCB_DPMS_DPMS_MODE_ON);
+}
+
+static void turn_monitors_off(void) {
+ if (dpms)
+ dpms_set_mode(conn, XCB_DPMS_DPMS_MODE_OFF);
+}
+
/*
* Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
* Necessary so that we can properly let xkbcommon track the keyboard state and
* 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;
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;
vpassword[c] = c + (int)beep;
}
+ev_timer* start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) {
+ if (timer_obj) {
+ ev_timer_stop(main_loop, timer_obj);
+ ev_timer_set(timer_obj, timeout, 0.);
+ ev_timer_start(main_loop, timer_obj);
+ } else {
+ /* When there is no memory, we just don’t have a timeout. We cannot
+ * exit() here, since that would effectively unlock the screen. */
+ timer_obj = calloc(sizeof(struct ev_timer), 1);
+ if (timer_obj) {
+ ev_timer_init(timer_obj, callback, timeout, 0.);
+ ev_timer_start(main_loop, timer_obj);
+ }
+ }
+ return timer_obj;
+}
+
+ev_timer* stop_timer(ev_timer *timer_obj) {
+ if (timer_obj) {
+ ev_timer_stop(main_loop, timer_obj);
+ free(timer_obj);
+ }
+ return NULL;
+}
/*
- * Resets pam_state to STATE_PAM_IDLE 2 seconds after an unsuccesful
+ * Resets pam_state to STATE_PAM_IDLE 2 seconds after an unsuccessful
* authentication event.
*
*/
clear_pam_wrong_timeout = NULL;
}
+static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) {
+ clear_indicator();
+ STOP_TIMER(clear_indicator_timeout);
+}
+
+static void clear_input(void) {
+ input_position = 0;
+ clear_password_memory();
+ password[input_position] = '\0';
+
+ /* Hide the unlock indicator after a bit if the password buffer is
+ * empty. */
+ START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
+ unlock_state = STATE_BACKSPACE_ACTIVE;
+ redraw_screen();
+ unlock_state = STATE_KEY_PRESSED;
+}
+
+static void turn_off_monitors_cb(EV_P_ ev_timer *w, int revents) {
+ if (input_position == 0)
+ turn_monitors_off();
+
+ STOP_TIMER(dpms_timeout);
+}
+
+static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) {
+ clear_input();
+ turn_monitors_off();
+ STOP_TIMER(discard_passwd_timeout);
+}
+
static void input_done(void) {
if (clear_pam_wrong_timeout) {
ev_timer_stop(main_loop, clear_pam_wrong_timeout);
if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
DEBUG("successfully authenticated\n");
clear_password_memory();
+ /* Turn the screen on, as it may have been turned off
+ * on release of the 'enter' key. */
+ turn_monitors_on();
exit(0);
}
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
/* Cancel the clear_indicator_timeout, it would hide the unlock indicator
* too early. */
- stop_clear_indicator_timeout();
+ STOP_TIMER(clear_indicator_timeout);
/* beep on authentication failure, if enabled */
if (beep) {
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. */
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:
/* Hide the unlock indicator after a bit if the password buffer is
* empty. */
- start_clear_indicator_timeout();
+ START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb);
unlock_state = STATE_BACKSPACE_ACTIVE;
redraw_screen();
unlock_state = STATE_KEY_PRESSED;
/* 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();
ev_timer_start(main_loop, timeout);
}
- stop_clear_indicator_timeout();
+ STOP_TIMER(clear_indicator_timeout);
+ START_TIMER(discard_passwd_timeout, TSTAMP_N_MINS(3), discard_passwd_cb);
}
/*
* hiding us) gets hidden.
*
*/
-static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
+static void handle_visibility_notify(xcb_connection_t *conn,
+ 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);
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);
-
+ * empty input, so turn off the screen if DPMS is enabled, but
+ * only do that after some timeout: maybe user mistyped and
+ * will type again right away */
+ START_TIMER(dpms_timeout, TSTAMP_N_SECS(inactivity_timeout),
+ turn_off_monitors_cb);
break;
case XCB_VISIBILITY_NOTIFY:
- handle_visibility_notify((xcb_visibility_notify_event_t*)event);
+ handle_visibility_notify(conn, (xcb_visibility_notify_event_t*)event);
break;
case XCB_MAP_NOTIFY:
/* In the parent process, we exit */
if (fork() != 0)
exit(0);
+
+ ev_loop_fork(EV_DEFAULT);
}
break;
}
}
+/*
+ * This function is called from a fork()ed child and will raise the i3lock
+ * window when the window is obscured, even when the main i3lock process is
+ * blocked due to PAM.
+ *
+ */
+static void raise_loop(xcb_window_t window) {
+ xcb_connection_t *conn;
+ xcb_generic_event_t *event;
+ int screens;
+
+ if ((conn = xcb_connect(NULL, &screens)) == NULL ||
+ xcb_connection_has_error(conn))
+ errx(EXIT_FAILURE, "Cannot open display\n");
+
+ /* We need to know about the window being obscured or getting destroyed. */
+ xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK,
+ (uint32_t[]){
+ XCB_EVENT_MASK_VISIBILITY_CHANGE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY
+ });
+ xcb_flush(conn);
+
+ DEBUG("Watching window 0x%08x\n", window);
+ while ((event = xcb_wait_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ xcb_generic_error_t *error = (xcb_generic_error_t*)event;
+ DEBUG("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);
+ DEBUG("Read event of type %d\n", type);
+ switch (type) {
+ case XCB_VISIBILITY_NOTIFY:
+ handle_visibility_notify(conn, (xcb_visibility_notify_event_t*)event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ DEBUG("UnmapNotify for 0x%08x\n", (((xcb_unmap_notify_event_t*)event)->window));
+ if (((xcb_unmap_notify_event_t*)event)->window == window)
+ exit(EXIT_SUCCESS);
+ break;
+ case XCB_DESTROY_NOTIFY:
+ DEBUG("DestroyNotify for 0x%08x\n", (((xcb_destroy_notify_event_t*)event)->window));
+ if (((xcb_destroy_notify_event_t*)event)->window == window)
+ exit(EXIT_SUCCESS);
+ break;
+ default:
+ DEBUG("Unhandled event type %d\n", type);
+ break;
+ }
+ free(event);
+ }
+}
+
int main(int argc, char *argv[]) {
char *username;
char *image_path = NULL;
{"no-unlock-indicator", no_argument, NULL, 'u'},
{"image", required_argument, NULL, 'i'},
{"tiling", no_argument, NULL, 't'},
+ {"ignore-empty-password", no_argument, NULL, 'e'},
+ {"inactivity-timeout", required_argument, NULL, 'I'},
{NULL, no_argument, NULL, 0}
};
if ((username = getenv("USER")) == NULL)
- errx(1, "USER environment variable not set, please set it.\n");
+ errx(EXIT_FAILURE, "USER environment variable not set, please set it.\n");
- while ((o = getopt_long(argc, argv, "hvnbdc:p:ui:t", longopts, &optind)) != -1) {
+ char *optstring = "hvnbdc:p:ui:teI:";
+ while ((o = getopt_long(argc, argv, optstring, longopts, &optind)) != -1) {
switch (o) {
case 'v':
errx(EXIT_SUCCESS, "version " VERSION " © 2010-2012 Michael Stapelberg");
case 'd':
dpms = true;
break;
+ case 'I': {
+ int time = 0;
+ if (sscanf(optarg, "%d", &time) != 1 || time < 0)
+ errx(EXIT_FAILURE, "invalid timeout, it must be a positive integer\n");
+ inactivity_timeout = time;
+ break;
+ }
case 'c': {
char *arg = optarg;
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");
+ errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb\n");
break;
}
} else if (!strcmp(optarg, "default")) {
curs_choice = CURS_DEFAULT;
} else {
- errx(1, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\".\n");
+ errx(EXIT_FAILURE, "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]"
+ errx(EXIT_FAILURE, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
+ " [-i image.png] [-t] [-e] [-I]"
);
}
}
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;
}
}
win = open_fullscreen_window(conn, screen, color, bg_pixmap);
xcb_free_pixmap(conn, bg_pixmap);
+ pid_t pid = fork();
+ /* The pid == -1 case is intentionally ignored here:
+ * While the child process is useful for preventing other windows from
+ * popping up while i3lock blocks, it is not critical. */
+ if (pid == 0) {
+ /* Child */
+ close(xcb_get_file_descriptor(conn));
+ raise_loop(win);
+ exit(EXIT_SUCCESS);
+ }
+
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);
+ turn_monitors_off();
/* Initialize the libev event loop. */
main_loop = EV_DEFAULT;