aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Magahern <james@magahern.com>2019-01-20 20:57:52 -0800
committerJames Magahern <james@magahern.com>2019-01-20 20:58:13 -0800
commit4b8abf88387a1bf732afd5c1a6abc5b8b486341c (patch)
treea50c6c6a6bcc51769c5b74c164488cf0f2115ae6
parentRed flash animation on bad password (diff)
Spinner animation and tweaks to memory management
-rw-r--r--resources/buzzsaver.gresource.xml1
-rw-r--r--resources/spinner.svg8
-rw-r--r--src/animation.h6
-rw-r--r--src/auth.c24
-rw-r--r--src/auth.h4
-rw-r--r--src/main.c58
-rw-r--r--src/render.c146
-rw-r--r--src/render.h25
8 files changed, 192 insertions, 80 deletions
diff --git a/resources/buzzsaver.gresource.xml b/resources/buzzsaver.gresource.xml
index 7523c4a..614bda5 100644
--- a/resources/buzzsaver.gresource.xml
+++ b/resources/buzzsaver.gresource.xml
@@ -3,6 +3,7 @@
<gresource prefix="/">
<file>resources/asterisk.svg</file>
<file>resources/logo.svg</file>
+ <file>resources/spinner.svg</file>
</gresource>
</gresources>
diff --git a/resources/spinner.svg b/resources/spinner.svg
new file mode 100644
index 0000000..339dccb
--- /dev/null
+++ b/resources/spinner.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="fill:white;enable-background:new 0 0 512 512;" xml:space="preserve"><link xmlns="" type="text/css" id="dark-mode" rel="stylesheet" href=""/><style xmlns="" type="text/css" id="dark-mode-custom-style"/>
+<g>
+ <path d="M96,255.006c0-6.09,0.352-12.098,1.015-18.011l-92.49-30.052C1.567,222.513,0,238.575,0,255.006 c0,73.615,31.083,139.96,80.827,186.662l57.142-78.647C111.907,334.557,96,296.641,96,255.006z M416,255.006 c0,41.634-15.906,79.55-41.969,108.014l57.143,78.647C480.916,394.967,512,328.621,512,255.006c0-16.431-1.566-32.493-4.523-48.063 l-92.49,30.052C415.648,242.909,416,248.917,416,255.006z M288,98.21c45.967,9.331,84.771,38.371,107.225,77.913l92.49-30.051 C451.115,68.362,376.594,12.042,288,0.994V98.21z M116.775,176.123c22.453-39.542,61.258-68.582,107.225-77.913V0.994 C135.406,12.042,60.885,68.362,24.287,146.071L116.775,176.123z M322.277,400.67c-20.195,9.205-42.635,14.336-66.277,14.336 c-23.642,0-46.083-5.131-66.277-14.334l-57.146,78.654c36.603,20.184,78.668,31.68,123.423,31.68 c44.756,0,86.82-11.496,123.424-31.68L322.277,400.67z"/>
+</g>
+</svg>
diff --git a/src/animation.h b/src/animation.h
index a74bb60..108fbe2 100644
--- a/src/animation.h
+++ b/src/animation.h
@@ -23,6 +23,7 @@ typedef enum {
ACursorAnimation,
ALogoAnimation,
ARedFlashAnimation,
+ ASpinnerAnimation,
} AnimationType;
typedef struct {
@@ -39,10 +40,15 @@ typedef struct {
unsigned flash_count;
} RedFlashAnimation;
+typedef struct {
+ double rotation;
+} SpinnerAnimation;
+
typedef union {
CursorAnimation cursor_anim;
LogoAnimation logo_anim;
RedFlashAnimation redflash_anim;
+ SpinnerAnimation spinner_anim;
} Animation;
typedef struct {
diff --git a/src/auth.c b/src/auth.c
index 97e934f..505d9e8 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -20,8 +20,8 @@ struct auth_handle_t {
void *context;
auth_callbacks_t callbacks;
- sem_t prompt_semaphore;
- auth_prompt_response_t *prompt_response;
+ sem_t prompt_semaphore;
+ auth_prompt_response_t prompt_response;
};
int process_message(const struct pam_message *msg, struct pam_response *resp, struct auth_handle_t *handle)
@@ -33,9 +33,12 @@ int process_message(const struct pam_message *msg, struct pam_response *resp, st
sem_wait(&handle->prompt_semaphore);
- auth_prompt_response_t *response = handle->prompt_response;
- resp->resp = response->response_buffer;
- resp->resp_retcode = response->response_code;
+ auth_prompt_response_t response = handle->prompt_response;
+
+ // resp is freed by libpam
+ resp->resp = malloc(MAX_RESPONSE_SIZE);
+ strncpy(resp->resp, response.response_buffer, MAX_RESPONSE_SIZE);
+ resp->resp_retcode = 0; // docs say this should always be zero
break;
}
case PAM_ERROR_MSG:
@@ -54,8 +57,8 @@ int perform_conversation(int num_msg, const struct pam_message **msg, struct pam
*resp = calloc(num_msg, sizeof(struct pam_response));
struct auth_handle_t *handle = (struct auth_handle_t *)data;
- for (unsigned i = 0; i < num_msg; ++i) {
- process_message(msg[i], resp[i], handle);
+ for (unsigned i = 0; i < num_msg; i++) {
+ process_message(&(( *msg )[i]), & (( *resp )[i]), handle);
}
return PAM_SUCCESS;
@@ -105,7 +108,6 @@ struct auth_handle_t* auth_begin_authentication(auth_callbacks_t callbacks, void
struct auth_handle_t *handle = malloc(sizeof(struct auth_handle_t));
handle->callbacks = callbacks;
handle->context = context;
- handle->prompt_response = NULL;
sem_init(&handle->prompt_semaphore, 0, 0);
pthread_t auth_thread;
@@ -119,11 +121,7 @@ struct auth_handle_t* auth_begin_authentication(auth_callbacks_t callbacks, void
void auth_attempt_authentication(struct auth_handle_t *handle, auth_prompt_response_t response)
{
- if (handle->prompt_response == NULL) {
- handle->prompt_response = malloc(sizeof(auth_prompt_response_t));
- }
- handle->prompt_response = memcpy(handle->prompt_response, &response, sizeof(auth_prompt_response_t));
-
+ memcpy(&handle->prompt_response, &response, sizeof(auth_prompt_response_t));
sem_post(&handle->prompt_semaphore);
}
diff --git a/src/auth.h b/src/auth.h
index 462e77b..ad09236 100644
--- a/src/auth.h
+++ b/src/auth.h
@@ -7,8 +7,10 @@
#pragma once
+#define MAX_RESPONSE_SIZE 128
+
typedef struct {
- char *response_buffer;
+ char response_buffer[MAX_RESPONSE_SIZE];
int response_code;
} auth_prompt_response_t;
diff --git a/src/main.c b/src/main.c
index ddaea43..f7b75d3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -16,7 +16,6 @@
#include <unistd.h>
static const int kXSecureLockCharFD = 0;
-static const size_t kMaxPasswordLength = 128;
static const int kDefaultWidth = 1024;
static const int kDefaultHeight = 768;
@@ -69,7 +68,7 @@ static void handle_xsl_key_input(saver_state_t *state, const char c)
accept_password(state);
break;
default:
- if (pw_len + 1 < state->password_buffer_len) {
+ if (pw_len + 1 < kMaxPasswordLength) {
password_buf[pw_len] = c;
password_buf[pw_len + 1] = '\0';
}
@@ -95,9 +94,9 @@ static void handle_key_event(saver_state_t *state, XKeyEvent *event)
} else if (XK_Return == key) {
accept_password(state);
} else if (strlen(keybuf) > 0) {
- size_t add_len = strlen(keybuf);
- if ( (length + add_len) < state->password_buffer_len - 1 ) {
- strncpy(password_buf + length, keybuf, add_len + 1);
+ if (length + 1 < kMaxPasswordLength) {
+ password_buf[length] = keybuf[0];
+ password_buf[length + 1] = '\0';
}
}
}
@@ -152,18 +151,25 @@ static void clear_password(saver_state_t *state)
static void accept_password(saver_state_t *state)
{
- size_t pw_length = strlen(state->password_buffer);
- char *password_buf = malloc(pw_length);
- strncpy(password_buf, state->password_buffer, pw_length + 1);
-
auth_prompt_response_t response;
- response.response_buffer = password_buf;
+ strncpy(response.response_buffer, state->password_buffer, MAX_RESPONSE_SIZE);
response.response_code = 0;
auth_attempt_authentication(state->auth_handle, response);
// Block input until we hear back from the auth thread
state->is_processing = true;
state->input_allowed = false;
+
+ // Spinner animation
+ if (state->spinner_anim_key == ANIM_KEY_NOEXIST) {
+ state->spinner_anim_key = schedule_animation(state, (animation_t) {
+ .type = ASpinnerAnimation,
+ .anim.spinner_anim = { 0 }
+ });
+ }
+
+ // Update prompt
+ set_password_prompt(state, "Authenticating...");
}
static void ending_animation_completed(struct animation_t *animation, void *context)
@@ -174,6 +180,17 @@ static void ending_animation_completed(struct animation_t *animation, void *cont
static void authentication_accepted(saver_state_t *state)
{
+ state->is_processing = false;
+ set_password_prompt(state, "Welcome");
+ clear_password(state);
+
+ // Stop cursor animation
+ animation_t *cursor_anim = get_animation_for_key(state, state->cursor_anim_key);
+ if (cursor_anim) {
+ cursor_anim->anim.cursor_anim.cursor_animating = false;
+ state->cursor_opacity = 0.0;
+ }
+
animation_t out_animation = {
.type = ALogoAnimation,
.completion_func = ending_animation_completed,
@@ -204,22 +221,18 @@ static void authentication_rejected(saver_state_t *state)
void callback_show_info(const char *info_msg, void *context)
{
- saver_state(context)->password_prompt = info_msg;
+ set_password_prompt(saver_state(context), info_msg);
}
void callback_show_error(const char *error_msg, void *context)
{
- saver_state(context)->password_prompt = error_msg;
+ set_password_prompt(saver_state(context), error_msg);
}
void callback_prompt_user(const char *prompt, void *context)
{
- size_t prompt_len = strlen(prompt);
- char *new_prompt = malloc(prompt_len);
- strncpy(new_prompt, prompt, prompt_len + 1);
-
saver_state_t *state = saver_state(context);
- state->password_prompt = new_prompt;
+ set_password_prompt(state, prompt);
state->input_allowed = true;
state->is_processing = false;
}
@@ -236,7 +249,7 @@ void callback_authentication_result(int result, void *context)
}
/*
- * Main drawing/update routines
+ * Main drawing/update routines
*/
static void update(saver_state_t *state)
@@ -266,12 +279,10 @@ static int runloop(cairo_surface_t *surface)
state.cursor_opacity = 1.0;
state.pango_layout = pango_layout;
state.status_font = status_font;
- state.password_buffer = calloc(1, kMaxPasswordLength);
- state.password_buffer_len = kMaxPasswordLength;
state.input_allowed = false;
- state.password_prompt = "";
state.is_authenticated = false;
state.is_processing = false;
+ state.spinner_anim_key = ANIM_KEY_NOEXIST;
// Add initial animations
// Cursor animation -- repeats indefinitely
@@ -282,7 +293,7 @@ static int runloop(cairo_surface_t *surface)
.cursor_animating = true
}
};
- schedule_animation(&state, cursor_animation);
+ state.cursor_anim_key = schedule_animation(&state, cursor_animation);
// Logo incoming animation
animation_t logo_animation = {
@@ -307,6 +318,9 @@ static int runloop(cairo_surface_t *surface)
state.auth_handle = auth_begin_authentication(callbacks, &state);
+ // XXX: hack for my desktop until i get multiple monitors working
+ //cairo_translate(cr, 2304.0, 0.0);
+
// Main run loop
const int frames_per_sec = 60;
const long sleep_nsec = (1.0 / frames_per_sec) * 1000000000;
diff --git a/src/render.c b/src/render.c
index 9f2c1b1..908238e 100644
--- a/src/render.c
+++ b/src/render.c
@@ -33,27 +33,34 @@ GBytes* get_data_for_resource(const char *resource_path)
return result;
}
+void set_password_prompt(saver_state_t *state, const char *prompt)
+{
+ strncpy(state->password_prompt, prompt, kMaxPromptLength - 1);
+}
+
static void update_single_animation(saver_state_t *state, animation_t *anim)
{
// Cursor animation
if (anim->type == ACursorAnimation) {
CursorAnimation *ca = &anim->anim.cursor_anim;
- if (ca->cursor_animating && !state->is_processing) {
- const double cursor_fade_speed = 0.05;
- if (ca->cursor_fade_direction > 0) {
- state->cursor_opacity += cursor_fade_speed;
- if (state->cursor_opacity > 1.0) {
- ca->cursor_fade_direction *= -1;
+ if (ca->cursor_animating) {
+ if (!state->is_processing) {
+ const double cursor_fade_speed = 0.05;
+ if (ca->cursor_fade_direction > 0) {
+ state->cursor_opacity += cursor_fade_speed;
+ if (state->cursor_opacity > 1.0) {
+ ca->cursor_fade_direction *= -1;
+ }
+ } else {
+ state->cursor_opacity -= cursor_fade_speed;
+ if (state->cursor_opacity <= 0.0) {
+ ca->cursor_fade_direction *= -1;
+ }
}
} else {
- state->cursor_opacity -= cursor_fade_speed;
- if (state->cursor_opacity <= 0.0) {
- ca->cursor_fade_direction *= -1;
- }
+ state->cursor_opacity = 1.0;
}
- } else {
- state->cursor_opacity = 1.0;
}
}
@@ -113,6 +120,11 @@ static void update_single_animation(saver_state_t *state, animation_t *anim)
anim->completed = completed;
state->background_redshift = progress;
}
+
+ // Spinner animation
+ else if (anim->type == ASpinnerAnimation) {
+ anim->anim.spinner_anim.rotation += 0.07;
+ }
}
static unsigned next_anim_index(saver_state_t *state, unsigned cur_idx)
@@ -126,19 +138,38 @@ static unsigned next_anim_index(saver_state_t *state, unsigned cur_idx)
return idx;
}
-void schedule_animation(saver_state_t *state, animation_t anim)
+animation_key_t schedule_animation(saver_state_t *state, animation_t anim)
{
anim.start_time = anim_now();
// Find next empty element
+ animation_key_t key = 0;
for (unsigned idx = 0; idx < kMaxAnimations; idx++) {
animation_t check_anim = state->animations[idx];
if (check_anim.type == _EmptyAnimationType) {
+ key = idx;
state->animations[idx] = anim;
state->num_animations++;
break;
}
}
+
+ return key;
+}
+
+void remove_animation(saver_state_t *state, animation_key_t key)
+{
+ state->animations[key].type = _EmptyAnimationType;
+}
+
+animation_t* get_animation_for_key(saver_state_t *state, animation_key_t anim_key)
+{
+ animation_t *animation = NULL;
+ if (state->animations[anim_key].type != _EmptyAnimationType) {
+ animation = &state->animations[anim_key];
+ }
+
+ return animation;
}
void update_animations(saver_state_t *state)
@@ -151,7 +182,7 @@ void update_animations(saver_state_t *state)
update_single_animation(state, anim);
if (anim->completed) {
- state->animations[idx].type = _EmptyAnimationType;
+ remove_animation(state, idx);
if (anim->completion_func != NULL) {
anim->completion_func((struct animation_t *)anim, anim->completion_func_context);
}
@@ -167,6 +198,23 @@ void update_animations(saver_state_t *state)
state->num_animations -= completed_animations;
}
+RsvgHandle* load_svg_for_resource_path(const char *resource_path)
+{
+ GError *error = NULL;
+ GBytes *bytes = get_data_for_resource(resource_path);
+ RsvgHandle *handle = NULL;
+
+ gsize size = 0;
+ gconstpointer data = g_bytes_get_data(bytes, &size);
+ handle = rsvg_handle_new_from_data(data, size, &error);
+ g_bytes_unref(bytes);
+ if (error != NULL) {
+ fprintf(stderr, "Error loading SVG at resource path: %s\n", resource_path);
+ }
+
+ return handle;
+}
+
void draw_background(saver_state_t *state)
{
// Draw background
@@ -178,20 +226,9 @@ void draw_background(saver_state_t *state)
void draw_logo(saver_state_t *state)
{
if (state->logo_svg_handle == NULL) {
- GError *error = NULL;
- GBytes *bytes = get_data_for_resource("/resources/logo.svg");
-
- gsize size = 0;
- gconstpointer data = g_bytes_get_data(bytes, &size);
- state->logo_svg_handle = rsvg_handle_new_from_data(data, size, &error);
- g_bytes_unref(bytes);
- if (error != NULL) {
- fprintf(stderr, "Error loading logo SVG\n");
- return;
- }
+ state->logo_svg_handle = load_svg_for_resource_path("/resources/logo.svg");
}
-
cairo_t *cr = state->ctx;
cairo_save(cr);
@@ -230,8 +267,8 @@ void draw_password_field(saver_state_t *state)
// Common color for status and password field
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, state->password_opacity);
- // Draw status text
- const char *prompt = (state->password_prompt ?: "???");
+ // Measure status text
+ const char *prompt = state->password_prompt;
pango_layout_set_font_description(state->pango_layout, state->status_font);
pango_layout_set_text(state->pango_layout, prompt, -1);
@@ -239,22 +276,50 @@ void draw_password_field(saver_state_t *state)
pango_layout_get_size(state->pango_layout, &t_width, &t_height);
double line_height = t_height / PANGO_SCALE;
- cairo_move_to(cr, field_x, field_y - line_height - field_padding);
+ // Measure processing indicator
+ double spinner_width = 0.0;
+ double spinner_scale_factor = 0.0;
+ RsvgDimensionData spinner_dimensions;
+ if (state->is_processing) {
+ if (state->spinner_svg_handle == NULL) {
+ state->spinner_svg_handle = load_svg_for_resource_path("/resources/spinner.svg");
+ }
+
+ rsvg_handle_get_dimensions(state->spinner_svg_handle, &spinner_dimensions);
+ spinner_scale_factor = ((line_height - 5.0) / spinner_dimensions.height);
+ spinner_width = spinner_dimensions.width * spinner_scale_factor;
+
+ // padding
+ spinner_width += 10.0;
+ }
+
+ // Draw status text
+ cairo_move_to(cr, spinner_width + field_x, field_y - line_height - field_padding);
pango_cairo_show_layout(cr, state->pango_layout);
+ // Draw processing indicator
+ if (state->is_processing) {
+ SpinnerAnimation spinner_anim = get_animation_for_key(state, state->spinner_anim_key)->anim.spinner_anim;
+
+ cairo_save(cr);
+
+ cairo_translate(cr, field_x, field_y - line_height - 8.0);
+
+ double tr_amount = (spinner_dimensions.width * spinner_scale_factor) / 2.0;
+ cairo_translate(cr, tr_amount, tr_amount);
+ cairo_rotate(cr, spinner_anim.rotation);
+ cairo_translate(cr, -tr_amount, -tr_amount);
+
+ cairo_scale(cr, spinner_scale_factor, spinner_scale_factor);
+
+ rsvg_handle_render_cairo(state->spinner_svg_handle, cr);
+
+ cairo_restore(cr);
+ }
+
// Draw password asterisks
if (state->asterisk_svg_handle == NULL) {
- GError *error = NULL;
- GBytes *bytes = get_data_for_resource("/resources/asterisk.svg");
-
- gsize size = 0;
- gconstpointer data = g_bytes_get_data(bytes, &size);
- state->asterisk_svg_handle = rsvg_handle_new_from_data(data, size, &error);
- g_bytes_unref(bytes);
- if (error != NULL) {
- fprintf(stderr, "Error loading asterisk SVG\n");
- return;
- }
+ state->asterisk_svg_handle = load_svg_for_resource_path("/resources/asterisk.svg");
}
const double cursor_padding_x = 10.0;
@@ -284,6 +349,7 @@ void draw_password_field(saver_state_t *state)
cairo_set_source(cr, asterisk_pattern);
cairo_paint_with_alpha(cr, state->password_opacity);
cairo_restore(cr);
+ cairo_pattern_destroy(asterisk_pattern);
// Draw cursor
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, MIN(state->password_opacity, state->cursor_opacity));
diff --git a/src/render.h b/src/render.h
index 137470d..d0de544 100644
--- a/src/render.h
+++ b/src/render.h
@@ -16,6 +16,11 @@
#include <stdbool.h>
#define kMaxAnimations 32
+#define kMaxPasswordLength 128
+#define kMaxPromptLength 128
+
+typedef unsigned animation_key_t;
+#define ANIM_KEY_NOEXIST (kMaxAnimations + 1)
typedef struct {
cairo_t *ctx;
@@ -36,13 +41,16 @@ typedef struct {
bool input_allowed;
double cursor_opacity;
+ animation_key_t cursor_anim_key;
bool is_processing;
bool is_authenticated;
- const char *password_prompt;
- char *password_buffer;
- size_t password_buffer_len;
+ RsvgHandle *spinner_svg_handle;
+ animation_key_t spinner_anim_key;
+
+ char password_prompt[kMaxPromptLength];
+ char password_buffer[kMaxPasswordLength];
double password_opacity;
animation_t animations[kMaxAnimations];
@@ -51,8 +59,17 @@ typedef struct {
struct auth_handle_t *auth_handle;
} saver_state_t;
+// Use this to set the prompt ("Password: ")
+void set_password_prompt(saver_state_t *state, const char *prompt);
+
// Start an animation
-void schedule_animation(saver_state_t *state, animation_t anim);
+animation_key_t schedule_animation(saver_state_t *state, animation_t anim);
+
+// Stop an animation
+void remove_animation(saver_state_t *state, animation_key_t anim_key);
+
+// Get a running animation (returns NULL if it doesn't exist)
+animation_t* get_animation_for_key(saver_state_t *state, animation_key_t anim_key);
// Update all running animations
void update_animations(saver_state_t *state);