Separate input and auth state

This commit establishes separate state machines for auth state (whether
the password submitted is being verified or is wrong) and input state
(typing indicators and clear message -- things relevant to the state of
the password being typed in, before it is submitted.) This makes it
possible to display the auth state while updating the input state (for
example, show that the previously submitted password is 'verifying' or
'wrong' while typing another.)

The two state machines interact only when submitting a password. There
is some interference with the rendering code -- a 'cleared' message
from the input state machine supersedes verifying/wrong messages from
the auth state machine; although since the 'clear' state has a shorter
timeout than the auth 'invalid' state, this is unlikely to hide the 'wrong'
message.
This commit is contained in:
Manuel Stoeckl 2023-05-02 21:00:41 -04:00 committed by Simon Ser
parent 876965f944
commit 31ebd85fe0
4 changed files with 98 additions and 59 deletions

View File

@ -8,14 +8,20 @@
#include "pool-buffer.h"
#include "seat.h"
// Indicator state: status of authentication attempt
enum auth_state {
AUTH_STATE_IDLE,
AUTH_STATE_CLEAR,
AUTH_STATE_INPUT,
AUTH_STATE_INPUT_NOP,
AUTH_STATE_BACKSPACE,
AUTH_STATE_VALIDATING,
AUTH_STATE_INVALID,
AUTH_STATE_IDLE, // nothing happening
AUTH_STATE_VALIDATING, // currently validating password
AUTH_STATE_INVALID, // displaying message: password was wrong
};
// Indicator state: status of password buffer / typing letters
enum input_state {
INPUT_STATE_IDLE, // nothing happening; other states decay to this after time
INPUT_STATE_CLEAR, // displaying message: password buffer was cleared
INPUT_STATE_LETTER, // pressed a key that input a letter
INPUT_STATE_BACKSPACE, // pressed backspace and removed a letter
INPUT_STATE_NEUTRAL, // pressed a key (like Ctrl) that did nothing
};
struct swaylock_colorset {
@ -73,7 +79,8 @@ struct swaylock_password {
struct swaylock_state {
struct loop *eventloop;
struct loop_timer *clear_indicator_timer; // clears the indicator
struct loop_timer *input_idle_timer; // timer to reset input state to IDLE
struct loop_timer *auth_idle_timer; // timer to stop displaying AUTH_STATE_INVALID
struct loop_timer *clear_password_timer; // clears the password buffer
struct wl_display *display;
struct wl_compositor *compositor;
@ -86,7 +93,8 @@ struct swaylock_state {
struct swaylock_xkb xkb;
cairo_surface_t *test_surface;
cairo_t *test_cairo; // used to estimate font/text sizes
enum auth_state auth_state;
enum auth_state auth_state; // state of the authentication attempt
enum input_state input_state; // state of the password buffer and key inputs
uint32_t highlight_start; // position of highlight; 2048 = 1 full turn
int failed_attempts;
bool run_display, locked;
@ -129,7 +137,7 @@ void render_frame(struct swaylock_surface *surface);
void damage_surface(struct swaylock_surface *surface);
void damage_state(struct swaylock_state *state);
void clear_password_buffer(struct swaylock_password *pw);
void schedule_indicator_clear(struct swaylock_state *state);
void schedule_auth_idle(struct swaylock_state *state);
void initialize_pw_backend(int argc, char **argv);
void run_pw_backend_child(void);

2
main.c
View File

@ -1072,7 +1072,7 @@ static void comm_in(int fd, short mask, void *data) {
state.run_display = false;
} else {
state.auth_state = AUTH_STATE_INVALID;
schedule_indicator_clear(&state);
schedule_auth_idle(&state);
++state.failed_attempts;
damage_state(&state);
}

View File

@ -46,28 +46,50 @@ static void append_ch(struct swaylock_password *pw, uint32_t codepoint) {
pw->len += utf8_size;
}
static void clear_indicator(void *data) {
static void set_input_idle(void *data) {
struct swaylock_state *state = data;
state->clear_indicator_timer = NULL;
state->input_idle_timer = NULL;
state->input_state = INPUT_STATE_IDLE;
damage_state(state);
}
static void set_auth_idle(void *data) {
struct swaylock_state *state = data;
state->auth_idle_timer = NULL;
state->auth_state = AUTH_STATE_IDLE;
damage_state(state);
}
void schedule_indicator_clear(struct swaylock_state *state) {
if (state->clear_indicator_timer) {
loop_remove_timer(state->eventloop, state->clear_indicator_timer);
static void schedule_input_idle(struct swaylock_state *state) {
if (state->input_idle_timer) {
loop_remove_timer(state->eventloop, state->input_idle_timer);
}
state->clear_indicator_timer = loop_add_timer(
state->eventloop, 3000, clear_indicator, state);
state->input_idle_timer = loop_add_timer(
state->eventloop, 1500, set_input_idle, state);
}
static void cancel_input_idle(struct swaylock_state *state) {
if (state->input_idle_timer) {
loop_remove_timer(state->eventloop, state->input_idle_timer);
state->input_idle_timer = NULL;
}
}
void schedule_auth_idle(struct swaylock_state *state) {
if (state->auth_idle_timer) {
loop_remove_timer(state->eventloop, state->auth_idle_timer);
}
state->auth_idle_timer = loop_add_timer(
state->eventloop, 3000, set_auth_idle, state);
}
static void clear_password(void *data) {
struct swaylock_state *state = data;
state->clear_password_timer = NULL;
state->auth_state = AUTH_STATE_CLEAR;
state->input_state = INPUT_STATE_CLEAR;
schedule_input_idle(state);
clear_password_buffer(&state->password);
damage_state(state);
schedule_indicator_clear(state);
}
static void schedule_password_clear(struct swaylock_state *state) {
@ -78,16 +100,26 @@ static void schedule_password_clear(struct swaylock_state *state) {
state->eventloop, 10000, clear_password, state);
}
static void cancel_password_clear(struct swaylock_state *state) {
if (state->clear_password_timer) {
loop_remove_timer(state->eventloop, state->clear_password_timer);
state->clear_password_timer = NULL;
}
}
static void submit_password(struct swaylock_state *state) {
if (state->args.ignore_empty && state->password.len == 0) {
return;
}
state->input_state = INPUT_STATE_IDLE;
state->auth_state = AUTH_STATE_VALIDATING;
cancel_password_clear(state);
cancel_input_idle(state);
if (!write_comm_request(&state->password)) {
state->auth_state = AUTH_STATE_INVALID;
schedule_indicator_clear(state);
schedule_auth_idle(state);
}
damage_state(state);
@ -110,20 +142,22 @@ void swaylock_handle_key(struct swaylock_state *state,
case XKB_KEY_Delete:
case XKB_KEY_BackSpace:
if (backspace(&state->password)) {
state->auth_state = AUTH_STATE_BACKSPACE;
state->input_state = INPUT_STATE_BACKSPACE;
schedule_password_clear(state);
update_highlight(state);
} else {
state->auth_state = AUTH_STATE_CLEAR;
state->input_state = INPUT_STATE_CLEAR;
cancel_password_clear(state);
}
schedule_input_idle(state);
damage_state(state);
schedule_indicator_clear(state);
schedule_password_clear(state);
break;
case XKB_KEY_Escape:
clear_password_buffer(&state->password);
state->auth_state = AUTH_STATE_CLEAR;
state->input_state = INPUT_STATE_CLEAR;
cancel_password_clear(state);
schedule_input_idle(state);
damage_state(state);
schedule_indicator_clear(state);
break;
case XKB_KEY_Caps_Lock:
case XKB_KEY_Shift_L:
@ -136,10 +170,10 @@ void swaylock_handle_key(struct swaylock_state *state,
case XKB_KEY_Alt_R:
case XKB_KEY_Super_L:
case XKB_KEY_Super_R:
state->auth_state = AUTH_STATE_INPUT_NOP;
damage_state(state);
schedule_indicator_clear(state);
state->input_state = INPUT_STATE_NEUTRAL;
schedule_password_clear(state);
schedule_input_idle(state);
damage_state(state);
break;
case XKB_KEY_m: /* fallthrough */
case XKB_KEY_d:
@ -153,20 +187,21 @@ void swaylock_handle_key(struct swaylock_state *state,
case XKB_KEY_u:
if (state->xkb.control) {
clear_password_buffer(&state->password);
state->auth_state = AUTH_STATE_CLEAR;
state->input_state = INPUT_STATE_CLEAR;
cancel_password_clear(state);
schedule_input_idle(state);
damage_state(state);
schedule_indicator_clear(state);
break;
}
// fallthrough
default:
if (codepoint) {
append_ch(&state->password, codepoint);
state->auth_state = AUTH_STATE_INPUT;
state->input_state = INPUT_STATE_LETTER;
schedule_password_clear(state);
schedule_input_idle(state);
update_highlight(state);
damage_state(state);
schedule_indicator_clear(state);
schedule_password_clear(state);
}
break;
}

View File

@ -12,12 +12,12 @@ const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f;
static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state,
struct swaylock_colorset *colorset) {
if (state->auth_state == AUTH_STATE_VALIDATING) {
if (state->input_state == INPUT_STATE_CLEAR) {
cairo_set_source_u32(cairo, colorset->cleared);
} else if (state->auth_state == AUTH_STATE_VALIDATING) {
cairo_set_source_u32(cairo, colorset->verifying);
} else if (state->auth_state == AUTH_STATE_INVALID) {
cairo_set_source_u32(cairo, colorset->wrong);
} else if (state->auth_state == AUTH_STATE_CLEAR) {
cairo_set_source_u32(cairo, colorset->cleared);
} else {
if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
cairo_set_source_u32(cairo, colorset->caps_lock);
@ -107,20 +107,20 @@ void render_frame(struct swaylock_surface *surface) {
char *text = NULL;
const char *layout_text = NULL;
if (state->args.show_indicator) {
switch (state->auth_state) {
case AUTH_STATE_VALIDATING:
text = "Verifying";
break;
case AUTH_STATE_INVALID:
text = "Wrong";
break;
case AUTH_STATE_CLEAR:
bool draw_indicator = state->args.show_indicator &&
(state->auth_state != AUTH_STATE_IDLE ||
state->input_state != INPUT_STATE_IDLE ||
state->args.indicator_idle_visible);
if (draw_indicator) {
if (state->input_state == INPUT_STATE_CLEAR) {
// This message has highest priority
text = "Cleared";
break;
case AUTH_STATE_INPUT:
case AUTH_STATE_INPUT_NOP:
case AUTH_STATE_BACKSPACE:
} else if (state->auth_state == AUTH_STATE_VALIDATING) {
text = "Verifying";
} else if (state->auth_state == AUTH_STATE_INVALID) {
text = "Wrong";
} else {
// Caps Lock has higher priority
if (state->xkb.caps_lock && state->args.show_caps_lock_text) {
text = "Caps Lock";
@ -148,9 +148,6 @@ void render_frame(struct swaylock_surface *surface) {
// will handle invalid index if none are active
layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout);
}
break;
default:
break;
}
}
@ -230,8 +227,7 @@ void render_frame(struct swaylock_surface *surface) {
float type_indicator_border_thickness =
TYPE_INDICATOR_BORDER_THICKNESS * surface->scale;
if (state->args.show_indicator && (state->auth_state != AUTH_STATE_IDLE ||
state->args.indicator_idle_visible)) {
if (draw_indicator) {
// Fill inner circle
cairo_set_line_width(cairo, 0);
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
@ -269,13 +265,13 @@ void render_frame(struct swaylock_surface *surface) {
}
// Typing indicator: Highlight random part on keypress
if (state->auth_state == AUTH_STATE_INPUT
|| state->auth_state == AUTH_STATE_BACKSPACE) {
if (state->input_state == INPUT_STATE_LETTER ||
state->input_state == INPUT_STATE_BACKSPACE) {
double highlight_start = state->highlight_start * (M_PI / 1024.0);
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
arc_radius, highlight_start,
highlight_start + TYPE_INDICATOR_RANGE);
if (state->auth_state == AUTH_STATE_INPUT) {
if (state->input_state == INPUT_STATE_LETTER) {
if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight);
} else {