From 27ecc36c0bef804d12dbf8c29684c8e8159c8e47 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 15 Apr 2024 14:28:38 +0100 Subject: [efi] Do not attempt to download autoexec.ipxe without a valid base URI If we do not have a current working URI (after applying the EFI device path settings and any cached DHCP settings), then an attempt to download autoexec.ipxe will fail since there is no base URI from which to resolve the full autoexec.ipxe URI. Avoid this potentially confusing error message by attempting the download only if we have successfully obtained a current working URI. Signed-off-by: Michael Brown --- src/interface/efi/efi_autoexec.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c index d9ad3b990..44b8b645b 100644 --- a/src/interface/efi/efi_autoexec.c +++ b/src/interface/efi/efi_autoexec.c @@ -28,6 +28,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include @@ -111,6 +112,14 @@ static int efi_autoexec_network ( EFI_HANDLE handle, struct image **image ) { goto err_create; } + /* Do nothing unless we have a usable current working URI */ + if ( ! cwuri ) { + DBGC ( device, "EFI %s has no current working URI\n", + efi_handle_name ( device ) ); + rc = -ENOTTY; + goto err_cwuri; + } + /* Open network device */ if ( ( rc = netdev_open ( netdev ) ) != 0 ) { DBGC ( device, "EFI %s could not open net device: %s\n", @@ -130,6 +139,7 @@ static int efi_autoexec_network ( EFI_HANDLE handle, struct image **image ) { sync ( EFI_AUTOEXEC_TIMEOUT ); err_open: + err_cwuri: mnptemp_destroy ( netdev ); err_create: return rc; -- cgit v1.2.3-55-g7522 From 40b51124400df9cb0c57512ff93ac21827d5bac0 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 15 Apr 2024 15:59:49 +0100 Subject: [hci] Use dynamically allocated buffers for editable strings Editable strings currently require a fixed-size buffer, which is inelegant and limits the potential for creating interactive forms with a variable number of edit box widgets. Remove this limitation by switching to using a dynamically allocated buffer for editable strings and edit box widgets. Signed-off-by: Michael Brown --- src/hci/editstring.c | 144 ++++++++++++++++++++++++++----------- src/hci/mucurses/widgets/editbox.c | 14 ++-- src/hci/readline.c | 48 +++++-------- src/hci/tui/login_ui.c | 39 +++++----- src/hci/tui/settings_ui.c | 60 ++++++++-------- src/include/ipxe/editbox.h | 2 +- src/include/ipxe/editstring.h | 33 +++++---- src/include/ipxe/errfile.h | 1 + 8 files changed, 199 insertions(+), 142 deletions(-) (limited to 'src') diff --git a/src/hci/editstring.c b/src/hci/editstring.c index 8cbce0767..b83916c6e 100644 --- a/src/hci/editstring.c +++ b/src/hci/editstring.c @@ -24,8 +24,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include #include #include +#include #include #include @@ -35,17 +37,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -static void insert_delete ( struct edit_string *string, size_t delete_len, - const char *insert_text ) - __attribute__ (( nonnull (1) )); -static void insert_character ( struct edit_string *string, - unsigned int character ) __nonnull; -static void delete_character ( struct edit_string *string ) __nonnull; -static void backspace ( struct edit_string *string ) __nonnull; -static void previous_word ( struct edit_string *string ) __nonnull; -static void kill_word ( struct edit_string *string ) __nonnull; -static void kill_sol ( struct edit_string *string ) __nonnull; -static void kill_eol ( struct edit_string *string ) __nonnull; +static __attribute__ (( nonnull ( 1 ) )) int +insert_delete ( struct edit_string *string, size_t delete_len, + const char *insert_text ); +static __nonnull int insert_character ( struct edit_string *string, + unsigned int character ); +static __nonnull void delete_character ( struct edit_string *string ); +static __nonnull void backspace ( struct edit_string *string ); +static __nonnull void previous_word ( struct edit_string *string ); +static __nonnull void kill_word ( struct edit_string *string ); +static __nonnull void kill_sol ( struct edit_string *string ); +static __nonnull void kill_eol ( struct edit_string *string ); /** * Insert and/or delete text within an editable string @@ -53,35 +55,56 @@ static void kill_eol ( struct edit_string *string ) __nonnull; * @v string Editable string * @v delete_len Length of text to delete from current cursor position * @v insert_text Text to insert at current cursor position, or NULL + * @ret rc Return status code */ -static void insert_delete ( struct edit_string *string, size_t delete_len, - const char *insert_text ) { - size_t old_len, max_delete_len, insert_len, max_insert_len, new_len; +static int insert_delete ( struct edit_string *string, size_t delete_len, + const char *insert_text ) { + size_t old_len, max_delete_len, insert_len, new_len; + char *buf; + char *tmp; /* Calculate lengths */ - old_len = strlen ( string->buf ); + buf = *(string->buf); + old_len = ( buf ? strlen ( buf ) : 0 ); assert ( string->cursor <= old_len ); max_delete_len = ( old_len - string->cursor ); if ( delete_len > max_delete_len ) delete_len = max_delete_len; insert_len = ( insert_text ? strlen ( insert_text ) : 0 ); - max_insert_len = ( ( string->len - 1 ) - ( old_len - delete_len ) ); - if ( insert_len > max_insert_len ) - insert_len = max_insert_len; new_len = ( old_len - delete_len + insert_len ); + /* Reallocate string, ignoring failures if shrinking */ + tmp = realloc ( buf, ( new_len + 1 /* NUL */ ) ); + if ( tmp ) { + buf = tmp; + *(string->buf) = buf; + } else if ( new_len > old_len ) { + return -ENOMEM; + } + /* Fill in edit history */ string->mod_start = string->cursor; string->mod_end = ( ( new_len > old_len ) ? new_len : old_len ); /* Move data following the cursor */ - memmove ( ( string->buf + string->cursor + insert_len ), - ( string->buf + string->cursor + delete_len ), - ( max_delete_len + 1 - delete_len ) ); + memmove ( ( buf + string->cursor + insert_len ), + ( buf + string->cursor + delete_len ), + ( max_delete_len - delete_len ) ); /* Copy inserted text to cursor position */ - memcpy ( ( string->buf + string->cursor ), insert_text, insert_len ); + memcpy ( ( buf + string->cursor ), insert_text, insert_len ); string->cursor += insert_len; + + /* Terminate string (if not NULL) */ + if ( buf ) { + buf[new_len] = '\0'; + } else { + assert ( old_len == 0 ); + assert ( insert_len == 0 ); + assert ( new_len == 0 ); + } + + return 0; } /** @@ -89,11 +112,13 @@ static void insert_delete ( struct edit_string *string, size_t delete_len, * * @v string Editable string * @v character Character to insert + * @ret rc Return status code */ -static void insert_character ( struct edit_string *string, +static int insert_character ( struct edit_string *string, unsigned int character ) { char insert_text[2] = { character, '\0' }; - insert_delete ( string, 0, insert_text ); + + return insert_delete ( string, 0, insert_text ); } /** @@ -102,7 +127,10 @@ static void insert_character ( struct edit_string *string, * @v string Editable string */ static void delete_character ( struct edit_string *string ) { - insert_delete ( string, 1, NULL ); + int rc; + + rc = insert_delete ( string, 1, NULL ); + assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); } /** @@ -111,6 +139,7 @@ static void delete_character ( struct edit_string *string ) { * @v string Editable string */ static void backspace ( struct edit_string *string ) { + if ( string->cursor > 0 ) { string->cursor--; delete_character ( string ); @@ -123,14 +152,16 @@ static void backspace ( struct edit_string *string ) { * @v string Editable string */ static void previous_word ( struct edit_string *string ) { - while ( string->cursor && - isspace ( string->buf[ string->cursor - 1 ] ) ) { - string->cursor--; + const char *buf = *(string->buf); + size_t cursor = string->cursor; + + while ( cursor && isspace ( buf[ cursor - 1 ] ) ) { + cursor--; } - while ( string->cursor && - ( ! isspace ( string->buf[ string->cursor - 1 ] ) ) ) { - string->cursor--; + while ( cursor && ( ! isspace ( buf[ cursor - 1 ] ) ) ) { + cursor--; } + string->cursor = cursor; } /** @@ -140,8 +171,11 @@ static void previous_word ( struct edit_string *string ) { */ static void kill_word ( struct edit_string *string ) { size_t old_cursor = string->cursor; + int rc; + previous_word ( string ); - insert_delete ( string, ( old_cursor - string->cursor ), NULL ); + rc = insert_delete ( string, ( old_cursor - string->cursor ), NULL ); + assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); } /** @@ -151,8 +185,11 @@ static void kill_word ( struct edit_string *string ) { */ static void kill_sol ( struct edit_string *string ) { size_t old_cursor = string->cursor; + int rc; + string->cursor = 0; - insert_delete ( string, old_cursor, NULL ); + rc = insert_delete ( string, old_cursor, NULL ); + assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); } /** @@ -161,18 +198,36 @@ static void kill_sol ( struct edit_string *string ) { * @v string Editable string */ static void kill_eol ( struct edit_string *string ) { - insert_delete ( string, ~( ( size_t ) 0 ), NULL ); + int rc; + + rc = insert_delete ( string, ~( ( size_t ) 0 ), NULL ); + assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); } /** * Replace editable string * * @v string Editable string - * @v replacement Replacement string + * @v replacement Replacement string, or NULL to empty the string + * @ret rc Return status code + * + * Replace the entire content of the editable string and update the + * edit history to allow the caller to bring the display into sync + * with the string content. + * + * This function does not itself update the display in any way. + * + * Upon success, the string buffer is guaranteed to be non-NULL (even + * if the replacement string is NULL or empty). + * + * Errors may safely be ignored if it is deemed that subsequently + * failing to update the display will provide sufficient feedback to + * the user. */ -void replace_string ( struct edit_string *string, const char *replacement ) { +int replace_string ( struct edit_string *string, const char *replacement ) { + string->cursor = 0; - insert_delete ( string, ~( ( size_t ) 0 ), replacement ); + return insert_delete ( string, ~( ( size_t ) 0 ), replacement ); } /** @@ -180,21 +235,26 @@ void replace_string ( struct edit_string *string, const char *replacement ) { * * @v string Editable string * @v key Key pressed by user - * @ret key Key returned to application, or zero + * @ret key Key returned to application, zero, or negative error * * Handles keypresses and updates the content of the editable string. * Basic line editing facilities (delete/insert/cursor) are supported. * If edit_string() understands and uses the keypress it will return * zero, otherwise it will return the original key. * - * This function does not update the display in any way. - * * The string's edit history will be updated to allow the caller to * efficiently bring the display into sync with the string content. + * + * This function does not itself update the display in any way. + * + * Errors may safely be ignored if it is deemed that subsequently + * failing to update the display will provide sufficient feedback to + * the user. */ int edit_string ( struct edit_string *string, int key ) { + const char *buf = *(string->buf); + size_t len = ( buf ? strlen ( buf ) : 0 ); int retval = 0; - size_t len = strlen ( string->buf ); /* Prepare edit history */ string->last_cursor = string->cursor; @@ -204,7 +264,7 @@ int edit_string ( struct edit_string *string, int key ) { /* Interpret key */ if ( ( key >= 0x20 ) && ( key <= 0x7e ) ) { /* Printable character; insert at current position */ - insert_character ( string, key ); + retval = insert_character ( string, key ); } else switch ( key ) { case KEY_BACKSPACE: /* Backspace */ diff --git a/src/hci/mucurses/widgets/editbox.c b/src/hci/mucurses/widgets/editbox.c index 210de4481..fddd2742e 100644 --- a/src/hci/mucurses/widgets/editbox.c +++ b/src/hci/mucurses/widgets/editbox.c @@ -39,20 +39,19 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * Initialise text box widget * * @v box Editable text box widget - * @v buf Text buffer - * @v len Size of text buffer + * @v buf Dynamically allocated string buffer * @v win Containing window * @v row Row * @v col Starting column * @v width Width * @v flags Flags */ -void init_editbox ( struct edit_box *box, char *buf, size_t len, +void init_editbox ( struct edit_box *box, char **buf, WINDOW *win, unsigned int row, unsigned int col, unsigned int width, unsigned int flags ) { memset ( box, 0, sizeof ( *box ) ); - init_editstring ( &box->string, buf, len ); - box->string.cursor = strlen ( buf ); + init_editstring ( &box->string, buf ); + box->string.cursor = ( *buf ? strlen ( *buf ) : 0 ); box->win = ( win ? win : stdscr ); box->row = row; box->col = col; @@ -67,6 +66,7 @@ void init_editbox ( struct edit_box *box, char *buf, size_t len, * */ void draw_editbox ( struct edit_box *box ) { + const char *content = *(box->string.buf); size_t width = box->width; char buf[ width + 1 ]; signed int cursor_offset, underflow, overflow, first; @@ -90,13 +90,13 @@ void draw_editbox ( struct edit_box *box ) { /* Construct underscore-padded string portion */ memset ( buf, '_', width ); buf[width] = '\0'; - len = ( strlen ( box->string.buf ) - first ); + len = ( content ? ( strlen ( content ) - first ) : 0 ); if ( len > width ) len = width; if ( box->flags & EDITBOX_STARS ) { memset ( buf, '*', len ); } else { - memcpy ( buf, ( box->string.buf + first ), len ); + memcpy ( buf, ( content + first ), len ); } /* Print box content and move cursor */ diff --git a/src/hci/readline.c b/src/hci/readline.c index ecc72d43f..5b46413e9 100644 --- a/src/hci/readline.c +++ b/src/hci/readline.c @@ -38,8 +38,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -#define READLINE_MAX 1024 - /** * Synchronise console with edited string * @@ -49,7 +47,8 @@ static void sync_console ( struct edit_string *string ) { unsigned int mod_start = string->mod_start; unsigned int mod_end = string->mod_end; unsigned int cursor = string->last_cursor; - size_t len = strlen ( string->buf ); + const char *buf = *(string->buf); + size_t len = strlen ( buf ); /* Expand region back to old cursor position if applicable */ if ( mod_start > string->last_cursor ) @@ -67,7 +66,7 @@ static void sync_console ( struct edit_string *string ) { /* Print modified region */ while ( cursor < mod_end ) { - putchar ( ( cursor >= len ) ? ' ' : string->buf[cursor] ); + putchar ( ( cursor >= len ) ? ' ' : buf[cursor] ); cursor++; } @@ -259,15 +258,11 @@ int readline_history ( const char *prompt, const char *prefill, struct readline_history *history, unsigned long timeout, char **line ) { struct edit_string string; - char *buf; int key; int move_by; const char *new_string; int rc; - /* Avoid returning uninitialised data on error */ - *line = NULL; - /* Display prompt, if applicable */ if ( prompt ) printf ( "%s", prompt ); @@ -275,20 +270,15 @@ int readline_history ( const char *prompt, const char *prefill, /* Ensure cursor is visible */ printf ( "\033[?25h" ); - /* Allocate buffer and initialise editable string */ - buf = zalloc ( READLINE_MAX ); - if ( ! buf ) { - rc = -ENOMEM; - goto done; - } + /* Initialise editable string */ + *line = NULL; memset ( &string, 0, sizeof ( string ) ); - init_editstring ( &string, buf, READLINE_MAX ); + init_editstring ( &string, line ); - /* Prefill string, if applicable */ - if ( prefill ) { - replace_string ( &string, prefill ); - sync_console ( &string ); - } + /* Prefill string */ + if ( ( rc = replace_string ( &string, prefill ) ) != 0 ) + goto error; + sync_console ( &string ); while ( 1 ) { @@ -296,7 +286,7 @@ int readline_history ( const char *prompt, const char *prefill, key = getkey ( timeout ); if ( key < 0 ) { rc = -ETIMEDOUT; - goto done; + goto error; } timeout = 0; @@ -307,17 +297,11 @@ int readline_history ( const char *prompt, const char *prefill, switch ( key ) { case CR: case LF: - /* Shrink string (ignoring failures) */ - *line = realloc ( buf, - ( strlen ( buf ) + 1 /* NUL */ ) ); - if ( ! *line ) - *line = buf; - buf = NULL; rc = 0; goto done; case CTRL_C: rc = -ECANCELED; - goto done; + goto error; case KEY_UP: move_by = 1; break; @@ -325,13 +309,13 @@ int readline_history ( const char *prompt, const char *prefill, move_by = -1; break; default: - /* Do nothing */ + /* Do nothing for unrecognised keys or edit errors */ break; } /* Handle history movement, if applicable */ if ( move_by && history ) { - new_string = history_move ( history, move_by, buf ); + new_string = history_move ( history, move_by, *line ); if ( new_string ) { replace_string ( &string, new_string ); sync_console ( &string ); @@ -339,9 +323,11 @@ int readline_history ( const char *prompt, const char *prefill, } } + error: + free ( *line ); + *line = NULL; done: putchar ( '\n' ); - free ( buf ); if ( history ) { if ( *line && (*line)[0] ) history_append ( history, *line ); diff --git a/src/hci/tui/login_ui.c b/src/hci/tui/login_ui.c index 3c55325d5..bd24991cd 100644 --- a/src/hci/tui/login_ui.c +++ b/src/hci/tui/login_ui.c @@ -30,6 +30,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); */ #include +#include #include #include #include @@ -49,8 +50,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define EDITBOX_WIDTH 20U int login_ui ( void ) { - char username[64]; - char password[64]; + char *username; + char *password; struct edit_box username_box; struct edit_box password_box; struct edit_box *current_box = &username_box; @@ -58,19 +59,16 @@ int login_ui ( void ) { int rc = -EINPROGRESS; /* Fetch current setting values */ - fetch_string_setting ( NULL, &username_setting, username, - sizeof ( username ) ); - fetch_string_setting ( NULL, &password_setting, password, - sizeof ( password ) ); + fetchf_setting_copy ( NULL, &username_setting, NULL, NULL, &username ); + fetchf_setting_copy ( NULL, &password_setting, NULL, NULL, &password ); /* Initialise UI */ initscr(); start_color(); - init_editbox ( &username_box, username, sizeof ( username ), NULL, - USERNAME_ROW, EDITBOX_COL, EDITBOX_WIDTH, 0 ); - init_editbox ( &password_box, password, sizeof ( password ), NULL, - PASSWORD_ROW, EDITBOX_COL, EDITBOX_WIDTH, - EDITBOX_STARS ); + init_editbox ( &username_box, &username, NULL, USERNAME_ROW, + EDITBOX_COL, EDITBOX_WIDTH, 0 ); + init_editbox ( &password_box, &password, NULL, PASSWORD_ROW, + EDITBOX_COL, EDITBOX_WIDTH, EDITBOX_STARS ); /* Draw initial UI */ color_set ( CPAIR_NORMAL, NULL ); @@ -122,16 +120,15 @@ int login_ui ( void ) { erase(); endwin(); - if ( rc != 0 ) - return rc; + /* Store settings on successful completion */ + if ( rc == 0 ) + rc = storef_setting ( NULL, &username_setting, username ); + if ( rc == 0 ) + rc = storef_setting ( NULL, &password_setting, password ); - /* Store settings */ - if ( ( rc = store_setting ( NULL, &username_setting, username, - strlen ( username ) ) ) != 0 ) - return rc; - if ( ( rc = store_setting ( NULL, &password_setting, password, - strlen ( password ) ) ) != 0 ) - return rc; + /* Free setting values */ + free ( username ); + free ( password ); - return 0; + return rc; } diff --git a/src/hci/tui/settings_ui.c b/src/hci/tui/settings_ui.c index be421cc0a..95cbd7758 100644 --- a/src/hci/tui/settings_ui.c +++ b/src/hci/tui/settings_ui.c @@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include #include #include #include @@ -58,12 +59,15 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); char start[0]; \ char pad1[1]; \ union { \ - char settings[ cols - 1 - 1 - 1 - 1 ]; \ + struct { \ + char name[ cols - 1 - 1 - 1 - 1 - 1 ]; \ + char pad2[1]; \ + } __attribute__ (( packed )) settings; \ struct { \ char name[15]; \ char pad2[1]; \ char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \ - } setting; \ + } __attribute__ (( packed )) setting; \ } u; \ char pad3[1]; \ char nul; \ @@ -92,8 +96,8 @@ struct settings_ui_row { struct edit_box editbox; /** Editing in progress flag */ int editing; - /** Buffer for setting's value */ - char value[256]; /* enough size for a DHCP string */ + /** Dynamically allocated buffer for setting's value */ + char *buf; }; /** A settings user interface */ @@ -121,24 +125,22 @@ static unsigned int select_setting_row ( struct settings_ui *ui, struct setting *previous = NULL; unsigned int count = 0; + /* Free any previous setting value */ + free ( ui->row.buf ); + ui->row.buf = NULL; + /* Initialise structure */ memset ( &ui->row, 0, sizeof ( ui->row ) ); ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first ); /* Include parent settings block, if applicable */ - if ( ui->settings->parent && ( count++ == index ) ) { + if ( ui->settings->parent && ( count++ == index ) ) ui->row.settings = ui->settings->parent; - snprintf ( ui->row.value, sizeof ( ui->row.value ), - "../" ); - } /* Include any child settings blocks, if applicable */ list_for_each_entry ( settings, &ui->settings->children, siblings ) { - if ( count++ == index ) { + if ( count++ == index ) ui->row.settings = settings; - snprintf ( ui->row.value, sizeof ( ui->row.value ), - "%s/", settings->name ); - } } /* Include any applicable settings */ @@ -155,15 +157,14 @@ static unsigned int select_setting_row ( struct settings_ui *ui, /* Read current setting value and origin */ if ( count++ == index ) { - fetchf_setting ( ui->settings, setting, &ui->row.origin, - &ui->row.setting, ui->row.value, - sizeof ( ui->row.value ) ); + fetchf_setting_copy ( ui->settings, setting, + &ui->row.origin, + &ui->row.setting, &ui->row.buf ); } } /* Initialise edit box */ - init_editbox ( &ui->row.editbox, ui->row.value, - sizeof ( ui->row.value ), NULL, ui->row.row, + init_editbox ( &ui->row.editbox, &ui->row.buf, NULL, ui->row.row, ( SETTINGS_LIST_COL + offsetof ( typeof ( *text ), u.setting.value ) ), sizeof ( text->u.setting.value ), 0 ); @@ -197,7 +198,7 @@ static size_t string_copy ( char *dest, const char *src, size_t len ) { static void draw_setting_row ( struct settings_ui *ui ) { SETTING_ROW_TEXT ( COLS ) text; unsigned int curs_offset; - char *value; + const char *value; /* Fill row with spaces */ memset ( &text, ' ', sizeof ( text ) ); @@ -207,10 +208,12 @@ static void draw_setting_row ( struct settings_ui *ui ) { if ( ui->row.settings ) { /* Construct space-padded name */ - curs_offset = ( offsetof ( typeof ( text ), u.settings ) + - string_copy ( text.u.settings, - ui->row.value, - sizeof ( text.u.settings ) ) ); + value = ( ( ui->row.settings == ui->settings->parent ) ? + ".." : ui->row.settings->name ); + curs_offset = string_copy ( text.u.settings.name, value, + sizeof ( text.u.settings.name ) ); + text.u.settings.name[curs_offset] = '/'; + curs_offset += offsetof ( typeof ( text ), u.settings ); } else { @@ -221,12 +224,12 @@ static void draw_setting_row ( struct settings_ui *ui ) { sizeof ( text.u.setting.name ) ); /* Construct space-padded value */ - value = ui->row.value; - if ( ! *value ) + value = ui->row.buf; + if ( ! ( value && value[0] ) ) value = ""; - curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) + - string_copy ( text.u.setting.value, value, - sizeof ( text.u.setting.value ))); + curs_offset = string_copy ( text.u.setting.value, value, + sizeof ( text.u.setting.value ) ); + curs_offset += offsetof ( typeof ( text ), u.setting.value ); } /* Print row */ @@ -257,7 +260,7 @@ static int edit_setting ( struct settings_ui *ui, int key ) { */ static int save_setting ( struct settings_ui *ui ) { assert ( ui->row.setting.name != NULL ); - return storef_setting ( ui->settings, &ui->row.setting, ui->row.value ); + return storef_setting ( ui->settings, &ui->row.setting, ui->row.buf ); } /** @@ -527,6 +530,7 @@ static int main_loop ( struct settings *settings ) { redraw = 1; break; case CTRL_X: + select_setting_row ( &ui, -1U ); return 0; case CR: case LF: diff --git a/src/include/ipxe/editbox.h b/src/include/ipxe/editbox.h index 2c70e0b6b..c14bbdc5b 100644 --- a/src/include/ipxe/editbox.h +++ b/src/include/ipxe/editbox.h @@ -36,7 +36,7 @@ enum edit_box_flags { EDITBOX_STARS = 0x0001, }; -extern void init_editbox ( struct edit_box *box, char *buf, size_t len, +extern void init_editbox ( struct edit_box *box, char **buf, WINDOW *win, unsigned int row, unsigned int col, unsigned int width, unsigned int flags ) __attribute__ (( nonnull (1, 2) )); diff --git a/src/include/ipxe/editstring.h b/src/include/ipxe/editstring.h index a00a8adaa..7ad8fb304 100644 --- a/src/include/ipxe/editstring.h +++ b/src/include/ipxe/editstring.h @@ -11,10 +11,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** An editable string */ struct edit_string { - /** Buffer for string */ - char *buf; - /** Size of buffer (including terminating NUL) */ - size_t len; + /** Dynamically allocated string buffer */ + char **buf; /** Cursor position */ unsigned int cursor; @@ -32,17 +30,28 @@ struct edit_string { * Initialise editable string * * @v string Editable string - * @v buf Buffer for string - * @v len Length of buffer + * @v buf Dynamically allocated string buffer + * + * The @c buf parameter must be the address of a caller-provided + * pointer to a NUL-terminated string allocated using malloc() (or + * equivalent, such as strdup()). Any edits made to the string will + * realloc() the string buffer as needed. + * + * The caller may choose leave the initial string buffer pointer as @c + * NULL, in which case it will be allocated upon the first attempt to + * insert a character into the buffer. If the caller does this, then + * it must be prepared to find the pointer still @c NULL after + * editing, since the user may never attempt to insert any characters. */ -static inline void init_editstring ( struct edit_string *string, char *buf, - size_t len ) { +static inline __nonnull void init_editstring ( struct edit_string *string, + char **buf ) { + string->buf = buf; - string->len = len; } -extern void replace_string ( struct edit_string *string, - const char *replacement ) __nonnull; -extern int edit_string ( struct edit_string *string, int key ) __nonnull; +extern __attribute__ (( nonnull ( 1 ) )) int +replace_string ( struct edit_string *string, const char *replacement ); + +extern __nonnull int edit_string ( struct edit_string *string, int key ); #endif /* _IPXE_EDITSTRING_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 0ab37230a..96981dd06 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -416,6 +416,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_settings ( ERRFILE_OTHER | 0x005e0000 ) #define ERRFILE_x25519 ( ERRFILE_OTHER | 0x005f0000 ) #define ERRFILE_des ( ERRFILE_OTHER | 0x00600000 ) +#define ERRFILE_editstring ( ERRFILE_OTHER | 0x00610000 ) /** @} */ -- cgit v1.2.3-55-g7522 From cb95b5b378b1a61d10770965d82dba535b6be242 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 16 Apr 2024 14:19:01 +0100 Subject: [efi] Veto the Dhcp6Dxe driver on all platforms The reference implementation of Dhcp6Dxe in EDK2 has a fatal flaw: the code in EfiDhcp6Stop() will poll the network in a tight loop until either a response is received or a timer tick (at TPL_CALLBACK) occurs. When EfiDhcp6Stop() is called at TPL_CALLBACK or higher, this will result in an endless loop and an apparently frozen system. Since this is the reference implementation of Dhcp6Dxe, it is likely that almost all platforms have the same problem. Fix by vetoing the broken driver. If the upstream driver is ever fixed and a new version number issued, then we could plausibly test against the version number exposed via the driver binding protocol. Signed-off-by: Michael Brown --- src/interface/efi/efi_veto.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src') diff --git a/src/interface/efi/efi_veto.c b/src/interface/efi/efi_veto.c index a3b60d65f..37aa9a379 100644 --- a/src/interface/efi/efi_veto.c +++ b/src/interface/efi/efi_veto.c @@ -494,6 +494,32 @@ efi_veto_vmware_uefipxebc ( EFI_DRIVER_BINDING_PROTOCOL *binding __unused, return 1; } +/** + * Veto Dhcp6Dxe driver + * + * @v binding Driver binding protocol + * @v loaded Loaded image protocol + * @v wtf Component name protocol, if present + * @v manufacturer Manufacturer name, if present + * @v name Driver name, if present + * @ret vetoed Driver is to be vetoed + */ +static int efi_veto_dhcp6 ( EFI_DRIVER_BINDING_PROTOCOL *binding __unused, + EFI_LOADED_IMAGE_PROTOCOL *loaded __unused, + EFI_COMPONENT_NAME_PROTOCOL *wtf __unused, + const char *manufacturer __unused, + const CHAR16 *name ) { + static const CHAR16 dhcp6[] = L"DHCP6 Protocol Driver"; + + /* Check driver name */ + if ( ! name ) + return 0; + if ( memcmp ( name, dhcp6, sizeof ( dhcp6 ) ) != 0 ) + return 0; + + return 1; +} + /** Driver vetoes */ static struct efi_veto_candidate efi_vetoes[] = { { @@ -508,6 +534,10 @@ static struct efi_veto_candidate efi_vetoes[] = { .name = "VMware UefiPxeBc", .veto = efi_veto_vmware_uefipxebc, }, + { + .name = "Dhcp6", + .veto = efi_veto_dhcp6, + }, }; /** -- cgit v1.2.3-55-g7522 From b01781a2b8fb29669d9a610bfb323fb8a6960612 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 17 Apr 2024 15:45:36 +0100 Subject: [hci] Fix semantics of replace_string() to match code comments The comments for replace_string() state that a successful return status guarantees that the dynamically allocated string pointer is no longer NULL (even if it was initially NULL and the replacement string is NULL or empty). This is relied upon by readline() to guarantee that it will always return a non-NULL string if successful. The code behaviour does not currently match this comment: an empty replacement string may result in a successful return status even if the (single-byte) allocation fails. Fix up the code behaviour to match the comments, and to additionally ensure that the edit history is filled in even in the event of an allocation failure. Signed-off-by: Michael Brown --- src/hci/editstring.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/hci/editstring.c b/src/hci/editstring.c index b83916c6e..be9ca06a5 100644 --- a/src/hci/editstring.c +++ b/src/hci/editstring.c @@ -59,10 +59,14 @@ static __nonnull void kill_eol ( struct edit_string *string ); */ static int insert_delete ( struct edit_string *string, size_t delete_len, const char *insert_text ) { - size_t old_len, max_delete_len, insert_len, new_len; + size_t old_len, max_delete_len, move_len, insert_len, new_len; char *buf; char *tmp; + /* Prepare edit history */ + string->mod_start = string->cursor; + string->mod_end = string->cursor; + /* Calculate lengths */ buf = *(string->buf); old_len = ( buf ? strlen ( buf ) : 0 ); @@ -70,39 +74,36 @@ static int insert_delete ( struct edit_string *string, size_t delete_len, max_delete_len = ( old_len - string->cursor ); if ( delete_len > max_delete_len ) delete_len = max_delete_len; + move_len = ( max_delete_len - delete_len ); insert_len = ( insert_text ? strlen ( insert_text ) : 0 ); new_len = ( old_len - delete_len + insert_len ); + /* Delete existing text */ + memmove ( ( buf + string->cursor ), + ( buf + string->cursor + delete_len ), move_len ); + /* Reallocate string, ignoring failures if shrinking */ tmp = realloc ( buf, ( new_len + 1 /* NUL */ ) ); if ( tmp ) { buf = tmp; *(string->buf) = buf; - } else if ( new_len > old_len ) { + } else if ( ( new_len > old_len ) || ( ! buf ) ) { return -ENOMEM; } - /* Fill in edit history */ - string->mod_start = string->cursor; - string->mod_end = ( ( new_len > old_len ) ? new_len : old_len ); - - /* Move data following the cursor */ + /* Create space for inserted text */ memmove ( ( buf + string->cursor + insert_len ), - ( buf + string->cursor + delete_len ), - ( max_delete_len - delete_len ) ); + ( buf + string->cursor ), move_len ); /* Copy inserted text to cursor position */ memcpy ( ( buf + string->cursor ), insert_text, insert_len ); string->cursor += insert_len; - /* Terminate string (if not NULL) */ - if ( buf ) { - buf[new_len] = '\0'; - } else { - assert ( old_len == 0 ); - assert ( insert_len == 0 ); - assert ( new_len == 0 ); - } + /* Terminate string */ + buf[new_len] = '\0'; + + /* Update edit history */ + string->mod_end = ( ( new_len > old_len ) ? new_len : old_len ); return 0; } -- cgit v1.2.3-55-g7522 From d7e58c5a812988c341ec4ad19f79faf067388d58 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 17 Apr 2024 15:49:37 +0100 Subject: [test] Add test cases for editable strings Signed-off-by: Michael Brown --- src/tests/editstring_test.c | 198 ++++++++++++++++++++++++++++++++++++++++++++ src/tests/tests.c | 1 + 2 files changed, 199 insertions(+) create mode 100644 src/tests/editstring_test.c (limited to 'src') diff --git a/src/tests/editstring_test.c b/src/tests/editstring_test.c new file mode 100644 index 000000000..72da33a77 --- /dev/null +++ b/src/tests/editstring_test.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Editable string tests + * + */ + +/* Forcibly enable assertions */ +#undef NDEBUG + +#include +#include +#include +#include +#include +#include + +/** An editable string test */ +struct editstring_test { + /** Initial string, or NULL */ + const char *start; + /** Key sequence */ + const int *keys; + /** Length of key sequence */ + unsigned int count; + /** Expected result */ + const char *expected; +}; + +/** Define an inline key sequence */ +#define KEYS(...) { __VA_ARGS__ } + +/** Define an editable string test */ +#define EDITSTRING_TEST( name, START, EXPECTED, KEYS ) \ + static const int name ## _keys[] = KEYS; \ + static struct editstring_test name = { \ + .start = START, \ + .keys = name ## _keys, \ + .count = ( sizeof ( name ## _keys ) / \ + sizeof ( name ## _keys[0] ) ), \ + .expected = EXPECTED, \ + }; + +/* Simple typing */ +EDITSTRING_TEST ( simple, "", "hello world!", + KEYS ( 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', + 'd', '!' ) ); + +/* Simple typing from a NULL starting value */ +EDITSTRING_TEST ( simple_null, NULL, "hi there", + KEYS ( 'h', 'i', ' ', 't', 'h', 'e', 'r', 'e' ) ); + +/* Insertion */ +EDITSTRING_TEST ( insert, "in middle", "in the middle", + KEYS ( KEY_LEFT, KEY_LEFT, KEY_LEFT, KEY_LEFT, KEY_LEFT, + KEY_LEFT, 't', 'h', 'e', ' ' ) ); + +/* Backspace at end */ +EDITSTRING_TEST ( backspace_end, "byebye", "bye", + KEYS ( KEY_BACKSPACE, KEY_BACKSPACE, KEY_BACKSPACE ) ); + +/* Backspace of whole string */ +EDITSTRING_TEST ( backspace_all, "abc", "", + KEYS ( KEY_BACKSPACE, KEY_BACKSPACE, KEY_BACKSPACE ) ); + +/* Backspace of empty string */ +EDITSTRING_TEST ( backspace_empty, NULL, "", KEYS ( KEY_BACKSPACE ) ); + +/* Backspace beyond start of string */ +EDITSTRING_TEST ( backspace_beyond, "too far", "", + KEYS ( KEY_BACKSPACE, KEY_BACKSPACE, KEY_BACKSPACE, + KEY_BACKSPACE, KEY_BACKSPACE, KEY_BACKSPACE, + KEY_BACKSPACE, KEY_BACKSPACE, KEY_BACKSPACE ) ); + +/* Deletion of character at cursor via DEL */ +EDITSTRING_TEST ( delete_dc, "go away", "goaway", + KEYS ( KEY_HOME, KEY_RIGHT, KEY_RIGHT, KEY_DC ) ); + +/* Deletion of character at cursor via Ctrl-D */ +EDITSTRING_TEST ( delete_ctrl_d, "not here", "nohere", + KEYS ( KEY_LEFT, KEY_LEFT, KEY_LEFT, KEY_LEFT, KEY_LEFT, + KEY_LEFT, CTRL_D, CTRL_D ) ); + +/* Deletion of word at end of string */ +EDITSTRING_TEST ( word_end, "remove these two words", "remove these ", + KEYS ( CTRL_W, CTRL_W ) ); + +/* Deletion of word at start of string */ +EDITSTRING_TEST ( word_start, "no word", "word", + KEYS ( CTRL_A, KEY_RIGHT, KEY_RIGHT, KEY_RIGHT, CTRL_W ) ); + +/* Deletion of word mid-string */ +EDITSTRING_TEST ( word_mid, "delete this word", "delete word", + KEYS ( KEY_LEFT, KEY_LEFT, KEY_LEFT, KEY_LEFT, CTRL_W ) ); + +/* Deletion to start of line */ +EDITSTRING_TEST ( sol, "everything must go", "go", + KEYS ( KEY_LEFT, KEY_LEFT, CTRL_U ) ); + +/* Delete to end of line */ +EDITSTRING_TEST ( eol, "all is lost", "all", + KEYS ( KEY_HOME, KEY_RIGHT, KEY_RIGHT, KEY_RIGHT, CTRL_K ) ); + +/** + * Report an editable string test result + * + * @v test Editable string test + * @v file Test code file + * @v line Test code line + */ +static void editstring_okx ( struct editstring_test *test, const char *file, + unsigned int line ) { + struct edit_string string; + unsigned int i; + char *actual; + int key; + + /* Initialise editable string */ + memset ( &string, 0, sizeof ( string ) ); + actual = NULL; + init_editstring ( &string, &actual ); + + /* Set initial string content */ + okx ( replace_string ( &string, test->start ) == 0, file, line ); + okx ( actual != NULL, file, line ); + okx ( string.cursor == ( test->start ? strlen ( test->start ) : 0 ), + file, line ); + DBGC ( test, "Initial string: \"%s\"\n", actual ); + + /* Inject keypresses */ + for ( i = 0 ; i < test->count ; i++ ) { + key = test->keys[i]; + okx ( edit_string ( &string, key ) == 0, file, line ); + okx ( actual != NULL, file, line ); + okx ( string.cursor <= strlen ( actual ), file, line ); + DBGC ( test, "After key %#02x (%c): \"%s\"\n", + key, ( isprint ( key ) ? key : '.' ), actual ); + } + + /* Verify result string */ + okx ( strcmp ( actual, test->expected ) == 0, file, line ); + + /* Free result string */ + free ( actual ); +} +#define editstring_ok( test ) editstring_okx ( test, __FILE__, __LINE__ ) + +/** + * Perform editable string self-tests + * + */ +static void editstring_test_exec ( void ) { + + editstring_ok ( &simple ); + editstring_ok ( &simple_null ); + editstring_ok ( &insert ); + editstring_ok ( &backspace_end ); + editstring_ok ( &backspace_all ); + editstring_ok ( &backspace_empty ); + editstring_ok ( &backspace_beyond ); + editstring_ok ( &delete_dc ); + editstring_ok ( &delete_ctrl_d ); + editstring_ok ( &word_end ); + editstring_ok ( &word_start ); + editstring_ok ( &word_mid ); + editstring_ok ( &sol ); + editstring_ok ( &eol ); +} + +/** Editable string self-test */ +struct self_test editstring_test __self_test = { + .name = "editstring", + .exec = editstring_test_exec, +}; diff --git a/src/tests/tests.c b/src/tests/tests.c index cb296049e..0e9b3e806 100644 --- a/src/tests/tests.c +++ b/src/tests/tests.c @@ -85,3 +85,4 @@ REQUIRE_OBJECT ( x25519_test ); REQUIRE_OBJECT ( des_test ); REQUIRE_OBJECT ( mschapv2_test ); REQUIRE_OBJECT ( uuid_test ); +REQUIRE_OBJECT ( editstring_test ); -- cgit v1.2.3-55-g7522 From dc118c53696af6a0b1a8ee78fc9a4d28a217fb21 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 15 May 2024 14:10:33 +0100 Subject: [hci] Provide a general concept of a text widget set Create a generic abstraction of a text widget, refactor the existing editable text box widget to use this abstraction, add an implementation of a non-editable text label widget, and generalise the login user interface to use this generic widget abstraction. Signed-off-by: Michael Brown --- src/hci/mucurses/widgets/editbox.c | 65 ++++++++-------- src/hci/mucurses/widgets/label.c | 77 +++++++++++++++++++ src/hci/tui/login_ui.c | 106 +++++++++----------------- src/hci/tui/settings_ui.c | 14 ++-- src/hci/tui/widget_ui.c | 143 +++++++++++++++++++++++++++++++++++ src/include/ipxe/editbox.h | 50 +++++------- src/include/ipxe/errfile.h | 1 + src/include/ipxe/label.h | 42 +++++++++++ src/include/ipxe/widget.h | 151 +++++++++++++++++++++++++++++++++++++ 9 files changed, 509 insertions(+), 140 deletions(-) create mode 100644 src/hci/mucurses/widgets/label.c create mode 100644 src/hci/tui/widget_ui.c create mode 100644 src/include/ipxe/label.h create mode 100644 src/include/ipxe/widget.h (limited to 'src') diff --git a/src/hci/mucurses/widgets/editbox.c b/src/hci/mucurses/widgets/editbox.c index fddd2742e..79e7cdd16 100644 --- a/src/hci/mucurses/widgets/editbox.c +++ b/src/hci/mucurses/widgets/editbox.c @@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include #include /** @file @@ -35,39 +36,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define EDITBOX_MIN_CHARS 3 -/** - * Initialise text box widget - * - * @v box Editable text box widget - * @v buf Dynamically allocated string buffer - * @v win Containing window - * @v row Row - * @v col Starting column - * @v width Width - * @v flags Flags - */ -void init_editbox ( struct edit_box *box, char **buf, - WINDOW *win, unsigned int row, unsigned int col, - unsigned int width, unsigned int flags ) { - memset ( box, 0, sizeof ( *box ) ); - init_editstring ( &box->string, buf ); - box->string.cursor = ( *buf ? strlen ( *buf ) : 0 ); - box->win = ( win ? win : stdscr ); - box->row = row; - box->col = col; - box->width = width; - box->flags = flags; -} - /** * Draw text box widget * - * @v box Editable text box widget - * + * @v widgets Text widget set + * @v widget Text widget */ -void draw_editbox ( struct edit_box *box ) { +static void draw_editbox ( struct widgets *widgets, struct widget *widget ) { + struct edit_box *box = container_of ( widget, struct edit_box, widget ); const char *content = *(box->string.buf); - size_t width = box->width; + size_t width = widget->width; char buf[ width + 1 ]; signed int cursor_offset, underflow, overflow, first; size_t len; @@ -93,15 +71,36 @@ void draw_editbox ( struct edit_box *box ) { len = ( content ? ( strlen ( content ) - first ) : 0 ); if ( len > width ) len = width; - if ( box->flags & EDITBOX_STARS ) { + if ( widget->flags & WIDGET_SECRET ) { memset ( buf, '*', len ); } else { memcpy ( buf, ( content + first ), len ); } /* Print box content and move cursor */ - if ( ! box->win ) - box->win = stdscr; - mvwprintw ( box->win, box->row, box->col, "%s", buf ); - wmove ( box->win, box->row, ( box->col + cursor_offset ) ); + color_set ( CPAIR_EDIT, NULL ); + mvwprintw ( widgets->win, widget->row, widget->col, "%s", buf ); + wmove ( widgets->win, widget->row, ( widget->col + cursor_offset ) ); + color_set ( CPAIR_NORMAL, NULL ); } + +/** + * Edit text box widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + */ +static int edit_editbox ( struct widgets *widgets __unused, + struct widget *widget, int key ) { + struct edit_box *box = container_of ( widget, struct edit_box, widget ); + + return edit_string ( &box->string, key ); +} + +/** Text box widget operations */ +struct widget_operations editbox_operations = { + .draw = draw_editbox, + .edit = edit_editbox, +}; diff --git a/src/hci/mucurses/widgets/label.c b/src/hci/mucurses/widgets/label.c new file mode 100644 index 000000000..29645a6ad --- /dev/null +++ b/src/hci/mucurses/widgets/label.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include + +/** @file + * + * Text label widget + * + */ + +/** + * Draw text label widget + * + * @v widgets Text widget set + * @v widget Text widget + */ +static void draw_label ( struct widgets *widgets, struct widget *widget ) { + struct label *label = container_of ( widget, struct label, widget ); + unsigned int width = widget->width; + unsigned int col = widget->col; + const char *text = label->text; + + /* Centre label if width is non-zero */ + if ( width ) + col += ( ( width - strlen ( text ) ) / 2 ); + + /* Print label content */ + attron ( A_BOLD ); + mvwprintw ( widgets->win, widget->row, col, "%s", text ); + attroff ( A_BOLD ); +} + +/** + * Edit text label widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + */ +static int edit_label ( struct widgets *widgets __unused, + struct widget *widget __unused, int key ) { + + /* Cannot be edited */ + return key; +} + +/** Text label widget operations */ +struct widget_operations label_operations = { + .draw = draw_label, + .edit = edit_label, +}; diff --git a/src/hci/tui/login_ui.c b/src/hci/tui/login_ui.c index bd24991cd..b76afb92e 100644 --- a/src/hci/tui/login_ui.c +++ b/src/hci/tui/login_ui.c @@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include @@ -45,90 +46,55 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define USERNAME_ROW ( ( LINES / 2U ) - 2U ) #define PASSWORD_LABEL_ROW ( ( LINES / 2U ) + 2U ) #define PASSWORD_ROW ( ( LINES / 2U ) + 4U ) -#define LABEL_COL ( ( COLS / 2U ) - 4U ) -#define EDITBOX_COL ( ( COLS / 2U ) - 10U ) -#define EDITBOX_WIDTH 20U +#define WIDGET_COL ( ( COLS / 2U ) - 10U ) +#define WIDGET_WIDTH 20U int login_ui ( void ) { char *username; char *password; - struct edit_box username_box; - struct edit_box password_box; - struct edit_box *current_box = &username_box; - int key; - int rc = -EINPROGRESS; + struct { + struct widgets widgets; + struct label username_label; + struct label password_label; + struct edit_box username_box; + struct edit_box password_box; + } widgets; + int rc; /* Fetch current setting values */ fetchf_setting_copy ( NULL, &username_setting, NULL, NULL, &username ); fetchf_setting_copy ( NULL, &password_setting, NULL, NULL, &password ); - /* Initialise UI */ - initscr(); - start_color(); - init_editbox ( &username_box, &username, NULL, USERNAME_ROW, - EDITBOX_COL, EDITBOX_WIDTH, 0 ); - init_editbox ( &password_box, &password, NULL, PASSWORD_ROW, - EDITBOX_COL, EDITBOX_WIDTH, EDITBOX_STARS ); + /* Construct user interface */ + memset ( &widgets, 0, sizeof ( widgets ) ); + init_widgets ( &widgets.widgets, NULL ); + init_label ( &widgets.username_label, USERNAME_LABEL_ROW, WIDGET_COL, + WIDGET_WIDTH, "Username" ); + init_label ( &widgets.password_label, PASSWORD_LABEL_ROW, WIDGET_COL, + WIDGET_WIDTH, "Password" ); + init_editbox ( &widgets.username_box, USERNAME_ROW, WIDGET_COL, + WIDGET_WIDTH, 0, &username ); + init_editbox ( &widgets.password_box, PASSWORD_ROW, WIDGET_COL, + WIDGET_WIDTH, WIDGET_SECRET, &password ); + add_widget ( &widgets.widgets, &widgets.username_label.widget ); + add_widget ( &widgets.widgets, &widgets.password_label.widget ); + add_widget ( &widgets.widgets, &widgets.username_box.widget ); + add_widget ( &widgets.widgets, &widgets.password_box.widget ); - /* Draw initial UI */ - color_set ( CPAIR_NORMAL, NULL ); - erase(); - attron ( A_BOLD ); - mvprintw ( USERNAME_LABEL_ROW, LABEL_COL, "Username:" ); - mvprintw ( PASSWORD_LABEL_ROW, LABEL_COL, "Password:" ); - attroff ( A_BOLD ); - color_set ( CPAIR_EDIT, NULL ); - draw_editbox ( &username_box ); - draw_editbox ( &password_box ); - - /* Main loop */ - while ( rc == -EINPROGRESS ) { - - draw_editbox ( current_box ); - - key = getkey ( 0 ); - switch ( key ) { - case KEY_DOWN: - current_box = &password_box; - break; - case KEY_UP: - current_box = &username_box; - break; - case TAB: - current_box = ( ( current_box == &username_box ) ? - &password_box : &username_box ); - break; - case KEY_ENTER: - if ( current_box == &username_box ) { - current_box = &password_box; - } else { - rc = 0; - } - break; - case CTRL_C: - case ESC: - rc = -ECANCELED; - break; - default: - edit_editbox ( current_box, key ); - break; - } - } - - /* Terminate UI */ - color_set ( CPAIR_NORMAL, NULL ); - erase(); - endwin(); + /* Present user interface */ + if ( ( rc = widget_ui ( &widgets.widgets ) ) != 0 ) + goto err_ui; /* Store settings on successful completion */ - if ( rc == 0 ) - rc = storef_setting ( NULL, &username_setting, username ); - if ( rc == 0 ) - rc = storef_setting ( NULL, &password_setting, password ); + if ( ( rc = storef_setting ( NULL, &username_setting, username ) ) !=0) + goto err_store_username; + if ( ( rc = storef_setting ( NULL, &password_setting, password ) ) !=0) + goto err_store_password; - /* Free setting values */ + err_store_username: + err_store_password: + err_ui: free ( username ); free ( password ); - return rc; } diff --git a/src/hci/tui/settings_ui.c b/src/hci/tui/settings_ui.c index 95cbd7758..53bf24d1a 100644 --- a/src/hci/tui/settings_ui.c +++ b/src/hci/tui/settings_ui.c @@ -108,6 +108,8 @@ struct settings_ui { struct jump_scroller scroll; /** Current row */ struct settings_ui_row row; + /** Widget set used for editing setting */ + struct widgets widgets; }; /** @@ -164,10 +166,11 @@ static unsigned int select_setting_row ( struct settings_ui *ui, } /* Initialise edit box */ - init_editbox ( &ui->row.editbox, &ui->row.buf, NULL, ui->row.row, + memset ( &ui->row.editbox, 0, sizeof ( ui->row.editbox ) ); + init_editbox ( &ui->row.editbox, ui->row.row, ( SETTINGS_LIST_COL + offsetof ( typeof ( *text ), u.setting.value ) ), - sizeof ( text->u.setting.value ), 0 ); + sizeof ( text->u.setting.value ), 0, &ui->row.buf ); return count; } @@ -250,7 +253,7 @@ static void draw_setting_row ( struct settings_ui *ui ) { static int edit_setting ( struct settings_ui *ui, int key ) { assert ( ui->row.setting.name != NULL ); ui->row.editing = 1; - return edit_editbox ( &ui->row.editbox, key ); + return edit_widget ( &ui->widgets, &ui->row.editbox.widget, key ); } /** @@ -454,6 +457,7 @@ static int main_loop ( struct settings *settings ) { /* Print initial screen content */ color_set ( CPAIR_NORMAL, NULL ); memset ( &ui, 0, sizeof ( ui ) ); + init_widgets ( &ui.widgets, NULL ); select_settings ( &ui, settings ); while ( 1 ) { @@ -477,9 +481,7 @@ static int main_loop ( struct settings *settings ) { assert ( ui.row.setting.name != NULL ); /* Redraw edit box */ - color_set ( CPAIR_EDIT, NULL ); - draw_editbox ( &ui.row.editbox ); - color_set ( CPAIR_NORMAL, NULL ); + draw_widget ( &ui.widgets, &ui.row.editbox.widget ); /* Process keypress */ key = edit_setting ( &ui, getkey ( 0 ) ); diff --git a/src/hci/tui/widget_ui.c b/src/hci/tui/widget_ui.c new file mode 100644 index 000000000..5079eefdd --- /dev/null +++ b/src/hci/tui/widget_ui.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Text widget UI + * + */ + +#include +#include +#include +#include + +/** + * Find editable widget in widget set + * + * @v widgets Text widget set + * @v index Editable widget index + * @ret widget Editable widget, or NULL + */ +static struct widget * find_widget ( struct widgets *widgets, + unsigned int index ) { + struct widget *widget; + + list_for_each_entry ( widget, &widgets->list, list ) { + if ( ! ( widget->flags & WIDGET_EDITABLE ) ) + continue; + if ( index-- == 0 ) + return widget; + } + return NULL; +} + +/** + * Text widget user interface main loop + * + * @v widgets Text widget set + * @ret rc Return status code + */ +static int widget_ui_loop ( struct widgets *widgets ) { + struct widget *widget; + unsigned int current; + unsigned int count; + int key; + + /* Draw all widgets */ + list_for_each_entry ( widget, &widgets->list, list ) + draw_widget ( widgets, widget ); + + /* Count editable widgets */ + count = 0; + while ( find_widget ( widgets, count ) != NULL ) + count++; + + /* Main loop */ + current = 0; + while ( 1 ) { + + /* Identify current widget */ + widget = find_widget ( widgets, current ); + if ( ! widget ) + return -ENOENT; + + /* Redraw current widget */ + draw_widget ( widgets, widget ); + + /* Process keypress */ + key = edit_widget ( widgets, widget, getkey ( 0 ) ); + switch ( key ) { + case KEY_UP: + if ( current > 0 ) + current--; + break; + case KEY_DOWN: + if ( ++current == count ) + current--; + break; + case TAB: + if ( ++current == count ) + current = 0; + break; + case KEY_ENTER: + current++; + if ( current >= count ) + return 0; + break; + case CTRL_C: + case ESC: + return -ECANCELED; + default: + /* Do nothing for unrecognised keys or edit errors */ + break; + } + } +} + +/** + * Present text widget user interface + * + * @v widgets Text widget set + * @ret rc Return status code + */ +int widget_ui ( struct widgets *widgets ) { + int rc; + + /* Initialise UI */ + initscr(); + start_color(); + color_set ( CPAIR_NORMAL, NULL ); + erase(); + + /* Run main loop */ + rc = widget_ui_loop ( widgets ); + + /* Terminate UI */ + color_set ( CPAIR_NORMAL, NULL ); + endwin(); + + return rc; +} diff --git a/src/include/ipxe/editbox.h b/src/include/ipxe/editbox.h index c14bbdc5b..1f62485fe 100644 --- a/src/include/ipxe/editbox.h +++ b/src/include/ipxe/editbox.h @@ -11,51 +11,39 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include /** An editable text box widget */ struct edit_box { + /** Text widget */ + struct widget widget; /** Editable string */ struct edit_string string; - /** Containing window */ - WINDOW *win; - /** Row */ - unsigned int row; - /** Starting column */ - unsigned int col; - /** Width */ - unsigned int width; /** First displayed character */ unsigned int first; - /** Flags */ - unsigned int flags; }; -/** Editable text box widget flags */ -enum edit_box_flags { - /** Show stars instead of contents (for password widgets) */ - EDITBOX_STARS = 0x0001, -}; - -extern void init_editbox ( struct edit_box *box, char **buf, - WINDOW *win, unsigned int row, unsigned int col, - unsigned int width, unsigned int flags ) - __attribute__ (( nonnull (1, 2) )); -extern void draw_editbox ( struct edit_box *box ) __nonnull; -static inline int edit_editbox ( struct edit_box *box, int key ) __nonnull; +extern struct widget_operations editbox_operations; /** - * Edit text box widget + * Initialise text box widget * * @v box Editable text box widget - * @v key Key pressed by user - * @ret key Key returned to application, or zero - * - * You must call draw_editbox() to update the display after calling - * edit_editbox(). - * + * @v row Row + * @v col Starting column + * @v width Width + * @v flags Flags + * @v buf Dynamically allocated string buffer */ -static inline int edit_editbox ( struct edit_box *box, int key ) { - return edit_string ( &box->string, key ); +static inline __attribute__ (( always_inline )) void +init_editbox ( struct edit_box *box, unsigned int row, unsigned int col, + unsigned int width, unsigned int flags, char **buf ) { + + init_widget ( &box->widget, &editbox_operations, row, col, + width, ( flags | WIDGET_EDITABLE ) ); + init_editstring ( &box->string, buf ); + if ( *buf ) + box->string.cursor = strlen ( *buf ); } #endif /* _IPXE_EDITBOX_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 96981dd06..a9ce6569b 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -417,6 +417,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_x25519 ( ERRFILE_OTHER | 0x005f0000 ) #define ERRFILE_des ( ERRFILE_OTHER | 0x00600000 ) #define ERRFILE_editstring ( ERRFILE_OTHER | 0x00610000 ) +#define ERRFILE_widget_ui ( ERRFILE_OTHER | 0x00620000 ) /** @} */ diff --git a/src/include/ipxe/label.h b/src/include/ipxe/label.h new file mode 100644 index 000000000..48e36cb2f --- /dev/null +++ b/src/include/ipxe/label.h @@ -0,0 +1,42 @@ +#ifndef _IPXE_LABEL_H +#define _IPXE_LABEL_H + +/** @file + * + * Text label widget + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** A text label widget */ +struct label { + /** Text widget */ + struct widget widget; + /** Label text */ + const char *text; +}; + +extern struct widget_operations label_operations; + +/** + * Initialise text label widget + * + * @v label Text label widget + * @v row Row + * @v col Starting column + * @v width Width + * @v text Label text + */ +static inline __attribute__ (( always_inline )) void +init_label ( struct label *label, unsigned int row, unsigned int col, + unsigned int width, const char *text ) { + + init_widget ( &label->widget, &label_operations, row, col, width, 0 ); + label->text = text; +} + +#endif /* _IPXE_LABEL_H */ diff --git a/src/include/ipxe/widget.h b/src/include/ipxe/widget.h new file mode 100644 index 000000000..fe93b9f5d --- /dev/null +++ b/src/include/ipxe/widget.h @@ -0,0 +1,151 @@ +#ifndef _IPXE_WIDGET_H +#define _IPXE_WIDGET_H + +/** @file + * + * Text widgets + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** A text widget set */ +struct widgets { + /** List of widgets (in tab order) */ + struct list_head list; + /** Containing window */ + WINDOW *win; +}; + +/** A text widget */ +struct widget { + /** List of widgets (in tab order) */ + struct list_head list; + /** Widget operations */ + struct widget_operations *op; + + /** Row */ + unsigned int row; + /** Starting column */ + unsigned int col; + /** Width */ + unsigned int width; + /** Flags */ + unsigned int flags; +}; + +/** Text widget flags */ +enum widget_flags { + /** Widget may have input focus */ + WIDGET_EDITABLE = 0x0001, + /** Widget contains a secret */ + WIDGET_SECRET = 0x0002, +}; + +/** Text widget operations */ +struct widget_operations { + /** + * Draw widget + * + * @v widgets Text widget set + * @v widget Text widget + */ + void ( * draw ) ( struct widgets *widgets, struct widget *widget ); + /** + * Edit widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + * + * This will not update the display: you must call the draw() + * method to ensure that any changes to an editable widget are + * displayed to the user. + */ + int ( * edit ) ( struct widgets *widgets, struct widget *widget, + int key ); +}; + +/** + * Initialise text widget set + * + * @v widgets Text widget set + * @v win Containing window + */ +static inline __attribute__ (( always_inline )) void +init_widgets ( struct widgets *widgets, WINDOW *win ) { + + INIT_LIST_HEAD ( &widgets->list ); + widgets->win = ( win ? win : stdscr ); +} + +/** + * Initialise text widget + * + * @v widget Text widget + * @v op Text widget operations + * @v row Row + * @v col Starting column + * @v width Width + */ +static inline __attribute__ (( always_inline )) void +init_widget ( struct widget *widget, struct widget_operations *op, + unsigned int row, unsigned int col, unsigned int width, + unsigned int flags ) { + + widget->op = op; + widget->row = row; + widget->col = col; + widget->width = width; + widget->flags = flags; +} + +/** + * Append text widget + * + * @v widgets Text widget set + * @v widget Text widget + */ +static inline __attribute__ (( always_inline )) void +add_widget ( struct widgets *widgets, struct widget *widget ) { + + list_add_tail ( &widget->list, &widgets->list ); +} + +/** + * Draw text widget + * + * @v widgets Text widget set + * @v widget Text widget + */ +static inline __attribute__ (( always_inline )) void +draw_widget ( struct widgets *widgets, struct widget *widget ) { + + widget->op->draw ( widgets, widget ); +} + +/** + * Edit text widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + * + * This will not update the display: you must call draw_widget() to + * ensure that any changes to an editable widget are displayed to the + * user. + */ +static inline __attribute__ (( always_inline )) int +edit_widget ( struct widgets *widgets, struct widget *widget, int key ) { + + return widget->op->edit ( widgets, widget, key ); +} + +extern int widget_ui ( struct widgets *widgets ); + +#endif /* _IPXE_WIDGET_H */ -- cgit v1.2.3-55-g7522 From e965f179e1654103eca33feed7a9cc4c51d91be6 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 23 May 2024 13:18:16 +0100 Subject: [libc] Add stpcpy() Signed-off-by: Michael Brown --- src/core/string.c | 17 +++++++++++++++-- src/include/string.h | 1 + src/tests/string_test.c | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/core/string.c b/src/core/string.c index 9a1b9b72a..364c4cf0e 100644 --- a/src/core/string.c +++ b/src/core/string.c @@ -321,9 +321,9 @@ char * strstr ( const char *haystack, const char *needle ) { * * @v dest Destination string * @v src Source string - * @ret dest Destination string + * @ret dnul Terminating NUL of destination string */ -char * strcpy ( char *dest, const char *src ) { +char * stpcpy ( char *dest, const char *src ) { const uint8_t *src_bytes = ( ( const uint8_t * ) src ); uint8_t *dest_bytes = ( ( uint8_t * ) dest ); @@ -333,6 +333,19 @@ char * strcpy ( char *dest, const char *src ) { if ( ! *dest_bytes ) break; } + return ( ( char * ) dest_bytes ); +} + +/** + * Copy string + * + * @v dest Destination string + * @v src Source string + * @ret dest Destination string + */ +char * strcpy ( char *dest, const char *src ) { + + stpcpy ( dest, src ); return dest; } diff --git a/src/include/string.h b/src/include/string.h index 5f5aecb92..4ee9c7344 100644 --- a/src/include/string.h +++ b/src/include/string.h @@ -43,6 +43,7 @@ extern char * __pure strchr ( const char *src, int character ) __nonnull; extern char * __pure strrchr ( const char *src, int character ) __nonnull; extern char * __pure strstr ( const char *haystack, const char *needle ) __nonnull; +extern char * stpcpy ( char *dest, const char *src ) __nonnull; extern char * strcpy ( char *dest, const char *src ) __nonnull; extern char * strncpy ( char *dest, const char *src, size_t max ) __nonnull; extern char * strcat ( char *dest, const char *src ) __nonnull; diff --git a/src/tests/string_test.c b/src/tests/string_test.c index 3afb8deb2..c0436c3ad 100644 --- a/src/tests/string_test.c +++ b/src/tests/string_test.c @@ -204,6 +204,24 @@ static void string_test_exec ( void ) { free ( dup ); } + /* Test stpcpy() */ + { + const char longer[12] = "duplicateme"; + const char shorter[6] = "hello"; + char dest[12]; + char *dnul; + + dnul = stpcpy ( dest, longer ); + ok ( *dnul == '\0' ); + ok ( dnul == &dest[11] ); + ok ( memcmp ( dest, longer, 12 ) == 0 ); + dnul = stpcpy ( dest, shorter ); + ok ( *dnul == '\0' ); + ok ( dnul == &dest[5] ); + ok ( memcmp ( dest, shorter, 6 ) == 0 ); + ok ( memcmp ( ( dest + 6 ), ( longer + 6 ), 6 ) == 0 ); + } + /* Test strcpy() */ { const char longer[7] = "copyme"; -- cgit v1.2.3-55-g7522 From bb4a10696fb6541fdcfc2c057cf698de37fa5eba Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 14 Jun 2024 11:47:55 +0100 Subject: [hci] Draw all widgets on the standard screen The curses concept of a window has been supported but never actively used in iPXE since the mucurses library was first implemented in 2006. Simplify the code by removing the ability to place a widget set in a specified window, and instead use the standard screen for all drawing operations. This simplification allows the widget set parameter to be omitted for the draw_widget() and edit_widget() operations, since the only reason for its inclusion was to provide access to the specified window. Signed-off-by: Michael Brown --- src/hci/mucurses/widgets/editbox.c | 11 ++++------- src/hci/mucurses/widgets/label.c | 9 +++------ src/hci/tui/login_ui.c | 2 +- src/hci/tui/settings_ui.c | 6 +++--- src/hci/tui/widget_ui.c | 6 +++--- src/include/ipxe/widget.h | 23 +++++++---------------- 6 files changed, 21 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/hci/mucurses/widgets/editbox.c b/src/hci/mucurses/widgets/editbox.c index 79e7cdd16..c024688ab 100644 --- a/src/hci/mucurses/widgets/editbox.c +++ b/src/hci/mucurses/widgets/editbox.c @@ -39,10 +39,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * Draw text box widget * - * @v widgets Text widget set * @v widget Text widget */ -static void draw_editbox ( struct widgets *widgets, struct widget *widget ) { +static void draw_editbox ( struct widget *widget ) { struct edit_box *box = container_of ( widget, struct edit_box, widget ); const char *content = *(box->string.buf); size_t width = widget->width; @@ -79,21 +78,19 @@ static void draw_editbox ( struct widgets *widgets, struct widget *widget ) { /* Print box content and move cursor */ color_set ( CPAIR_EDIT, NULL ); - mvwprintw ( widgets->win, widget->row, widget->col, "%s", buf ); - wmove ( widgets->win, widget->row, ( widget->col + cursor_offset ) ); + mvprintw ( widget->row, widget->col, "%s", buf ); + move ( widget->row, ( widget->col + cursor_offset ) ); color_set ( CPAIR_NORMAL, NULL ); } /** * Edit text box widget * - * @v widgets Text widget set * @v widget Text widget * @v key Key pressed by user * @ret key Key returned to application, or zero */ -static int edit_editbox ( struct widgets *widgets __unused, - struct widget *widget, int key ) { +static int edit_editbox ( struct widget *widget, int key ) { struct edit_box *box = container_of ( widget, struct edit_box, widget ); return edit_string ( &box->string, key ); diff --git a/src/hci/mucurses/widgets/label.c b/src/hci/mucurses/widgets/label.c index 29645a6ad..29057f089 100644 --- a/src/hci/mucurses/widgets/label.c +++ b/src/hci/mucurses/widgets/label.c @@ -36,10 +36,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * Draw text label widget * - * @v widgets Text widget set * @v widget Text widget */ -static void draw_label ( struct widgets *widgets, struct widget *widget ) { +static void draw_label ( struct widget *widget ) { struct label *label = container_of ( widget, struct label, widget ); unsigned int width = widget->width; unsigned int col = widget->col; @@ -51,20 +50,18 @@ static void draw_label ( struct widgets *widgets, struct widget *widget ) { /* Print label content */ attron ( A_BOLD ); - mvwprintw ( widgets->win, widget->row, col, "%s", text ); + mvprintw ( widget->row, col, "%s", text ); attroff ( A_BOLD ); } /** * Edit text label widget * - * @v widgets Text widget set * @v widget Text widget * @v key Key pressed by user * @ret key Key returned to application, or zero */ -static int edit_label ( struct widgets *widgets __unused, - struct widget *widget __unused, int key ) { +static int edit_label ( struct widget *widget __unused, int key ) { /* Cannot be edited */ return key; diff --git a/src/hci/tui/login_ui.c b/src/hci/tui/login_ui.c index b76afb92e..9c5148865 100644 --- a/src/hci/tui/login_ui.c +++ b/src/hci/tui/login_ui.c @@ -67,7 +67,7 @@ int login_ui ( void ) { /* Construct user interface */ memset ( &widgets, 0, sizeof ( widgets ) ); - init_widgets ( &widgets.widgets, NULL ); + init_widgets ( &widgets.widgets ); init_label ( &widgets.username_label, USERNAME_LABEL_ROW, WIDGET_COL, WIDGET_WIDTH, "Username" ); init_label ( &widgets.password_label, PASSWORD_LABEL_ROW, WIDGET_COL, diff --git a/src/hci/tui/settings_ui.c b/src/hci/tui/settings_ui.c index 53bf24d1a..045d97853 100644 --- a/src/hci/tui/settings_ui.c +++ b/src/hci/tui/settings_ui.c @@ -253,7 +253,7 @@ static void draw_setting_row ( struct settings_ui *ui ) { static int edit_setting ( struct settings_ui *ui, int key ) { assert ( ui->row.setting.name != NULL ); ui->row.editing = 1; - return edit_widget ( &ui->widgets, &ui->row.editbox.widget, key ); + return edit_widget ( &ui->row.editbox.widget, key ); } /** @@ -457,7 +457,7 @@ static int main_loop ( struct settings *settings ) { /* Print initial screen content */ color_set ( CPAIR_NORMAL, NULL ); memset ( &ui, 0, sizeof ( ui ) ); - init_widgets ( &ui.widgets, NULL ); + init_widgets ( &ui.widgets ); select_settings ( &ui, settings ); while ( 1 ) { @@ -481,7 +481,7 @@ static int main_loop ( struct settings *settings ) { assert ( ui.row.setting.name != NULL ); /* Redraw edit box */ - draw_widget ( &ui.widgets, &ui.row.editbox.widget ); + draw_widget ( &ui.row.editbox.widget ); /* Process keypress */ key = edit_setting ( &ui, getkey ( 0 ) ); diff --git a/src/hci/tui/widget_ui.c b/src/hci/tui/widget_ui.c index 5079eefdd..961545cf4 100644 --- a/src/hci/tui/widget_ui.c +++ b/src/hci/tui/widget_ui.c @@ -68,7 +68,7 @@ static int widget_ui_loop ( struct widgets *widgets ) { /* Draw all widgets */ list_for_each_entry ( widget, &widgets->list, list ) - draw_widget ( widgets, widget ); + draw_widget ( widget ); /* Count editable widgets */ count = 0; @@ -85,10 +85,10 @@ static int widget_ui_loop ( struct widgets *widgets ) { return -ENOENT; /* Redraw current widget */ - draw_widget ( widgets, widget ); + draw_widget ( widget ); /* Process keypress */ - key = edit_widget ( widgets, widget, getkey ( 0 ) ); + key = edit_widget ( widget, getkey ( 0 ) ); switch ( key ) { case KEY_UP: if ( current > 0 ) diff --git a/src/include/ipxe/widget.h b/src/include/ipxe/widget.h index fe93b9f5d..0d8af1030 100644 --- a/src/include/ipxe/widget.h +++ b/src/include/ipxe/widget.h @@ -16,8 +16,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); struct widgets { /** List of widgets (in tab order) */ struct list_head list; - /** Containing window */ - WINDOW *win; }; /** A text widget */ @@ -50,14 +48,12 @@ struct widget_operations { /** * Draw widget * - * @v widgets Text widget set * @v widget Text widget */ - void ( * draw ) ( struct widgets *widgets, struct widget *widget ); + void ( * draw ) ( struct widget *widget ); /** * Edit widget * - * @v widgets Text widget set * @v widget Text widget * @v key Key pressed by user * @ret key Key returned to application, or zero @@ -66,21 +62,18 @@ struct widget_operations { * method to ensure that any changes to an editable widget are * displayed to the user. */ - int ( * edit ) ( struct widgets *widgets, struct widget *widget, - int key ); + int ( * edit ) ( struct widget *widget, int key ); }; /** * Initialise text widget set * * @v widgets Text widget set - * @v win Containing window */ static inline __attribute__ (( always_inline )) void -init_widgets ( struct widgets *widgets, WINDOW *win ) { +init_widgets ( struct widgets *widgets ) { INIT_LIST_HEAD ( &widgets->list ); - widgets->win = ( win ? win : stdscr ); } /** @@ -119,19 +112,17 @@ add_widget ( struct widgets *widgets, struct widget *widget ) { /** * Draw text widget * - * @v widgets Text widget set * @v widget Text widget */ static inline __attribute__ (( always_inline )) void -draw_widget ( struct widgets *widgets, struct widget *widget ) { +draw_widget ( struct widget *widget ) { - widget->op->draw ( widgets, widget ); + widget->op->draw ( widget ); } /** * Edit text widget * - * @v widgets Text widget set * @v widget Text widget * @v key Key pressed by user * @ret key Key returned to application, or zero @@ -141,9 +132,9 @@ draw_widget ( struct widgets *widgets, struct widget *widget ) { * user. */ static inline __attribute__ (( always_inline )) int -edit_widget ( struct widgets *widgets, struct widget *widget, int key ) { +edit_widget ( struct widget *widget, int key ) { - return widget->op->edit ( widgets, widget, key ); + return widget->op->edit ( widget, key ); } extern int widget_ui ( struct widgets *widgets ); -- cgit v1.2.3-55-g7522 From bf98eae5daeb0b9281562e70fdf3768a629adde8 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 14 Jun 2024 11:51:57 +0100 Subject: [hci] Split out msg() and alert() from settings UI code The msg() and alert() functions currently defined in settings_ui.c provide a general-purpose facility for printing messages centred on the screen. Split this out to a separate file to allow for reuse by the form presentation code. Signed-off-by: Michael Brown --- src/hci/tui/message.c | 109 +++++++++++++++++++++++++++++++++++++++++++++ src/hci/tui/settings_ui.c | 78 +++----------------------------- src/include/ipxe/message.h | 16 +++++++ 3 files changed, 131 insertions(+), 72 deletions(-) create mode 100644 src/hci/tui/message.c create mode 100644 src/include/ipxe/message.h (limited to 'src') diff --git a/src/hci/tui/message.c b/src/hci/tui/message.c new file mode 100644 index 000000000..e3331d655 --- /dev/null +++ b/src/hci/tui/message.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Message printing + * + */ + +#include +#include +#include +#include +#include +#include + +/** + * Print message centred on specified row + * + * @v row Row + * @v fmt printf() format string + * @v args printf() argument list + */ +static void vmsg ( unsigned int row, const char *fmt, va_list args ) { + char buf[COLS]; + size_t len; + + len = vsnprintf ( buf, sizeof ( buf ), fmt, args ); + mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf ); +} + +/** + * Print message centred on specified row + * + * @v row Row + * @v fmt printf() format string + * @v .. printf() arguments + */ +void msg ( unsigned int row, const char *fmt, ... ) { + va_list args; + + va_start ( args, fmt ); + vmsg ( row, fmt, args ); + va_end ( args ); +} + +/** + * Clear message on specified row + * + * @v row Row + */ +void clearmsg ( unsigned int row ) { + move ( row, 0 ); + clrtoeol(); +} + +/** + * Show alert message + * + * @v row Row + * @v fmt printf() format string + * @v args printf() argument list + */ +static void valert ( unsigned int row, const char *fmt, va_list args ) { + + clearmsg ( row ); + color_set ( CPAIR_ALERT, NULL ); + vmsg ( row, fmt, args ); + sleep ( 2 ); + color_set ( CPAIR_NORMAL, NULL ); + clearmsg ( row ); +} + +/** + * Show alert message + * + * @v row Row + * @v fmt printf() format string + * @v ... printf() arguments + */ +void alert ( unsigned int row, const char *fmt, ... ) { + va_list args; + + va_start ( args, fmt ); + valert ( row, fmt, args ); + va_end ( args ); +} diff --git a/src/hci/tui/settings_ui.c b/src/hci/tui/settings_ui.c index 045d97853..10c942321 100644 --- a/src/hci/tui/settings_ui.c +++ b/src/hci/tui/settings_ui.c @@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include @@ -266,75 +267,6 @@ static int save_setting ( struct settings_ui *ui ) { return storef_setting ( ui->settings, &ui->row.setting, ui->row.buf ); } -/** - * Print message centred on specified row - * - * @v row Row - * @v fmt printf() format string - * @v args printf() argument list - */ -static void vmsg ( unsigned int row, const char *fmt, va_list args ) { - char buf[COLS]; - size_t len; - - len = vsnprintf ( buf, sizeof ( buf ), fmt, args ); - mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf ); -} - -/** - * Print message centred on specified row - * - * @v row Row - * @v fmt printf() format string - * @v .. printf() arguments - */ -static void msg ( unsigned int row, const char *fmt, ... ) { - va_list args; - - va_start ( args, fmt ); - vmsg ( row, fmt, args ); - va_end ( args ); -} - -/** - * Clear message on specified row - * - * @v row Row - */ -static void clearmsg ( unsigned int row ) { - move ( row, 0 ); - clrtoeol(); -} - -/** - * Print alert message - * - * @v fmt printf() format string - * @v args printf() argument list - */ -static void valert ( const char *fmt, va_list args ) { - clearmsg ( ALERT_ROW ); - color_set ( CPAIR_ALERT, NULL ); - vmsg ( ALERT_ROW, fmt, args ); - sleep ( 2 ); - color_set ( CPAIR_NORMAL, NULL ); - clearmsg ( ALERT_ROW ); -} - -/** - * Print alert message - * - * @v fmt printf() format string - * @v ... printf() arguments - */ -static void alert ( const char *fmt, ... ) { - va_list args; - - va_start ( args, fmt ); - valert ( fmt, args ); - va_end ( args ); -} - /** * Draw title row * @@ -488,8 +420,10 @@ static int main_loop ( struct settings *settings ) { switch ( key ) { case CR: case LF: - if ( ( rc = save_setting ( &ui ) ) != 0 ) - alert ( " %s ", strerror ( rc ) ); + if ( ( rc = save_setting ( &ui ) ) != 0 ) { + alert ( ALERT_ROW, " %s ", + strerror ( rc ) ); + } /* Fall through */ case CTRL_C: select_setting_row ( &ui, ui.scroll.current ); @@ -526,7 +460,7 @@ static int main_loop ( struct settings *settings ) { break; if ( ( rc = delete_setting ( ui.settings, &ui.row.setting ) ) != 0 ){ - alert ( " %s ", strerror ( rc ) ); + alert ( ALERT_ROW, " %s ", strerror ( rc ) ); } select_setting_row ( &ui, ui.scroll.current ); redraw = 1; diff --git a/src/include/ipxe/message.h b/src/include/ipxe/message.h new file mode 100644 index 000000000..e2e783740 --- /dev/null +++ b/src/include/ipxe/message.h @@ -0,0 +1,16 @@ +#ifndef _IPXE_MESSAGE_H +#define _IPXE_MESSAGE_H + +/** @file + * + * Message printing + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern void msg ( unsigned int row, const char *fmt, ... ); +extern void clearmsg ( unsigned int row ); +extern void alert ( unsigned int row, const char *fmt, ... ); + +#endif /* _IPXE_MESSAGE_H */ -- cgit v1.2.3-55-g7522 From 76e0933d781474acf52000cba0afaebe32361667 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 18 Jun 2024 15:17:03 -0700 Subject: [hci] Rename "item" command's first parameter from "label" to "name" Switch terminology for the "item" command from "item