From 84d47d28244a12de8314bc8ba81affd1b2fb86cf Mon Sep 17 00:00:00 2001 From: Vladimir Stoiakin Date: Wed, 12 Mar 2025 11:11:22 +0300 Subject: [PATCH] unl0kr: add a CLI option to show a custom message Fixes: #33 --- CHANGELOG.md | 1 + man/unl0kr.1.scd | 2 + shared/theme.c | 4 +- shared/themes.c | 2 +- unl0kr/command_line.c | 9 +- unl0kr/command_line.h | 2 + unl0kr/main.c | 152 +++++++++++++++++++++---------- unl0kr/regenerate-screenshots.sh | 2 +- unl0kr/unl0kr-agent.c | 13 ++- 9 files changed, 132 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f9f82..042f9bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ If a change only affects particular applications, they are listed in parentheses - feat(unl0kr): Enable software rotation (!32, thanks @xs5871 & @craftyguy) - misc: do not hardcode endianess of a system (!41, thanks @vstoiakin) - misc(unl0kr): Optimize the main loop (!38, thanks @vstoiakin) +- feat(unl0kr): Add the option to show a custom message to a user (!39, thanks @vstoiakin) ## 3.2.0 (2024-06-03) diff --git a/man/unl0kr.1.scd b/man/unl0kr.1.scd index 523fc61..e65fce1 100644 --- a/man/unl0kr.1.scd +++ b/man/unl0kr.1.scd @@ -21,6 +21,8 @@ password is printed to STDOUT. All other output happens on STDERR. ## Optional +*-m, --message* + A message that will be displayed to a user. *-C, --config-override* Path to a config override file. Can be supplied multiple times. Config files are merged in the following order: diff --git a/shared/theme.c b/shared/theme.c index cc5b7a0..dfa7e50 100644 --- a/shared/theme.c +++ b/shared/theme.c @@ -40,7 +40,7 @@ static struct { lv_style_t msgbox_background; lv_style_t bar; lv_style_t bar_indicator; -} styles; +} styles; static bool are_styles_initialised = false; @@ -90,6 +90,8 @@ static void init_styles(const bbx_theme *theme) { reset_style(&(styles.window)); lv_style_set_bg_opa(&(styles.window), LV_OPA_COVER); lv_style_set_bg_color(&(styles.window), lv_color_hex(theme->window.bg_color)); + lv_style_set_layout(&(styles.window), LV_LAYOUT_FLEX); + lv_style_set_flex_flow(&(styles.window), LV_FLEX_FLOW_COLUMN); reset_style(&(styles.header)); lv_style_set_bg_opa(&(styles.header), LV_OPA_COVER); diff --git a/shared/themes.c b/shared/themes.c index be4f9a3..e48513c 100644 --- a/shared/themes.c +++ b/shared/themes.c @@ -1085,7 +1085,7 @@ static const bbx_theme nord_dark = { } }, .label = { - .fg_color = 0x070c0d + .fg_color = NORD5 }, .msgbox = { .fg_color = NORD6, diff --git a/unl0kr/command_line.c b/unl0kr/command_line.c index 100a826..5c13d8e 100644 --- a/unl0kr/command_line.c +++ b/unl0kr/command_line.c @@ -13,6 +13,7 @@ #include #include #include +#include /** @@ -39,6 +40,7 @@ static void print_usage(); static void init_opts(ul_cli_opts *opts) { opts->num_config_files = 0; opts->config_files = NULL; + opts->message = NULL; opts->hor_res = -1; opts->ver_res = -1; opts->x_offset = 0; @@ -58,6 +60,7 @@ static void print_usage() { "password is printed to STDOUT. All other output happens on STDERR.\n" "\n" "Mandatory arguments to long options are mandatory for short options too.\n" + " -m, --message A message that will be displayed to a user\n" " -C, --config-override Path to a config override file. Can be supplied\n" " multiple times. Config files are merged in the\n" " following order:\n" @@ -93,6 +96,7 @@ void ul_cli_parse_opts(int argc, char *argv[], ul_cli_opts *opts) { init_opts(opts); struct option long_opts[] = { + { "message", required_argument, NULL, 'm' }, { "config-override", required_argument, NULL, 'C' }, { "geometry", required_argument, NULL, 'g' }, { "dpi", required_argument, NULL, 'd' }, @@ -105,8 +109,11 @@ void ul_cli_parse_opts(int argc, char *argv[], ul_cli_opts *opts) { int opt, index = 0; - while ((opt = getopt_long(argc, argv, "C:g:d:r:hnvV", long_opts, &index)) != -1) { + while ((opt = getopt_long(argc, argv, "m:C:g:d:r:hnvV", long_opts, &index)) != -1) { switch (opt) { + case 'm': + opts->message = strdup(optarg); + break; case 'C': opts->config_files = realloc(opts->config_files, (opts->num_config_files + 1) * sizeof(char *)); if (!opts->config_files) { diff --git a/unl0kr/command_line.h b/unl0kr/command_line.h index c6e075f..09f147d 100644 --- a/unl0kr/command_line.h +++ b/unl0kr/command_line.h @@ -18,6 +18,8 @@ typedef struct { int num_config_files; /* Paths of config file */ const char **config_files; + /* Message for a user */ + const char *message; /* Horizontal display resolution */ int hor_res; /* Vertical display resolution */ diff --git a/unl0kr/main.c b/unl0kr/main.c index d2a5b84..1ee786d 100644 --- a/unl0kr/main.c +++ b/unl0kr/main.c @@ -40,8 +40,13 @@ bool is_alternate_theme = false; bool is_password_obscured = true; bool is_keyboard_hidden = false; +lv_obj_t *container = NULL; lv_obj_t *keyboard = NULL; +int32_t content_height_with_kb; +int32_t content_height_without_kb; +int32_t content_pad_bottom_with_kb; +int32_t content_pad_bottom_without_kb; /** * Static prototypes @@ -117,12 +122,13 @@ static void toggle_keyboard_hidden(void); static void set_keyboard_hidden(bool is_hidden); /** - * Callback for the keyboard's vertical slide in / out animation. + * Callback for the pad animation. * - * @param obj keyboard widget - * @param value y position + * @param obj container widget + * @param value the current value of the pad */ -static void keyboard_anim_y_cb(void *obj, int32_t value); + +static void pad_anim_cb(void *obj, int32_t value); /** * Handle LV_EVENT_VALUE_CHANGED events from the keyboard layout dropdown. @@ -249,22 +255,43 @@ static void toggle_keyboard_hidden(void) { static void set_keyboard_hidden(bool is_hidden) { if (!conf_opts.general.animations) { - lv_obj_set_y(keyboard, is_hidden ? lv_obj_get_height(keyboard) : 0); + lv_obj_set_height(container, is_hidden? content_height_without_kb : content_height_with_kb); + lv_obj_set_style_pad_bottom(container, + is_hidden? content_pad_bottom_without_kb : content_pad_bottom_with_kb, LV_PART_MAIN); return; } lv_anim_t keyboard_anim; lv_anim_init(&keyboard_anim); - lv_anim_set_var(&keyboard_anim, keyboard); - lv_anim_set_values(&keyboard_anim, is_hidden ? 0 : lv_obj_get_height(keyboard), is_hidden ? lv_obj_get_height(keyboard) : 0); + lv_anim_set_var(&keyboard_anim, container); + lv_anim_set_exec_cb(&keyboard_anim, (lv_anim_exec_xcb_t) lv_obj_set_height); lv_anim_set_path_cb(&keyboard_anim, lv_anim_path_ease_out); lv_anim_set_time(&keyboard_anim, 500); - lv_anim_set_exec_cb(&keyboard_anim, keyboard_anim_y_cb); + + lv_anim_set_values(&keyboard_anim, + is_hidden? content_height_with_kb : content_height_without_kb, + is_hidden? content_height_without_kb : content_height_with_kb); + lv_anim_start(&keyboard_anim); + + if (content_pad_bottom_with_kb != content_pad_bottom_without_kb) { + lv_anim_t pad_anim; + lv_anim_init(&pad_anim); + lv_anim_set_var(&pad_anim, container); + lv_anim_set_exec_cb(&pad_anim, pad_anim_cb); + lv_anim_set_path_cb(&pad_anim, lv_anim_path_ease_out); + lv_anim_set_time(&pad_anim, 500); + + lv_anim_set_values(&pad_anim, + is_hidden? content_pad_bottom_with_kb : content_pad_bottom_without_kb, + is_hidden? content_pad_bottom_without_kb : content_pad_bottom_with_kb); + + lv_anim_start(&pad_anim); + } } -static void keyboard_anim_y_cb(void *obj, int32_t value) { - lv_obj_set_y(obj, value); +static void pad_anim_cb(void *obj, int32_t value) { + lv_obj_set_style_pad_bottom(obj, value, LV_PART_MAIN); } static void layout_dropdown_value_changed_cb(lv_event_t *event) { @@ -456,10 +483,6 @@ int main(int argc, char *argv[]) { /* Set up display rotation */ lv_display_set_rotation(disp, cli_opts.rotation); - /* Store final display resolution for convenient later access */ - const uint32_t hor_res = lv_disp_get_hor_res(disp); - const uint32_t ver_res = lv_disp_get_ver_res(disp); - /* Prepare for routing physical keyboard input into the textarea */ lv_group_t *keyboard_input_group = lv_group_create(); bbx_indev_set_keyboard_input_group(keyboard_input_group); @@ -475,29 +498,20 @@ int main(int argc, char *argv[]) { /* Initialise theme */ set_theme(is_alternate_theme); - /* Prevent scrolling when keyboard is off-screen */ - lv_obj_clear_flag(lv_scr_act(), LV_OBJ_FLAG_SCROLLABLE); - /* Figure out a few numbers for sizing and positioning */ - const int base_keyboard_height = ver_res > hor_res ? ver_res / 3 : ver_res / 2; /* Height for 4 rows */ - const int keyboard_height = base_keyboard_height * 1.25; /* Add space for an extra top row */ - const int padding = keyboard_height / 10; - const int textarea_container_max_width = LV_MIN(hor_res, ver_res); + const int32_t hor_res = lv_disp_get_hor_res(disp); + const int32_t ver_res = lv_disp_get_ver_res(disp); + const int32_t keyboard_height = ver_res > hor_res ? ver_res / 2.5 : ver_res / 1.8; /* Height for 5 rows */ - /* Main flexbox */ - lv_obj_t *container = lv_obj_create(lv_scr_act()); - lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_set_size(container, LV_PCT(100), ver_res - keyboard_height); - lv_obj_set_pos(container, 0, 0); - lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE); + /* Prevent scrolling when keyboard is off-screen */ + lv_obj_t *screen = lv_screen_active(); + lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE); /* Header flexbox */ - lv_obj_t *header = lv_obj_create(container); + lv_obj_t *header = lv_obj_create(screen); lv_obj_add_flag(header, BBX_WIDGET_HEADER); lv_theme_apply(header); /* Force re-apply theme after setting flag so that the widget can be identified */ lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(header, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_size(header, LV_PCT(100), LV_SIZE_CONTENT); /* Theme switcher button */ @@ -532,19 +546,45 @@ int main(int argc, char *argv[]) { lv_label_set_text(shutdown_btn_label, LV_SYMBOL_POWER); lv_obj_center(shutdown_btn_label); - /* Flexible spacer */ - lv_obj_t *flexible_spacer = lv_obj_create(container); - lv_obj_set_size(flexible_spacer, LV_PCT(100), 0); - lv_obj_set_flex_grow(flexible_spacer, 1); + lv_obj_update_layout(layout_dropdown); + const int32_t dropwdown_height = lv_obj_get_height(layout_dropdown); + lv_obj_set_size(toggle_theme_btn, dropwdown_height, dropwdown_height); + lv_obj_set_size(toggle_kb_btn, dropwdown_height, dropwdown_height); + lv_obj_set_size(shutdown_btn, dropwdown_height, dropwdown_height); + + lv_obj_update_layout(header); + content_height_without_kb = ver_res - lv_obj_get_height(header); + content_height_with_kb = content_height_without_kb - keyboard_height; + + /* Container for a message and an input field */ + container = lv_obj_create(screen); + lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(container, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_size(container, LV_PCT(100), is_keyboard_hidden? content_height_without_kb : content_height_with_kb); + + /* Message for a user */ + lv_obj_t *message_label; + if (cli_opts.message) { + /* lv_label does not support wrapping and scrolling simultaneously, + so we place it in a scrollable container */ + lv_obj_t *message_container = lv_obj_create(container); + lv_obj_set_width(message_container, LV_PCT(100)); + lv_obj_set_flex_flow(message_container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_grow(message_container, 1); + + lv_obj_t *message_spacer = lv_obj_create(message_container); + lv_obj_set_width(message_spacer, 0); + lv_obj_set_flex_grow(message_spacer, 1); + + message_label = lv_label_create(message_container); + lv_obj_set_size(message_label, LV_PCT(100), LV_SIZE_CONTENT); + lv_label_set_text(message_label, cli_opts.message); + } /* Textarea flexbox */ lv_obj_t *textarea_container = lv_obj_create(container); lv_obj_set_size(textarea_container, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_max_width(textarea_container, textarea_container_max_width, LV_PART_MAIN); lv_obj_set_flex_flow(textarea_container, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(textarea_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_set_style_pad_left(textarea_container, padding, LV_PART_MAIN); - lv_obj_set_style_pad_right(textarea_container, padding, LV_PART_MAIN); /* Textarea */ lv_obj_t *textarea = lv_textarea_create(textarea_container); @@ -559,27 +599,40 @@ int main(int argc, char *argv[]) { /* Route physical keyboard input into textarea */ lv_group_add_obj(keyboard_input_group, textarea); + lv_obj_update_layout(textarea); + const int32_t textarea_height = lv_obj_get_height(textarea); + /* Reveal / obscure password button */ lv_obj_t *toggle_pw_btn = lv_btn_create(textarea_container); - const int textarea_height = lv_obj_get_height(textarea); lv_obj_set_size(toggle_pw_btn, textarea_height, textarea_height); lv_obj_t *toggle_pw_btn_label = lv_label_create(toggle_pw_btn); lv_obj_center(toggle_pw_btn_label); lv_label_set_text(toggle_pw_btn_label, LV_SYMBOL_EYE_OPEN); lv_obj_add_event_cb(toggle_pw_btn, toggle_pw_btn_clicked_cb, LV_EVENT_CLICKED, NULL); - /* Set header button size to match dropdown (for some reason the height is only available here) */ - const int dropwdown_height = lv_obj_get_height(layout_dropdown); - lv_obj_set_size(toggle_theme_btn, dropwdown_height, dropwdown_height); - lv_obj_set_size(toggle_kb_btn, dropwdown_height, dropwdown_height); - lv_obj_set_size(shutdown_btn, dropwdown_height, dropwdown_height); + /* The bottom pad is used to center content when the keyboard is hidden */ + content_pad_bottom_with_kb = 20; + int32_t content_pad_row = 10; - /* Fixed spacer */ - lv_obj_t *fixed_spacer = lv_obj_create(container); - lv_obj_set_size(fixed_spacer, LV_PCT(100), padding); + lv_obj_set_style_pad_top(container, 10, LV_PART_MAIN); + lv_obj_set_style_pad_left(container, 20, LV_PART_MAIN); + lv_obj_set_style_pad_right(container, 20, LV_PART_MAIN); + lv_obj_set_style_pad_row(container, content_pad_row, LV_PART_MAIN); + + int32_t content_native_height = textarea_height; + if (cli_opts.message) { + lv_obj_update_layout(message_label); + content_native_height += content_pad_row + lv_obj_get_height(message_label); + } + + content_pad_bottom_without_kb = (content_height_without_kb - content_native_height) / 2; + if (content_pad_bottom_without_kb < content_pad_bottom_with_kb) + content_pad_bottom_without_kb = content_pad_bottom_with_kb; + + lv_obj_set_style_pad_bottom(container, is_keyboard_hidden? content_pad_bottom_without_kb : content_pad_bottom_with_kb, LV_PART_MAIN); /* Keyboard (after textarea / label so that key popovers are not drawn over) */ - keyboard = lv_keyboard_create(lv_scr_act()); + keyboard = lv_keyboard_create(screen); lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER); lv_keyboard_set_textarea(keyboard, textarea); uint32_t num_keyboard_events = lv_obj_get_event_count(keyboard); @@ -591,8 +644,7 @@ int main(int argc, char *argv[]) { } lv_obj_add_event_cb(keyboard, keyboard_value_changed_cb, LV_EVENT_VALUE_CHANGED, NULL); lv_obj_add_event_cb(keyboard, keyboard_ready_cb, LV_EVENT_READY, NULL); - lv_obj_set_pos(keyboard, 0, is_keyboard_hidden ? keyboard_height : 0); - lv_obj_set_size(keyboard, hor_res, keyboard_height); + lv_obj_set_size(keyboard, LV_PCT(100), keyboard_height); bbx_theme_prepare_keyboard(keyboard); /* Apply textarea options */ diff --git a/unl0kr/regenerate-screenshots.sh b/unl0kr/regenerate-screenshots.sh index a41c260..b006650 100755 --- a/unl0kr/regenerate-screenshots.sh +++ b/unl0kr/regenerate-screenshots.sh @@ -80,7 +80,7 @@ while read -r theme; do readme="$readme"$'\n'"## $theme"$'\n\n' for res in ${resolutions[@]}; do - $executable -g $res -C $config & + $executable -m "Please enter a password to unlock the root volume" -g $res -C $config & pid=$! sleep 3 # Wait for UI to render diff --git a/unl0kr/unl0kr-agent.c b/unl0kr/unl0kr-agent.c index e62edb8..ef63486 100644 --- a/unl0kr/unl0kr-agent.c +++ b/unl0kr/unl0kr-agent.c @@ -418,7 +418,18 @@ int exec_unl0kr(char** ret_password) exit(EXIT_FAILURE); } - execl(UNL0KR_BINARY, UNL0KR_BINARY, "-n", (char*) 0); + char* argv[5]; + int argc = 2; + argv[0] = UNL0KR_BINARY; + argv[1] = "-n"; + if (request.message) { + argv[2] = "-m"; + argv[3] = request.message; + argc += 2; + } + argv[argc] = NULL; + + execv(UNL0KR_BINARY, argv); perror("exec() is failed"); exit(EXIT_FAILURE);