Browse Source

Implement a visual unlock indicator

master
Michael Stapelberg 13 years ago
parent
commit
f216517bb0
  1. 1
      Makefile
  2. 2
      debian/control
  3. 7
      i3lock.1
  4. 444
      i3lock.c

1
Makefile

@ -20,6 +20,7 @@ CFLAGS += $(shell pkg-config --cflags xcb-keysyms xcb-dpms)
LIBS += $(shell pkg-config --libs xcb-keysyms xcb-dpms xcb-image)
endif
LIBS += -lpam
LIBS += -lev
FILES:=$(wildcard *.c)
FILES:=$(FILES:.c=.o)

2
debian/control

@ -3,7 +3,7 @@ Section: utils
Priority: optional
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
Build-Depends: debhelper (>= 7.0.50~), libx11-dev, libpam0g-dev, libcairo2-dev, libxcb1-dev, libxcb-dpms0-dev, libxcb-keysyms1-dev, libxcb-image0-dev, pkg-config
Build-Depends: debhelper (>= 7.0.50~), libx11-dev, libpam0g-dev, libcairo2-dev, libxcb1-dev, libxcb-dpms0-dev, libxcb-keysyms1-dev, libxcb-image0-dev, pkg-config, libev-dev
Standards-Version: 3.9.2
Homepage: http://i3wm.org/i3lock/

7
i3lock.1

@ -69,6 +69,13 @@ Enable turning off your screen using DPMS. Note that, when you do not specify th
option, DPMS will turn off your screen after 15 minutes of inactivity anyways (if
you did not disable this in your X server).
.TP
.B \-u, \-\-no-unlock-indicator
Disables the unlock indicator. i3lock will by default show an unlock indicator
after pressing keys. This will give feedback for every keypress and it will
show you the current PAM state (whether your password is currently being
verified or whether it is wrong).
.TP
.BI \-i\ path \fR,\ \fB\-\-image= path
Display the given PNG image instead of a blank screen.

444
i3lock.c

