unl0kr: add a CLI option to show a custom message

Fixes: #33
This commit is contained in:
Vladimir Stoiakin
2025-03-12 11:11:22 +03:00
parent 18b576f259
commit 84d47d2824
9 changed files with 132 additions and 55 deletions

View File

@@ -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) - feat(unl0kr): Enable software rotation (!32, thanks @xs5871 & @craftyguy)
- misc: do not hardcode endianess of a system (!41, thanks @vstoiakin) - misc: do not hardcode endianess of a system (!41, thanks @vstoiakin)
- misc(unl0kr): Optimize the main loop (!38, 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) ## 3.2.0 (2024-06-03)

View File

@@ -21,6 +21,8 @@ password is printed to STDOUT. All other output happens on STDERR.
## Optional ## Optional
*-m, --message*
A message that will be displayed to a user.
*-C, --config-override* *-C, --config-override*
Path to a config override file. Can be supplied multiple times. Config Path to a config override file. Can be supplied multiple times. Config
files are merged in the following order: files are merged in the following order:

View File

@@ -40,7 +40,7 @@ static struct {
lv_style_t msgbox_background; lv_style_t msgbox_background;
lv_style_t bar; lv_style_t bar;
lv_style_t bar_indicator; lv_style_t bar_indicator;
} styles; } styles;
static bool are_styles_initialised = false; static bool are_styles_initialised = false;
@@ -90,6 +90,8 @@ static void init_styles(const bbx_theme *theme) {
reset_style(&(styles.window)); reset_style(&(styles.window));
lv_style_set_bg_opa(&(styles.window), LV_OPA_COVER); 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_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)); reset_style(&(styles.header));
lv_style_set_bg_opa(&(styles.header), LV_OPA_COVER); lv_style_set_bg_opa(&(styles.header), LV_OPA_COVER);

View File

