[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 <mcb30@ipxe.org>
pull/1194/head
Michael Brown 2024-04-15 15:59:49 +01:00
parent 27ecc36c0b
commit 40b5112440
8 changed files with 199 additions and 142 deletions

View File

@ -24,8 +24,10 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <stdlib.h>
#include <ipxe/keys.h> #include <ipxe/keys.h>
#include <ipxe/editstring.h> #include <ipxe/editstring.h>
@ -35,17 +37,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
* *
*/ */
static void insert_delete ( struct edit_string *string, size_t delete_len, static __attribute__ (( nonnull ( 1 ) )) int
const char *insert_text ) insert_delete ( struct edit_string *string, size_t delete_len,
__attribute__ (( nonnull (1) )); const char *insert_text );
static void insert_character ( struct edit_string *string, static __nonnull int insert_character ( struct edit_string *string,
unsigned int character ) __nonnull; unsigned int character );
static void delete_character ( struct edit_string *string ) __nonnull; static __nonnull void delete_character ( struct edit_string *string );
static void backspace ( struct edit_string *string ) __nonnull; static __nonnull void backspace ( struct edit_string *string );
static void previous_word ( struct edit_string *string ) __nonnull; static __nonnull void previous_word ( struct edit_string *string );
static void kill_word ( struct edit_string *string ) __nonnull; static __nonnull void kill_word ( struct edit_string *string );
static void kill_sol ( struct edit_string *string ) __nonnull; static __nonnull void kill_sol ( struct edit_string *string );
static void kill_eol ( struct edit_string *string ) __nonnull; static __nonnull void kill_eol ( struct edit_string *string );
/** /**
* Insert and/or delete text within an editable 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 string Editable string
* @v delete_len Length of text to delete from current cursor position * @v delete_len Length of text to delete from current cursor position
* @v insert_text Text to insert at current cursor position, or NULL * @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, static int insert_delete ( struct edit_string *string, size_t delete_len,
const char *insert_text ) { const char *insert_text ) {
size_t old_len, max_delete_len, insert_len, max_insert_len, new_len; size_t old_len, max_delete_len, insert_len, new_len;
char *buf;
char *tmp;
/* Calculate lengths */ /* Calculate lengths */
old_len = strlen ( string->buf ); buf = *(string->buf);
old_len = ( buf ? strlen ( buf ) : 0 );
assert ( string->cursor <= old_len ); assert ( string->cursor <= old_len );
max_delete_len = ( old_len - string->cursor ); max_delete_len = ( old_len - string->cursor );
if ( delete_len > max_delete_len ) if ( delete_len > max_delete_len )
delete_len = max_delete_len; delete_len = max_delete_len;
insert_len = ( insert_text ? strlen ( insert_text ) : 0 ); 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 ); 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 */ /* Fill in edit history */
string->mod_start = string->cursor; string->mod_start = string->cursor;
string->mod_end = ( ( new_len > old_len ) ? new_len : old_len ); string->mod_end = ( ( new_len > old_len ) ? new_len : old_len );
/* Move data following the cursor */ /* Move data following the cursor */
memmove ( ( string->buf + string->cursor + insert_len ), memmove ( ( buf + string->cursor + insert_len ),
( string->buf + string->cursor + delete_len ), ( buf + string->cursor + delete_len ),
( max_delete_len + 1 - delete_len ) ); ( max_delete_len - delete_len ) );
/* Copy inserted text to cursor position */ /* 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; 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 string Editable string
* @v character Character to insert * @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 ) { unsigned int character ) {
char insert_text[2] = { character, '\0' }; 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 * @v string Editable string
*/ */
static void delete_character ( struct edit_string *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 * @v string Editable string
*/ */
static void backspace ( struct edit_string *string ) { static void backspace ( struct edit_string *string ) {
if ( string->cursor > 0 ) { if ( string->cursor > 0 ) {
string->cursor--; string->cursor--;
delete_character ( string ); delete_character ( string );
@ -123,14 +152,16 @@ static void backspace ( struct edit_string *string ) {
* @v string Editable string * @v string Editable string
*/ */
static void previous_word ( struct edit_string *string ) { static void previous_word ( struct edit_string *string ) {
while ( string->cursor && const char *buf = *(string->buf);
isspace ( string->buf[ string->cursor - 1 ] ) ) { size_t cursor = string->cursor;
string->cursor--;
while ( cursor && isspace ( buf[ cursor - 1 ] ) ) {
cursor--;
} }
while ( string->cursor && while ( cursor && ( ! isspace ( buf[ cursor - 1 ] ) ) ) {
( ! isspace ( string->buf[ string->cursor - 1 ] ) ) ) { cursor--;
string->cursor--;
} }
string->cursor = cursor;
} }
/** /**
@ -140,8 +171,11 @@ static void previous_word ( struct edit_string *string ) {
*/ */
static void kill_word ( struct edit_string *string ) { static void kill_word ( struct edit_string *string ) {
size_t old_cursor = string->cursor; size_t old_cursor = string->cursor;
int rc;
previous_word ( string ); 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 ) { static void kill_sol ( struct edit_string *string ) {
size_t old_cursor = string->cursor; size_t old_cursor = string->cursor;
int rc;
string->cursor = 0; 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 * @v string Editable string
*/ */
static void kill_eol ( struct edit_string *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 * Replace editable string
* *
* @v string 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; 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 string Editable string
* @v key Key pressed by user * @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. * Handles keypresses and updates the content of the editable string.
* Basic line editing facilities (delete/insert/cursor) are supported. * Basic line editing facilities (delete/insert/cursor) are supported.
* If edit_string() understands and uses the keypress it will return * If edit_string() understands and uses the keypress it will return
* zero, otherwise it will return the original key. * 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 * The string's edit history will be updated to allow the caller to
* efficiently bring the display into sync with the string content. * 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 ) { int edit_string ( struct edit_string *string, int key ) {
const char *buf = *(string->buf);
size_t len = ( buf ? strlen ( buf ) : 0 );
int retval = 0; int retval = 0;
size_t len = strlen ( string->buf );
/* Prepare edit history */ /* Prepare edit history */
string->last_cursor = string->cursor; string->last_cursor = string->cursor;
@ -204,7 +264,7 @@ int edit_string ( struct edit_string *string, int key ) {
/* Interpret key */ /* Interpret key */
if ( ( key >= 0x20 ) && ( key <= 0x7e ) ) { if ( ( key >= 0x20 ) && ( key <= 0x7e ) ) {
/* Printable character; insert at current position */ /* Printable character; insert at current position */
insert_character ( string, key ); retval = insert_character ( string, key );
} else switch ( key ) { } else switch ( key ) {
case KEY_BACKSPACE: case KEY_BACKSPACE:
/* Backspace */ /* Backspace */

View File

@ -39,20 +39,19 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
* Initialise text box widget * Initialise text box widget
* *
* @v box Editable text box widget * @v box Editable text box widget
* @v buf Text buffer * @v buf Dynamically allocated string buffer
* @v len Size of text buffer
* @v win Containing window * @v win Containing window
* @v row Row * @v row Row
* @v col Starting column * @v col Starting column
* @v width Width * @v width Width
* @v flags Flags * @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, WINDOW *win, unsigned int row, unsigned int col,
unsigned int width, unsigned int flags ) { unsigned int width, unsigned int flags ) {
memset ( box, 0, sizeof ( *box ) ); memset ( box, 0, sizeof ( *box ) );
init_editstring ( &box->string, buf, len ); init_editstring ( &box->string, buf );
box->string.cursor = strlen ( buf ); box->string.cursor = ( *buf ? strlen ( *buf ) : 0 );
box->win = ( win ? win : stdscr ); box->win = ( win ? win : stdscr );
box->row = row; box->row = row;
box->col = col; 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 ) { void draw_editbox ( struct edit_box *box ) {
const char *content = *(box->string.buf);
size_t width = box->width; size_t width = box->width;
char buf[ width + 1 ]; char buf[ width + 1 ];
signed int cursor_offset, underflow, overflow, first; signed int cursor_offset, underflow, overflow, first;
@ -90,13 +90,13 @@ void draw_editbox ( struct edit_box *box ) {
/* Construct underscore-padded string portion */ /* Construct underscore-padded string portion */
memset ( buf, '_', width ); memset ( buf, '_', width );
buf[width] = '\0'; buf[width] = '\0';
len = ( strlen ( box->string.buf ) - first ); len = ( content ? ( strlen ( content ) - first ) : 0 );
if ( len > width ) if ( len > width )
len = width; len = width;
if ( box->flags & EDITBOX_STARS ) { if ( box->flags & EDITBOX_STARS ) {
memset ( buf, '*', len ); memset ( buf, '*', len );
} else { } else {
memcpy ( buf, ( box->string.buf + first ), len ); memcpy ( buf, ( content + first ), len );
} }
/* Print box content and move cursor */ /* Print box content and move cursor */

View File

@ -38,8 +38,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
* *
*/ */
#define READLINE_MAX 1024
/** /**
* Synchronise console with edited string * 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_start = string->mod_start;
unsigned int mod_end = string->mod_end; unsigned int mod_end = string->mod_end;
unsigned int cursor = string->last_cursor; 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 */ /* Expand region back to old cursor position if applicable */
if ( mod_start > string->last_cursor ) if ( mod_start > string->last_cursor )
@ -67,7 +66,7 @@ static void sync_console ( struct edit_string *string ) {
/* Print modified region */ /* Print modified region */
while ( cursor < mod_end ) { while ( cursor < mod_end ) {
putchar ( ( cursor >= len ) ? ' ' : string->buf[cursor] ); putchar ( ( cursor >= len ) ? ' ' : buf[cursor] );
cursor++; cursor++;
} }
@ -259,15 +258,11 @@ int readline_history ( const char *prompt, const char *prefill,
struct readline_history *history, unsigned long timeout, struct readline_history *history, unsigned long timeout,
char **line ) { char **line ) {
struct edit_string string; struct edit_string string;
char *buf;
int key; int key;
int move_by; int move_by;
const char *new_string; const char *new_string;
int rc; int rc;
/* Avoid returning uninitialised data on error */
*line = NULL;
/* Display prompt, if applicable */ /* Display prompt, if applicable */
if ( prompt ) if ( prompt )
printf ( "%s", prompt ); printf ( "%s", prompt );
@ -275,20 +270,15 @@ int readline_history ( const char *prompt, const char *prefill,
/* Ensure cursor is visible */ /* Ensure cursor is visible */
printf ( "\033[?25h" ); printf ( "\033[?25h" );
/* Allocate buffer and initialise editable string */ /* Initialise editable string */
buf = zalloc ( READLINE_MAX ); *line = NULL;
if ( ! buf ) {
rc = -ENOMEM;
goto done;
}
memset ( &string, 0, sizeof ( string ) ); memset ( &string, 0, sizeof ( string ) );
init_editstring ( &string, buf, READLINE_MAX ); init_editstring ( &string, line );
/* Prefill string, if applicable */ /* Prefill string */
if ( prefill ) { if ( ( rc = replace_string ( &string, prefill ) ) != 0 )
replace_string ( &string, prefill ); goto error;
sync_console ( &string ); sync_console ( &string );
}
while ( 1 ) { while ( 1 ) {
@ -296,7 +286,7 @@ int readline_history ( const char *prompt, const char *prefill,
key = getkey ( timeout ); key = getkey ( timeout );
if ( key < 0 ) { if ( key < 0 ) {
rc = -ETIMEDOUT; rc = -ETIMEDOUT;
goto done; goto error;
} }
timeout = 0; timeout = 0;
@ -307,17 +297,11 @@ int readline_history ( const char *prompt, const char *prefill,
switch ( key ) { switch ( key ) {
case CR: case CR:
case LF: case LF:
/* Shrink string (ignoring failures) */
*line = realloc ( buf,
( strlen ( buf ) + 1 /* NUL */ ) );
if ( ! *line )
*line = buf;
buf = NULL;
rc = 0; rc = 0;
goto done; goto done;
case CTRL_C: case CTRL_C:
rc = -ECANCELED; rc = -ECANCELED;
goto done; goto error;
case KEY_UP: case KEY_UP:
move_by = 1; move_by = 1;
break; break;
@ -325,13 +309,13 @@ int readline_history ( const char *prompt, const char *prefill,
move_by = -1; move_by = -1;
break; break;
default: default:
/* Do nothing */ /* Do nothing for unrecognised keys or edit errors */
break; break;
} }
/* Handle history movement, if applicable */ /* Handle history movement, if applicable */
if ( move_by && history ) { if ( move_by && history ) {
new_string = history_move ( history, move_by, buf ); new_string = history_move ( history, move_by, *line );
if ( new_string ) { if ( new_string ) {
replace_string ( &string, new_string ); replace_string ( &string, new_string );
sync_console ( &string ); sync_console ( &string );
@ -339,9 +323,11 @@ int readline_history ( const char *prompt, const char *prefill,
} }
} }
error:
free ( *line );
*line = NULL;
done: done:
putchar ( '\n' ); putchar ( '\n' );
free ( buf );
if ( history ) { if ( history ) {
if ( *line && (*line)[0] ) if ( *line && (*line)[0] )
history_append ( history, *line ); history_append ( history, *line );

View File

@ -30,6 +30,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/ */
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <errno.h> #include <errno.h>
#include <curses.h> #include <curses.h>
#include <ipxe/console.h> #include <ipxe/console.h>
@ -49,8 +50,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define EDITBOX_WIDTH 20U #define EDITBOX_WIDTH 20U
int login_ui ( void ) { int login_ui ( void ) {
char username[64]; char *username;
char password[64]; char *password;
struct edit_box username_box; struct edit_box username_box;
struct edit_box password_box; struct edit_box password_box;
struct edit_box *current_box = &username_box; struct edit_box *current_box = &username_box;
@ -58,19 +59,16 @@ int login_ui ( void ) {
int rc = -EINPROGRESS; int rc = -EINPROGRESS;
/* Fetch current setting values */ /* Fetch current setting values */
fetch_string_setting ( NULL, &username_setting, username, fetchf_setting_copy ( NULL, &username_setting, NULL, NULL, &username );
sizeof ( username ) ); fetchf_setting_copy ( NULL, &password_setting, NULL, NULL, &password );
fetch_string_setting ( NULL, &password_setting, password,
sizeof ( password ) );
/* Initialise UI */ /* Initialise UI */
initscr(); initscr();
start_color(); start_color();
init_editbox ( &username_box, username, sizeof ( username ), NULL, init_editbox ( &username_box, &username, NULL, USERNAME_ROW,
USERNAME_ROW, EDITBOX_COL, EDITBOX_WIDTH, 0 ); EDITBOX_COL, EDITBOX_WIDTH, 0 );
init_editbox ( &password_box, password, sizeof ( password ), NULL, init_editbox ( &password_box, &password, NULL, PASSWORD_ROW,
PASSWORD_ROW, EDITBOX_COL, EDITBOX_WIDTH, EDITBOX_COL, EDITBOX_WIDTH, EDITBOX_STARS );
EDITBOX_STARS );
/* Draw initial UI */ /* Draw initial UI */
color_set ( CPAIR_NORMAL, NULL ); color_set ( CPAIR_NORMAL, NULL );
@ -122,16 +120,15 @@ int login_ui ( void ) {
erase(); erase();
endwin(); endwin();
if ( rc != 0 ) /* Store settings on successful completion */
return rc; if ( rc == 0 )
rc = storef_setting ( NULL, &username_setting, username );
if ( rc == 0 )
rc = storef_setting ( NULL, &password_setting, password );
/* Store settings */ /* Free setting values */
if ( ( rc = store_setting ( NULL, &username_setting, username, free ( username );
strlen ( username ) ) ) != 0 ) free ( password );
return rc;
if ( ( rc = store_setting ( NULL, &password_setting, password,
strlen ( password ) ) ) != 0 )
return rc;
return 0; return rc;
} }

View File

@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <curses.h> #include <curses.h>
@ -58,12 +59,15 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
char start[0]; \ char start[0]; \
char pad1[1]; \ char pad1[1]; \
union { \ union { \
char settings[ cols - 1 - 1 - 1 - 1 ]; \ struct { \
char name[ cols - 1 - 1 - 1 - 1 - 1 ]; \
char pad2[1]; \
} __attribute__ (( packed )) settings; \
struct { \ struct { \
char name[15]; \ char name[15]; \
char pad2[1]; \ char pad2[1]; \
char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \ char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \
} setting; \ } __attribute__ (( packed )) setting; \
} u; \ } u; \
char pad3[1]; \ char pad3[1]; \
char nul; \ char nul; \
@ -92,8 +96,8 @@ struct settings_ui_row {
struct edit_box editbox; struct edit_box editbox;
/** Editing in progress flag */ /** Editing in progress flag */
int editing; int editing;
/** Buffer for setting's value */ /** Dynamically allocated buffer for setting's value */
char value[256]; /* enough size for a DHCP string */ char *buf;
}; };
/** A settings user interface */ /** A settings user interface */
@ -121,24 +125,22 @@ static unsigned int select_setting_row ( struct settings_ui *ui,
struct setting *previous = NULL; struct setting *previous = NULL;
unsigned int count = 0; unsigned int count = 0;
/* Free any previous setting value */
free ( ui->row.buf );
ui->row.buf = NULL;
/* Initialise structure */ /* Initialise structure */
memset ( &ui->row, 0, sizeof ( ui->row ) ); memset ( &ui->row, 0, sizeof ( ui->row ) );
ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first ); ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first );
/* Include parent settings block, if applicable */ /* Include parent settings block, if applicable */
if ( ui->settings->parent && ( count++ == index ) ) { if ( ui->settings->parent && ( count++ == index ) )
ui->row.settings = ui->settings->parent; ui->row.settings = ui->settings->parent;
snprintf ( ui->row.value, sizeof ( ui->row.value ),
"../" );
}
/* Include any child settings blocks, if applicable */ /* Include any child settings blocks, if applicable */
list_for_each_entry ( settings, &ui->settings->children, siblings ) { list_for_each_entry ( settings, &ui->settings->children, siblings ) {
if ( count++ == index ) { if ( count++ == index )
ui->row.settings = settings; ui->row.settings = settings;
snprintf ( ui->row.value, sizeof ( ui->row.value ),
"%s/", settings->name );
}
} }
/* Include any applicable settings */ /* Include any applicable settings */
@ -155,15 +157,14 @@ static unsigned int select_setting_row ( struct settings_ui *ui,
/* Read current setting value and origin */ /* Read current setting value and origin */
if ( count++ == index ) { if ( count++ == index ) {
fetchf_setting ( ui->settings, setting, &ui->row.origin, fetchf_setting_copy ( ui->settings, setting,
&ui->row.setting, ui->row.value, &ui->row.origin,
sizeof ( ui->row.value ) ); &ui->row.setting, &ui->row.buf );
} }
} }
/* Initialise edit box */ /* Initialise edit box */
init_editbox ( &ui->row.editbox, ui->row.value, init_editbox ( &ui->row.editbox, &ui->row.buf, NULL, ui->row.row,
sizeof ( ui->row.value ), NULL, ui->row.row,
( SETTINGS_LIST_COL + ( SETTINGS_LIST_COL +
offsetof ( typeof ( *text ), u.setting.value ) ), offsetof ( typeof ( *text ), u.setting.value ) ),
sizeof ( text->u.setting.value ), 0 ); 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 ) { static void draw_setting_row ( struct settings_ui *ui ) {
SETTING_ROW_TEXT ( COLS ) text; SETTING_ROW_TEXT ( COLS ) text;
unsigned int curs_offset; unsigned int curs_offset;
char *value; const char *value;
/* Fill row with spaces */ /* Fill row with spaces */
memset ( &text, ' ', sizeof ( text ) ); memset ( &text, ' ', sizeof ( text ) );
@ -207,10 +208,12 @@ static void draw_setting_row ( struct settings_ui *ui ) {
if ( ui->row.settings ) { if ( ui->row.settings ) {
/* Construct space-padded name */ /* Construct space-padded name */
curs_offset = ( offsetof ( typeof ( text ), u.settings ) + value = ( ( ui->row.settings == ui->settings->parent ) ?
string_copy ( text.u.settings, ".." : ui->row.settings->name );
ui->row.value, curs_offset = string_copy ( text.u.settings.name, value,
sizeof ( text.u.settings ) ) ); sizeof ( text.u.settings.name ) );
text.u.settings.name[curs_offset] = '/';
curs_offset += offsetof ( typeof ( text ), u.settings );
} else { } else {
@ -221,12 +224,12 @@ static void draw_setting_row ( struct settings_ui *ui ) {
sizeof ( text.u.setting.name ) ); sizeof ( text.u.setting.name ) );
/* Construct space-padded value */ /* Construct space-padded value */
value = ui->row.value; value = ui->row.buf;
if ( ! *value ) if ( ! ( value && value[0] ) )
value = "<not specified>"; value = "<not specified>";
curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) + curs_offset = string_copy ( text.u.setting.value, value,
string_copy ( text.u.setting.value, value, sizeof ( text.u.setting.value ) );
sizeof ( text.u.setting.value ))); curs_offset += offsetof ( typeof ( text ), u.setting.value );
} }
/* Print row */ /* Print row */
@ -257,7 +260,7 @@ static int edit_setting ( struct settings_ui *ui, int key ) {
*/ */
static int save_setting ( struct settings_ui *ui ) { static int save_setting ( struct settings_ui *ui ) {
assert ( ui->row.setting.name != NULL ); 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; redraw = 1;
break; break;
case CTRL_X: case CTRL_X:
select_setting_row ( &ui, -1U );
return 0; return 0;
case CR: case CR:
case LF: case LF:

View File

@ -36,7 +36,7 @@ enum edit_box_flags {
EDITBOX_STARS = 0x0001, 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, WINDOW *win, unsigned int row, unsigned int col,
unsigned int width, unsigned int flags ) unsigned int width, unsigned int flags )
__attribute__ (( nonnull (1, 2) )); __attribute__ (( nonnull (1, 2) ));

View File

@ -11,10 +11,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** An editable string */ /** An editable string */
struct edit_string { struct edit_string {
/** Buffer for string */ /** Dynamically allocated string buffer */
char *buf; char **buf;
/** Size of buffer (including terminating NUL) */
size_t len;
/** Cursor position */ /** Cursor position */
unsigned int cursor; unsigned int cursor;
@ -32,17 +30,28 @@ struct edit_string {
* Initialise editable string * Initialise editable string
* *
* @v string Editable string * @v string Editable string
* @v buf Buffer for string * @v buf Dynamically allocated string buffer
* @v len Length of 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, static inline __nonnull void init_editstring ( struct edit_string *string,
size_t len ) { char **buf ) {
string->buf = buf; string->buf = buf;
string->len = len;
} }
extern void replace_string ( struct edit_string *string, extern __attribute__ (( nonnull ( 1 ) )) int
const char *replacement ) __nonnull; replace_string ( struct edit_string *string, const char *replacement );
extern int edit_string ( struct edit_string *string, int key ) __nonnull;
extern __nonnull int edit_string ( struct edit_string *string, int key );
#endif /* _IPXE_EDITSTRING_H */ #endif /* _IPXE_EDITSTRING_H */

View File

@ -416,6 +416,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_efi_settings ( ERRFILE_OTHER | 0x005e0000 ) #define ERRFILE_efi_settings ( ERRFILE_OTHER | 0x005e0000 )
#define ERRFILE_x25519 ( ERRFILE_OTHER | 0x005f0000 ) #define ERRFILE_x25519 ( ERRFILE_OTHER | 0x005f0000 )
#define ERRFILE_des ( ERRFILE_OTHER | 0x00600000 ) #define ERRFILE_des ( ERRFILE_OTHER | 0x00600000 )
#define ERRFILE_editstring ( ERRFILE_OTHER | 0x00610000 )
/** @} */ /** @} */