@ -22,6 +22,9 @@
#include <X11/keysym.h>
#include <getopt.h>
#include <string.h>
#include <math.h>
#include <ev.h>
#ifndef NOLIBCAIRO
#include <cairo.h>
@ -33,7 +36,16 @@
#include "xcb.h"
#include "cursors.h"
#define BUTTON_RADIUS 90
#define BUTTON_SPACE (BUTTON_RADIUS + 5)
#define BUTTON_CENTER (BUTTON_RADIUS + 5)
#define BUTTON_DIAMETER (5 * BUTTON_SPACE)
static char color[7] = "ffffff";
static uint32_t last_resolution[2];
static xcb_connection_t *conn;
static xcb_window_t win;
static xcb_visualtype_t *vistype;
static xcb_cursor_t cursor;
static xcb_key_symbols_t *symbols;
static xcb_screen_t *scr;
@ -47,6 +59,26 @@ static int modeswitchmask;
static int numlockmask;
static bool beep = false;
static bool debug_mode = false;
static bool dpms = false;
static bool unlock_indicator = true;
static struct ev_loop *main_loop;
static struct ev_timer *clear_pam_wrong_timeout;
static struct ev_timer *clear_indicator_timeout;
static enum {
STATE_STARTED = 0, /* default state */
STATE_KEY_PRESSED = 1, /* key was pressed, show unlock indicator */
STATE_KEY_ACTIVE = 2, /* a key was pressed recently, highlight part
of the unlock indicator. */
STATE_BACKSPACE_ACTIVE = 3 /* backspace was pressed recently, highlight
part of the unlock indicator in red. */
} unlock_state;
static enum {
STATE_PAM_IDLE = 0, /* no PAM interaction at the moment */
STATE_PAM_VERIFY = 1, /* currently verifying the password via PAM */
STATE_PAM_WRONG = 2 /* the password was wrong */
} pam_state;
#define DEBUG(fmt, ...) do { \
if (debug_mode) \
@ -63,43 +95,234 @@ static bool tile = false;
* resolution and returns it.
*
*/
xcb_pixmap_t draw_image(xcb_visualtype_t *vistype, u_int32_t* resolution, char* color) {
static xcb_pixmap_t draw_image(xcb_visualtype_t *vistype, u_int32_t* resolution) {
xcb_pixmap_t bg_pixmap = XCB_NONE;
#ifndef NOLIBCAIRO
if (!img)
return bg_pixmap;
bg_pixmap = create_bg_pixmap(conn, scr, resolution, color);
/* Initialize cairo */
cairo_surface_t *output;
output = cairo_xcb_surface_create(conn, bg_pixmap, vistype,
resolution[0], resolution[1]);
cairo_t *ctx = cairo_create(output);
if (!tile) {
cairo_set_source_surface(ctx, img, 0, 0);
cairo_paint(ctx);
} else {
/* create a pattern and fill a rectangle as big as the screen */
cairo_pattern_t *pattern;
pattern = cairo_pattern_create_for_surface(img);
cairo_set_source(ctx, pattern);
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_rectangle(ctx, 0, 0, resolution[0], resolution[1]);
cairo_fill(ctx);
cairo_pattern_destroy(pattern);
if (img) {
if (!tile) {
cairo_set_source_surface(ctx, img, 0, 0);
cairo_paint(ctx);
} else {
/* create a pattern and fill a rectangle as big as the screen */
cairo_pattern_t *pattern;
pattern = cairo_pattern_create_for_surface(img);
cairo_set_source(ctx, pattern);
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_rectangle(ctx, 0, 0, resolution[0], resolution[1]);
cairo_fill(ctx);
cairo_pattern_destroy(pattern);
}
}
if (unlock_state >= STATE_KEY_PRESSED && unlock_indicator) {
cairo_pattern_t *outer_pat = NULL;
outer_pat = cairo_pattern_create_linear(0, 0, 0, BUTTON_DIAMETER);
switch (pam_state) {
case STATE_PAM_VERIFY:
cairo_pattern_add_color_stop_rgb(outer_pat, 0, 139.0/255, 0, 250.0/255);
cairo_pattern_add_color_stop_rgb(outer_pat, 1, 51.0/255, 0, 250.0/255);
break;
case STATE_PAM_WRONG:
cairo_pattern_add_color_stop_rgb(outer_pat, 0, 255.0/250, 139.0/255, 0);
cairo_pattern_add_color_stop_rgb(outer_pat, 1, 125.0/255, 51.0/255, 0);
break;
case STATE_PAM_IDLE:
cairo_pattern_add_color_stop_rgb(outer_pat, 0, 139.0/255, 125.0/255, 0);
cairo_pattern_add_color_stop_rgb(outer_pat, 1, 51.0/255, 125.0/255, 0);
break;
}
/* Draw a (centered) circle with transparent background. */
cairo_set_line_width(ctx, 10.0);
cairo_arc(ctx,
(resolution[0] / 2) /* x */,
(resolution[1] / 2) /* y */,
BUTTON_RADIUS /* radius */,
0 /* start */,
2 * M_PI /* end */);
/* Use the appropriate color for the different PAM states
* (currently verifying, wrong password, or default) */
switch (pam_state) {
case STATE_PAM_VERIFY:
cairo_set_source_rgba(ctx, 0, 114.0/255, 255.0/255, 0.75);
break;
case STATE_PAM_WRONG:
cairo_set_source_rgba(ctx, 250.0/255, 0, 0, 0.75);
break;
default:
cairo_set_source_rgba(ctx, 0, 0, 0, 0.75);
break;
}
cairo_fill_preserve(ctx);
cairo_set_source(ctx, outer_pat);
cairo_stroke(ctx);
/* Draw an inner seperator line. */
cairo_set_source_rgb(ctx, 0, 0, 0);
cairo_set_line_width(ctx, 2.0);
cairo_arc(ctx,
(resolution[0] / 2) /* x */,
(resolution[1] / 2) /* y */,
BUTTON_RADIUS - 5 /* radius */,
0,
2 * M_PI);
cairo_stroke(ctx);
cairo_set_line_width(ctx, 10.0);
/* Display a (centered) text of the current PAM state. */
char *text = NULL;
switch (pam_state) {
case STATE_PAM_VERIFY:
text = "verifying…";
break;
case STATE_PAM_WRONG:
text = "wrong!";
break;
default:
break;
}
if (text) {
cairo_text_extents_t extents;
double x, y;
cairo_set_source_rgb(ctx, 0, 0, 0);
cairo_set_font_size(ctx, 28.0);
cairo_text_extents(ctx, text, &extents);
x = (resolution[0] / 2.0) - ((extents.width / 2) + extents.x_bearing);
y = (resolution[1] / 2.0) - ((extents.height / 2) + extents.y_bearing);
cairo_move_to(ctx, x, y);
cairo_show_text(ctx, text);
cairo_close_path(ctx);
}
/* After the user pressed any valid key or the backspace key, we
* highlight a random part of the unlock indicator to confirm this
* keypress. */
if (unlock_state == STATE_KEY_ACTIVE ||
unlock_state == STATE_BACKSPACE_ACTIVE) {
cairo_new_sub_path(ctx);
double highlight_start = (rand() % (int)(2 * M_PI * 100)) / 100.0;
DEBUG("Highlighting part %.2f\n", highlight_start);
cairo_arc(ctx, resolution[0] / 2 /* x */, resolution[1] / 2 /* y */,
BUTTON_RADIUS /* radius */, highlight_start,
highlight_start + (M_PI / 3.0));
if (unlock_state == STATE_KEY_ACTIVE) {
/* For normal keys, we use a lighter green. */
outer_pat = cairo_pattern_create_linear(0, 0, 0, BUTTON_DIAMETER);
cairo_pattern_add_color_stop_rgb(outer_pat, 0, 139.0/255, 219.0/255, 0);
cairo_pattern_add_color_stop_rgb(outer_pat, 1, 51.0/255, 219.0/255, 0);
} else {
/* For backspace, we use red. */
outer_pat = cairo_pattern_create_linear(0, 0, 0, BUTTON_DIAMETER);
cairo_pattern_add_color_stop_rgb(outer_pat, 0, 219.0/255, 139.0/255, 0);
cairo_pattern_add_color_stop_rgb(outer_pat, 1, 219.0/255, 51.0/255, 0);
}
cairo_set_source(ctx, outer_pat);
cairo_stroke(ctx);
/* Draw two little separators for the highlighted part of the
* unlock indicator. */
cairo_set_source_rgb(ctx, 0, 0, 0);
cairo_arc(ctx,
(resolution[0] / 2) /* x */,
(resolution[1] / 2) /* y */,
BUTTON_RADIUS /* radius */,
highlight_start /* start */,
highlight_start + (M_PI / 128.0) /* end */);
cairo_stroke(ctx);
cairo_arc(ctx,
(resolution[0] / 2) /* x */,
(resolution[1] / 2) /* y */,
BUTTON_RADIUS /* radius */,
highlight_start + (M_PI / 3.0) /* start */,
(highlight_start + (M_PI / 3.0)) + (M_PI / 128.0) /* end */);
cairo_stroke(ctx);
}
}
cairo_surface_destroy(output);
cairo_destroy(ctx);
#endif
return bg_pixmap;
}
/*
* Calls draw_image on a new pixmap and swaps that with the current pixmap
*
*/
static void redraw_screen() {
xcb_pixmap_t bg_pixmap = draw_image(vistype, last_resolution);
xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){ bg_pixmap });
/* XXX: Possible optimization: Only update the area in the middle of the
* screen instead of the whole screen. */
xcb_clear_area(conn, 0, win, 0, 0, scr->width_in_pixels, scr->height_in_pixels);
xcb_flush(conn);
}
/*
* Resets pam_state to STATE_PAM_IDLE 2 seconds after an unsuccesful
* authentication event.
*
*/
static void clear_pam_wrong(EV_P_ ev_timer *w, int revents) {
pam_state = STATE_PAM_IDLE;
unlock_state = STATE_STARTED;
redraw_screen();
}
/*
* Hides the unlock indicator completely when there is no content in the
* password buffer.
*
*/
static void clear_indicator(EV_P_ ev_timer *w, int revents) {
DEBUG("Clear indicator\n");
unlock_state = STATE_STARTED;
redraw_screen();
}
/*
* (Re-)starts the clear_indicator timeout. Called after pressing backspace or
* after an unsuccessful authentication attempt.
*
*/
static void start_clear_indicator_timeout() {
if (clear_indicator_timeout) {
ev_timer_stop(main_loop, clear_indicator_timeout);
ev_timer_set(clear_indicator_timeout, 1.0, 0.);
ev_timer_start(main_loop, clear_indicator_timeout);
} else {
clear_indicator_timeout = calloc(sizeof(struct ev_timer), 1);
ev_timer_init(clear_indicator_timeout, clear_indicator, 1.0, 0.);
ev_timer_start(main_loop, clear_indicator_timeout);
}
}
static void input_done() {
if (input_position == 0)
return;
/* TODO: change cursor during authentication? */
if (clear_pam_wrong_timeout) {
ev_timer_stop(main_loop, clear_pam_wrong_timeout);
clear_pam_wrong_timeout = NULL;
}
pam_state = STATE_PAM_VERIFY;
redraw_screen();
if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
printf("successfully authenticated\n");
exit(0);
@ -107,6 +330,16 @@ static void input_done() {
fprintf(stderr, "Authentication failure\n");
pam_state = STATE_PAM_WRONG;
redraw_screen();
/* Clear this state after 2 seconds (unless the user enters another
* password during that time). */
ev_now_update(main_loop);
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);
/* beep on authentication failure, if enabled */
if (beep) {
xcb_bell(conn, 100);
@ -138,6 +371,10 @@ static void handle_key_release(xcb_key_release_event_t *event) {
modeswitch_active, iso_level3_shift_active);
}
static void redraw_timeout(EV_P_ ev_timer *w, int revents) {
redraw_screen();
}
/*
* Handle key presses. Fixes state, then looks up the key symbol for the
* given keycode, then looks up the key symbol (as UCS-2), converts it to
@ -211,6 +448,13 @@ static void handle_key_press(xcb_key_press_event_t *event) {
/* decrement input_position to point to the previous glyph */
u8_dec(password, &input_position);
password[input_position] = '\0';
/* Clear this state after 2 seconds (unless the user enters another
* password during that time). */
start_clear_indicator_timeout();
unlock_state = STATE_BACKSPACE_ACTIVE;
redraw_screen();
unlock_state = STATE_KEY_PRESSED;
//printf("new input position = %d, new password = %s\n", input_position, password);
return;
}
@ -260,6 +504,19 @@ static void handle_key_press(xcb_key_press_event_t *event) {
input_position += convert_ucs_to_utf8((char*)inp, password + input_position);
password[input_position] = '\0';
DEBUG("current password = %s\n", password);
unlock_state = STATE_KEY_ACTIVE;
redraw_screen();
unlock_state = STATE_KEY_PRESSED;
struct ev_timer *timeout = calloc(sizeof(struct ev_timer), 1);
ev_timer_init(timeout, redraw_timeout, 0.25, 0.);
ev_timer_start(main_loop, timeout);
if (clear_indicator_timeout) {
ev_timer_stop(main_loop, clear_indicator_timeout);
clear_indicator_timeout = NULL;
}
}
/*
@ -296,7 +553,7 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
* and also redraw the image, if any.
*
*/
void handle_screen_resize(xcb_visualtype_t *vistype, xcb_window_t win, uint32_t* last_resolution, char* color) {
void handle_screen_resize(xcb_visualtype_t *vistype, xcb_window_t win, uint32_t* last_resolution) {
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
geomc = xcb_get_geometry(conn, scr->root);
@ -312,7 +569,7 @@ void handle_screen_resize(xcb_visualtype_t *vistype, xcb_window_t win, uint32_t*
#ifndef NOLIBCAIRO
if (img) {
xcb_pixmap_t bg_pixmap = draw_image(vistype, last_resolution, color);
xcb_pixmap_t bg_pixmap = draw_image(vistype, last_resolution);
xcb_change_window_attributes(conn, win, XCB_CW_BACK_PIXMAP, (uint32_t[1]){ bg_pixmap });
}
#endif
@ -354,10 +611,82 @@ static int conv_callback(int num_msg, const struct pam_message **msg,
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;
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);
if (type == XCB_KEY_PRESS) {
handle_key_press((xcb_key_press_event_t*)event);
continue;
}
if (type == 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);
continue;
}
if (type == XCB_VISIBILITY_NOTIFY) {
handle_visibility_notify((xcb_visibility_notify_event_t*)event);
continue;
}
if (type == XCB_MAPPING_NOTIFY) {
handle_mapping_notify((xcb_mapping_notify_event_t*)event);
continue;
}
if (type == XCB_CONFIGURE_NOTIFY) {
handle_screen_resize(vistype, win, last_resolution);
continue;
}
printf("WARNING: unhandled event of type %d\n", type);
free(event);
}
}
int main(int argc, char *argv[]) {
bool dont_fork = false;
bool dpms = false;
char color[7] = "ffffff";
char *username;
#ifndef NOLIBCAIRO
char *image_path = NULL;
@ -365,9 +694,6 @@ int main(int argc, char *argv[]) {
int ret;
struct pam_conv conv = {conv_callback, NULL};
int screen;
xcb_visualtype_t *vistype;
xcb_generic_event_t *event;
xcb_window_t win;
int curs_choice = CURS_NONE;
char o;
int optind = 0;
@ -380,6 +706,7 @@ int main(int argc, char *argv[]) {
{"pointer", required_argument, NULL , 'p'},
{"debug", no_argument, NULL, 0},
{"help", no_argument, NULL, 'h'},
{"no-unlock-indicator", no_argument, NULL, 'u'},
#ifndef NOLIBCAIRO
{"image", required_argument, NULL, 'i'},
{"tiling", no_argument, NULL, 't'},
@ -390,7 +717,7 @@ int main(int argc, char *argv[]) {
if ((username = getenv("USER")) == NULL)
errx(1, "USER environment variable not set, please set it.\n");
while ((o = getopt_long(argc, argv, "hvnbdc:p:"
while ((o = getopt_long(argc, argv, "hvnbdc:p:u"
#ifndef NOLIBCAIRO
"i:t"
#endif
@ -419,6 +746,9 @@ int main(int argc, char *argv[]) {
break;
}
case 'u':
unlock_indicator = false;
break;
#ifndef NOLIBCAIRO
case 'i':
image_path = strdup(optarg);
@ -441,7 +771,7 @@ int main(int argc, char *argv[]) {
debug_mode = true;
break;
default:
errx(1, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-p win|default]"
errx(1, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]"
#ifndef NOLIBCAIRO
" [-i image.png] [-t]"
#else
@ -451,6 +781,10 @@ int main(int argc, char *argv[]) {
}
}
/* 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)
@ -480,8 +814,8 @@ int main(int argc, char *argv[]) {
scr = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
vistype = get_root_visual_type(scr);
uint32_t last_resolution[2] = {scr->width_in_pixels, scr->height_in_pixels};
last_resolution[0] = scr->width_in_pixels;
last_resolution[1] = scr->height_in_pixels;
#ifndef NOLIBCAIRO
@ -492,7 +826,7 @@ int main(int argc, char *argv[]) {
#endif
/* Pixmap on which the image is rendered to (if any) */
xcb_pixmap_t bg_pixmap = draw_image(vistype, last_resolution, color);
xcb_pixmap_t bg_pixmap = draw_image(vistype, last_resolution);
/* open the fullscreen window, already with the correct pixmap in place */
win = open_fullscreen_window(conn, scr, color, bg_pixmap);
@ -508,46 +842,24 @@ int main(int argc, char *argv[]) {
if (dpms)
dpms_turn_off_screen(conn);
while ((event = xcb_wait_for_event(conn))) {
if (event->response_type == 0)
errx(1, "XCB: Invalid event received");
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
if (type == XCB_KEY_PRESS) {
handle_key_press((xcb_key_press_event_t*)event);
continue;
}
if (type == 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);
continue;
}
/* Initialize the libev event loop. */
main_loop = EV_DEFAULT;
if (main_loop == NULL)
errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?\n");
if (type == XCB_VISIBILITY_NOTIFY) {
handle_visibility_notify((xcb_visibility_notify_event_t*)event);
continue;
}
struct ev_io *xcb_watcher = calloc(sizeof(struct ev_io), 1);
struct ev_check *xcb_check = calloc(sizeof(struct ev_check), 1);
struct ev_prepare *xcb_prepare = calloc(sizeof(struct ev_prepare), 1);
if (type == XCB_MAPPING_NOTIFY) {
handle_mapping_notify((xcb_mapping_notify_event_t*)event);
continue;
}
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(main_loop, xcb_watcher);
if (type == XCB_CONFIGURE_NOTIFY) {
handle_screen_resize(vistype, win, last_resolution, color);
continue;
}
ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(main_loop, xcb_check);
printf("WARNING: unhandled event of type %d\n", type);
}
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
ev_prepare_start(main_loop, xcb_prepare);
return 0;
xcb_flush(conn);
ev_loop(main_loop, 0);
}

Loading…
Cancel
Save