@@ -1085,7 +1085,7 @@ static const bbx_theme nord_dark = {
} }
}, },
.label = { .label = {
.fg_color = 0x070c0d .fg_color = NORD5
}, },
.msgbox = { .msgbox = {
.fg_color = NORD6, .fg_color = NORD6,

View File

@@ -13,6 +13,7 @@
#include <getopt.h> #include <getopt.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
/** /**
@@ -39,6 +40,7 @@ static void print_usage();
static void init_opts(ul_cli_opts *opts) { static void init_opts(ul_cli_opts *opts) {
opts->num_config_files = 0; opts->num_config_files = 0;
opts->config_files = NULL; opts->config_files = NULL;
opts->message = NULL;
opts->hor_res = -1; opts->hor_res = -1;
opts->ver_res = -1; opts->ver_res = -1;
opts->x_offset = 0; opts->x_offset = 0;
@@ -58,6 +60,7 @@ static void print_usage() {
"password is printed to STDOUT. All other output happens on STDERR.\n" "password is printed to STDOUT. All other output happens on STDERR.\n"
"\n" "\n"
"Mandatory arguments to long options are mandatory for short options too.\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" " -C, --config-override Path to a config override file. Can be supplied\n"
" multiple times. Config files are merged in the\n" " multiple times. Config files are merged in the\n"
" following order:\n" " following order:\n"
@@ -93,6 +96,7 @@ void ul_cli_parse_opts(int argc, char *argv[], ul_cli_opts *opts) {
init_opts(opts); init_opts(opts);
struct option long_opts[] = { struct option long_opts[] = {
{ "message", required_argument, NULL, 'm' },
{ "config-override", required_argument, NULL, 'C' }, { "config-override", required_argument, NULL, 'C' },
{ "geometry", required_argument, NULL, 'g' }, { "geometry", required_argument, NULL, 'g' },
{ "dpi", required_argument, NULL, 'd' }, { "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; 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) { switch (opt) {
case 'm':
opts->message = strdup(optarg);
break;
case 'C': case 'C':
opts->config_files = realloc(opts->config_files, (opts->num_config_files + 1) * sizeof(char *)); opts->config_files = realloc(opts->config_files, (opts->num_config_files + 1) * sizeof(char *));
if (!opts->config_files) { if (!opts->config_files) {

View File

@@ -18,6 +18,8 @@ typedef struct {
int num_config_files; int num_config_files;
/* Paths of config file */ /* Paths of config file */
const char **config_files; const char **config_files;
/* Message for a user */
const char *message;
/* Horizontal display resolution */ /* Horizontal display resolution */
int hor_res; int hor_res;
/* Vertical display resolution */ /* Vertical display resolution */

View File

@@ -40,8 +40,13 @@ bool is_alternate_theme = false;
bool is_password_obscured = true; bool is_password_obscured = true;
bool is_keyboard_hidden = false; bool is_keyboard_hidden = false;
lv_obj_t *container = NULL;
lv_obj_t *keyboard = 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 * Static prototypes
@@ -117,12 +122,13 @@ static void toggle_keyboard_hidden(void);
static void set_keyboard_hidden(bool is_hidden); 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 obj container widget
* @param value y position * @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. * 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) { static void set_keyboard_hidden(bool is_hidden) {
if (!conf_opts.general.animations) { 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; return;
} }
lv_anim_t keyboard_anim; lv_anim_t keyboard_anim;
lv_anim_init(&keyboard_anim); lv_anim_init(&keyboard_anim);
lv_anim_set_var(&keyboard_anim, keyboard); lv_anim_set_var(&keyboard_anim, container);
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_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_path_cb(&keyboard_anim, lv_anim_path_ease_out);
lv_anim_set_time(&keyboard_anim, 500); 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); 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) { static void pad_anim_cb(void *obj, int32_t value) {
lv_obj_set_y(obj, value); lv_obj_set_style_pad_bottom(obj, value, LV_PART_MAIN);
} }
static void layout_dropdown_value_changed_cb(lv_event_t *event) { 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 */ /* Set up display rotation */
lv_display_set_rotation(disp, cli_opts.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 */ /* Prepare for routing physical keyboard input into the textarea */
lv_group_t *keyboard_input_group = lv_group_create(); lv_group_t *keyboard_input_group = lv_group_create();
bbx_indev_set_keyboard_input_group(keyboard_input_group); bbx_indev_set_keyboard_input_group(keyboard_input_group);
@@ -475,29 +498,20 @@ int main(int argc, char *argv[]) {
/* Initialise theme */ /* Initialise theme */
set_theme(is_alternate_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 */ /* 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 int32_t hor_res = lv_disp_get_hor_res(disp);
const int keyboard_height = base_keyboard_height * 1.25; /* Add space for an extra top row */ const int32_t ver_res = lv_disp_get_ver_res(disp);
const int padding = keyboard_height / 10; const int32_t keyboard_height = ver_res > hor_res ? ver_res / 2.5 : ver_res / 1.8; /* Height for 5 rows */
const int textarea_container_max_width = LV_MIN(hor_res, ver_res);
/* Main flexbox */ /* Prevent scrolling when keyboard is off-screen */
lv_obj_t *container = lv_obj_create(lv_scr_act()); lv_obj_t *screen = lv_screen_active();
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE);
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);
/* Header flexbox */ /* 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_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_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_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); lv_obj_set_size(header, LV_PCT(100), LV_SIZE_CONTENT);
/* Theme switcher button */ /* Theme switcher button */
@@ -532,19 +546,45 @@ int main(int argc, char *argv[]) {
lv_label_set_text(shutdown_btn_label, LV_SYMBOL_POWER); lv_label_set_text(shutdown_btn_label, LV_SYMBOL_POWER);
lv_obj_center(shutdown_btn_label); lv_obj_center(shutdown_btn_label);
/* Flexible spacer */ lv_obj_update_layout(layout_dropdown);
lv_obj_t *flexible_spacer = lv_obj_create(container); const int32_t dropwdown_height = lv_obj_get_height(layout_dropdown);
lv_obj_set_size(flexible_spacer, LV_PCT(100), 0); lv_obj_set_size(toggle_theme_btn, dropwdown_height, dropwdown_height);
lv_obj_set_flex_grow(flexible_spacer, 1); 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 */ /* Textarea flexbox */
lv_obj_t *textarea_container = lv_obj_create(container); lv_obj_t *textarea_container = lv_obj_create(container);
lv_obj_set_size(textarea_container, LV_PCT(100), LV_SIZE_CONTENT); 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_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 */ /* Textarea */
lv_obj_t *textarea = lv_textarea_create(textarea_container); 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 */ /* Route physical keyboard input into textarea */
lv_group_add_obj(keyboard_input_group, 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 */ /* Reveal / obscure password button */
lv_obj_t *toggle_pw_btn = lv_btn_create(textarea_container); 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_set_size(toggle_pw_btn, textarea_height, textarea_height);
lv_obj_t *toggle_pw_btn_label = lv_label_create(toggle_pw_btn); lv_obj_t *toggle_pw_btn_label = lv_label_create(toggle_pw_btn);
lv_obj_center(toggle_pw_btn_label); lv_obj_center(toggle_pw_btn_label);
lv_label_set_text(toggle_pw_btn_label, LV_SYMBOL_EYE_OPEN); 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); 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) */ /* The bottom pad is used to center content when the keyboard is hidden */
const int dropwdown_height = lv_obj_get_height(layout_dropdown); content_pad_bottom_with_kb = 20;
lv_obj_set_size(toggle_theme_btn, dropwdown_height, dropwdown_height); int32_t content_pad_row = 10;
lv_obj_set_size(toggle_kb_btn, dropwdown_height, dropwdown_height);
lv_obj_set_size(shutdown_btn, dropwdown_height, dropwdown_height);
/* Fixed spacer */ lv_obj_set_style_pad_top(container, 10, LV_PART_MAIN);
lv_obj_t *fixed_spacer = lv_obj_create(container); lv_obj_set_style_pad_left(container, 20, LV_PART_MAIN);
lv_obj_set_size(fixed_spacer, LV_PCT(100), padding); 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 (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_mode(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER);
lv_keyboard_set_textarea(keyboard, textarea); lv_keyboard_set_textarea(keyboard, textarea);
uint32_t num_keyboard_events = lv_obj_get_event_count(keyboard); 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_value_changed_cb, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(keyboard, keyboard_ready_cb, LV_EVENT_READY, 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, LV_PCT(100), keyboard_height);
lv_obj_set_size(keyboard, hor_res, keyboard_height);
bbx_theme_prepare_keyboard(keyboard); bbx_theme_prepare_keyboard(keyboard);
/* Apply textarea options */ /* Apply textarea options */

View File

@@ -80,7 +80,7 @@ while read -r theme; do
readme="$readme"$'\n'"## $theme"$'\n\n' readme="$readme"$'\n'"## $theme"$'\n\n'
for res in ${resolutions[@]}; do 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=$! pid=$!
sleep 3 # Wait for UI to render sleep 3 # Wait for UI to render

View File

@@ -418,7 +418,18 @@ int exec_unl0kr(char** ret_password)
exit(EXIT_FAILURE); 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"); perror("exec() is failed");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);