From 4b8abf88387a1bf732afd5c1a6abc5b8b486341c Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sun, 20 Jan 2019 20:57:52 -0800 Subject: Spinner animation and tweaks to memory management --- resources/buzzsaver.gresource.xml | 1 + resources/spinner.svg | 8 +++ src/animation.h | 6 ++ src/auth.c | 24 +++---- src/auth.h | 4 +- src/main.c | 58 +++++++++------ src/render.c | 146 +++++++++++++++++++++++++++----------- src/render.h | 25 +++++-- 8 files changed, 192 insertions(+), 80 deletions(-) create mode 100644 resources/spinner.svg 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 @@ resources/asterisk.svg resources/logo.svg + resources/spinner.svg 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 @@ + + + + + + + + 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 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 #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); -- cgit v1.2.3