X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;ds=sidebyside;f=i3lock.c;h=64e4600142f0f853023f9cbbb4c4fe9364b1d2c7;hb=0cbf483cb4a1597343df90642ac475b288ae9e80;hp=159928264928189dbe3188b04672b7060732d2e7;hpb=2509c987d1d01a1890521a0c9d087dedbc7103dc;p=i3%2Fi3lock diff --git a/i3lock.c b/i3lock.c index 1599282..64e4600 100644 --- a/i3lock.c +++ b/i3lock.c @@ -13,18 +13,17 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include #include -#include -#include #include +#include #include #include @@ -34,6 +33,8 @@ #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) \ @@ -42,8 +43,8 @@ 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; @@ -59,16 +60,23 @@ 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; +int failed_attempts = 0; +bool show_failed_attempts = false; static struct xkb_state *xkb_state; static struct xkb_context *xkb_context; static struct xkb_keymap *xkb_keymap; +static uint8_t xkb_base_event; +static uint8_t xkb_base_error; cairo_surface_t *img = NULL; bool tile = false; bool ignore_empty_password = false; +bool skip_repeated_empty_password = false; /* isutf, u8_dec © 2005 Jeff Bezanson, public domain */ #define isutf(c) (((c) & 0xC0) != 0x80) @@ -96,77 +104,35 @@ static void turn_monitors_off(void) { * Necessary so that we can properly let xkbcommon track the keyboard state and * translate keypresses to utf-8. * - * 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; - XkbFileInfo result; - memset(&result, '\0', sizeof(result)); - result.xkb = XkbGetKeyboard(display, XkbAllMapComponentsMask, XkbUseCoreKbd); - if (result.xkb == NULL) { - fprintf(stderr, "[i3lock] XKB: XkbGetKeyboard failed\n"); - return false; - } - - FILE *temp = tmpfile(); - if (temp == NULL) { - fprintf(stderr, "[i3lock] could not create tempfile\n"); - return false; - } - - bool ok = XkbWriteXKBKeymap(temp, &result, false, false, NULL, NULL); - if (!ok) { - fprintf(stderr, "[i3lock] XkbWriteXKBKeymap failed\n"); - goto out; - } - - rewind(temp); - if (xkb_context == NULL) { if ((xkb_context = xkb_context_new(0)) == NULL) { fprintf(stderr, "[i3lock] could not create xkbcommon context\n"); - goto out; + return false; } } - if (xkb_keymap != NULL) - xkb_keymap_unref(xkb_keymap); + xkb_keymap_unref(xkb_keymap); - if ((xkb_keymap = xkb_keymap_new_from_file(xkb_context, temp, XKB_KEYMAP_FORMAT_TEXT_V1, 0)) == NULL) { - fprintf(stderr, "[i3lock] xkb_keymap_new_from_file failed\n"); - goto out; + int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn); + DEBUG("device = %d\n", device_id); + if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { + fprintf(stderr, "[i3lock] xkb_x11_keymap_new_from_device failed\n"); + return false; } - struct xkb_state *new_state = xkb_state_new(xkb_keymap); + struct xkb_state *new_state = + xkb_x11_state_new_from_device(xkb_keymap, conn, device_id); if (new_state == NULL) { - fprintf(stderr, "[i3lock] xkb_state_new failed\n"); - goto out; + fprintf(stderr, "[i3lock] xkb_x11_state_new_from_device failed\n"); + return false; } - /* 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_unref(xkb_state); xkb_state = new_state; - ret = true; -out: - XkbFreeKeyboard(result.xkb, XkbAllComponentsMask, true); - fclose(temp); - return ret; + return true; } /* @@ -212,7 +178,7 @@ ev_timer* stop_timer(ev_timer *timer_obj) { } /* - * 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. * */ @@ -223,9 +189,7 @@ static void clear_pam_wrong(EV_P_ ev_timer *w, int revents) { redraw_screen(); /* Now free this timeout. */ - ev_timer_stop(main_loop, clear_pam_wrong_timeout); - free(clear_pam_wrong_timeout); - clear_pam_wrong_timeout = NULL; + STOP_TIMER(clear_pam_wrong_timeout); } static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) { @@ -246,13 +210,21 @@ static void clear_input(void) { unlock_state = STATE_KEY_PRESSED; } -static void input_done(void) { - 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 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) { + STOP_TIMER(clear_pam_wrong_timeout); pam_state = STATE_PAM_VERIFY; redraw_screen(); @@ -269,16 +241,14 @@ static void input_done(void) { fprintf(stderr, "Authentication failure\n"); pam_state = STATE_PAM_WRONG; + failed_attempts += 1; clear_input(); redraw_screen(); /* Clear this state after 2 seconds (unless the user enters another * password during that time). */ ev_now_update(main_loop); - if ((clear_pam_wrong_timeout = calloc(sizeof(struct ev_timer), 1))) { - ev_timer_init(clear_pam_wrong_timeout, clear_pam_wrong, 2.0, 0.); - ev_timer_start(main_loop, clear_pam_wrong_timeout); - } + START_TIMER(clear_pam_wrong_timeout, TSTAMP_N_SECS(2), clear_pam_wrong); /* Cancel the clear_indicator_timeout, it would hide the unlock indicator * too early. */ @@ -291,20 +261,19 @@ static void input_done(void) { } } -/* - * Called when the user releases a key. We need to leave the Mode_switch - * state when the user releases the Mode_switch key. - * - */ -static void handle_key_release(xcb_key_release_event_t *event) { - xkb_state_update_key(xkb_state, event->detail, XKB_KEY_UP); -} - static void redraw_timeout(EV_P_ ev_timer *w, int revents) { redraw_screen(); + STOP_TIMER(w); +} + +static bool skip_without_validation(void) { + if (input_position != 0) + return false; - ev_timer_stop(main_loop, w); - free(w); + if (skip_repeated_empty_password || ignore_empty_password) + return true; + + return false; } /* @@ -321,7 +290,6 @@ static void handle_key_press(xcb_key_press_event_t *event) { 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. */ memset(buffer, '\0', sizeof(buffer)); @@ -331,7 +299,7 @@ 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) { + if (skip_without_validation()) { clear_input(); return; } @@ -339,8 +307,13 @@ static void handle_key_press(xcb_key_press_event_t *event) { unlock_state = STATE_KEY_PRESSED; redraw_screen(); input_done(); + skip_repeated_empty_password = true; return; + default: + skip_repeated_empty_password = false; + } + switch (ksym) { case XKB_KEY_u: if (ctrl) { DEBUG("C-u pressed\n"); @@ -396,13 +369,10 @@ static void handle_key_press(xcb_key_press_event_t *event) { 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); - } - + struct ev_timer *timeout = NULL; + START_TIMER(timeout, TSTAMP_N_SECS(0.25), redraw_timeout); STOP_TIMER(clear_indicator_timeout); + START_TIMER(discard_passwd_timeout, TSTAMP_N_MINS(3), discard_passwd_cb); } /* @@ -426,11 +396,54 @@ static void handle_visibility_notify(xcb_connection_t *conn, /* * Called when the keyboard mapping changes. We update our symbols. * + * 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. + * */ -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(); +static void process_xkb_event(xcb_generic_event_t *gevent) { + union xkb_event { + struct { + uint8_t response_type; + uint8_t xkbType; + uint16_t sequence; + xcb_timestamp_t time; + uint8_t deviceID; + } any; + xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify; + xcb_xkb_map_notify_event_t map_notify; + xcb_xkb_state_notify_event_t state_notify; + } *event = (union xkb_event*)gevent; + + DEBUG("process_xkb_event for device %d\n", event->any.deviceID); + + if (event->any.deviceID != xkb_x11_get_core_keyboard_device_id(conn)) + return; + + /* + * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap + * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent + * recompilations. + */ + switch (event->any.xkbType) { + case XCB_XKB_NEW_KEYBOARD_NOTIFY: + if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES) + (void)load_keymap(); + break; + + case XCB_XKB_MAP_NOTIFY: + (void)load_keymap(); + break; + + case XCB_XKB_STATE_NOTIFY: + xkb_state_update_mask(xkb_state, + event->state_notify.baseMods, + event->state_notify.latchedMods, + event->state_notify.lockedMods, + event->state_notify.baseGroup, + event->state_notify.latchedGroup, + event->state_notify.lockedGroup); + break; + } } /* @@ -524,6 +537,9 @@ static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { static void xcb_check_cb(EV_P_ ev_check *w, int revents) { xcb_generic_event_t *event; + if (xcb_connection_has_error(conn)) + errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?\n"); + while ((event = xcb_poll_for_event(conn)) != NULL) { if (event->response_type == 0) { xcb_generic_error_t *error = (xcb_generic_error_t*)event; @@ -536,19 +552,19 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { /* 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 (input_position == 0) - turn_monitors_off(); - + * 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: @@ -569,13 +585,13 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { } break; - case XCB_MAPPING_NOTIFY: - handle_mapping_notify((xcb_mapping_notify_event_t*)event); - break; - case XCB_CONFIGURE_NOTIFY: handle_screen_resize(); break; + + default: + if (type == xkb_base_event) + process_xkb_event(event); } free(event); @@ -660,13 +676,16 @@ int main(int argc, char *argv[]) { {"image", required_argument, NULL, 'i'}, {"tiling", no_argument, NULL, 't'}, {"ignore-empty-password", no_argument, NULL, 'e'}, + {"inactivity-timeout", required_argument, NULL, 'I'}, + {"show-failed-attempts", no_argument, NULL, 'f'}, {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:te", longopts, &optind)) != -1) { + char *optstring = "hvnbdc:p:ui:teI:f"; + while ((o = getopt_long(argc, argv, optstring, longopts, &optind)) != -1) { switch (o) { case 'v': errx(EXIT_SUCCESS, "version " VERSION " © 2010-2012 Michael Stapelberg"); @@ -679,6 +698,13 @@ int main(int argc, char *argv[]) { 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; @@ -687,7 +713,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, 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; } @@ -706,7 +732,7 @@ int main(int argc, char *argv[]) { } 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': @@ -716,9 +742,12 @@ int main(int argc, char *argv[]) { if (strcmp(longopts[optind].name, "debug") == 0) debug_mode = true; break; + case 'f': + show_failed_attempts = true; + break; default: - errx(1, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]" - " [-i image.png] [-t] [-e]" + errx(EXIT_FAILURE, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]" + " [-i image.png] [-t] [-e] [-I] [-f]" ); } } @@ -743,16 +772,46 @@ int main(int argc, char *argv[]) { err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK"); #endif - /* Initialize connection to X11 */ - if ((display = XOpenDisplay(NULL)) == NULL) - errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?"); - XSetEventQueueOwner(display, XCBOwnsEventQueue); - conn = XGetXCBConnection(display); - /* Double checking that connection is good and operatable with xcb */ - if (xcb_connection_has_error(conn)) + int screennr; + if ((conn = xcb_connect(NULL, &screennr)) == NULL || + xcb_connection_has_error(conn)) errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?"); + if (xkb_x11_setup_xkb_extension(conn, + XKB_X11_MIN_MAJOR_XKB_VERSION, + XKB_X11_MIN_MINOR_XKB_VERSION, + 0, + NULL, + NULL, + &xkb_base_event, + &xkb_base_error) != 1) + errx(EXIT_FAILURE, "Could not setup XKB extension."); + + static const xcb_xkb_map_part_t required_map_parts = + (XCB_XKB_MAP_PART_KEY_TYPES | + XCB_XKB_MAP_PART_KEY_SYMS | + XCB_XKB_MAP_PART_MODIFIER_MAP | + XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | + XCB_XKB_MAP_PART_KEY_ACTIONS | + XCB_XKB_MAP_PART_VIRTUAL_MODS | + XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP); + + static const xcb_xkb_event_type_t required_events = + (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | + XCB_XKB_EVENT_TYPE_MAP_NOTIFY | + XCB_XKB_EVENT_TYPE_STATE_NOTIFY); + + xcb_xkb_select_events( + conn, + xkb_x11_get_core_keyboard_device_id(conn), + required_events, + 0, + required_events, + required_map_parts, + required_map_parts, + 0); + /* When we cannot initially load the keymap, we better exit */ if (!load_keymap()) errx(EXIT_FAILURE, "Could not load keymap");