diff options
Diffstat (limited to 'src/hci/tui')
| -rw-r--r-- | src/hci/tui/form_ui.c | 545 | ||||
| -rw-r--r-- | src/hci/tui/login_ui.c | 152 | ||||
| -rw-r--r-- | src/hci/tui/menu_ui.c | 126 | ||||
| -rw-r--r-- | src/hci/tui/message.c | 110 | ||||
| -rw-r--r-- | src/hci/tui/settings_ui.c | 150 |
5 files changed, 795 insertions, 288 deletions
diff --git a/src/hci/tui/form_ui.c b/src/hci/tui/form_ui.c new file mode 100644 index 000000000..7d8026f16 --- /dev/null +++ b/src/hci/tui/form_ui.c @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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_SECBOOT ( PERMITTED ); + +/** @file + * + * Text widget forms + * + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ipxe/ansicol.h> +#include <ipxe/dynui.h> +#include <ipxe/jumpscroll.h> +#include <ipxe/settings.h> +#include <ipxe/editbox.h> +#include <ipxe/message.h> + +/** Form title row */ +#define TITLE_ROW 1U + +/** Starting control row */ +#define START_ROW 3U + +/** Ending control row */ +#define END_ROW ( LINES - 3U ) + +/** Instructions row */ +#define INSTRUCTION_ROW ( LINES - 2U ) + +/** Padding between instructions */ +#define INSTRUCTION_PAD " " + +/** Input field width */ +#define INPUT_WIDTH ( COLS / 2U ) + +/** Input field column */ +#define INPUT_COL ( ( COLS - INPUT_WIDTH ) / 2U ) + +/** A form */ +struct form { + /** Dynamic user interface */ + struct dynamic_ui *dynui; + /** Jump scroller */ + struct jump_scroller scroll; + /** Array of form controls */ + struct form_control *controls; +}; + +/** A form control */ +struct form_control { + /** Dynamic user interface item */ + struct dynamic_item *item; + /** Settings block */ + struct settings *settings; + /** Setting */ + struct setting setting; + /** Label row */ + unsigned int row; + /** Editable text box */ + struct edit_box editbox; + /** Modifiable setting name */ + char *name; + /** Modifiable setting value */ + char *value; + /** Most recent error in saving */ + int rc; +}; + +/** + * Allocate form + * + * @v dynui Dynamic user interface + * @ret form Form, or NULL on error + */ +static struct form * alloc_form ( struct dynamic_ui *dynui ) { + struct form *form; + struct form_control *control; + struct dynamic_item *item; + char *name; + size_t len; + + /* Calculate total length */ + len = sizeof ( *form ); + list_for_each_entry ( item, &dynui->items, list ) { + len += sizeof ( *control ); + if ( item->name ) + len += ( strlen ( item->name ) + 1 /* NUL */ ); + } + + /* Allocate and initialise structure */ + form = zalloc ( len ); + if ( ! form ) + return NULL; + control = ( ( ( void * ) form ) + sizeof ( *form ) ); + name = ( ( ( void * ) control ) + + ( dynui->count * sizeof ( *control ) ) ); + form->dynui = dynui; + form->controls = control; + list_for_each_entry ( item, &dynui->items, list ) { + control->item = item; + if ( item->name ) { + control->name = name; + name = ( stpcpy ( name, item->name ) + 1 /* NUL */ ); + } + control++; + } + assert ( ( ( void * ) name ) == ( ( ( void * ) form ) + len ) ); + + return form; +} + +/** + * Free form + * + * @v form Form + */ +static void free_form ( struct form *form ) { + unsigned int i; + + /* Free input value buffers */ + for ( i = 0 ; i < form->dynui->count ; i++ ) + free ( form->controls[i].value ); + + /* Free form */ + free ( form ); +} + +/** + * Assign form rows + * + * @v form Form + * @ret rc Return status code + */ +static int layout_form ( struct form *form ) { + struct form_control *control; + struct dynamic_item *item; + unsigned int labels = 0; + unsigned int inputs = 0; + unsigned int pad_control = 0; + unsigned int pad_label = 0; + unsigned int minimum; + unsigned int remaining; + unsigned int between; + unsigned int row; + unsigned int flags; + unsigned int i; + + /* Count labels and inputs */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + item = control->item; + if ( item->text[0] ) + labels++; + if ( item->name ) { + if ( ! inputs ) + form->scroll.current = i; + inputs++; + if ( item->flags & DYNUI_DEFAULT ) + form->scroll.current = i; + form->scroll.count = ( i + 1 ); + } + } + form->scroll.rows = form->scroll.count; + DBGC ( form, "FORM %p has %d controls (%d labels, %d inputs)\n", + form, form->dynui->count, labels, inputs ); + + /* Refuse to create forms with no inputs */ + if ( ! inputs ) + return -EINVAL; + + /* Calculate minimum number of rows */ + minimum = ( labels + ( inputs * 2 /* edit box and error message */ ) ); + remaining = ( END_ROW - START_ROW ); + DBGC ( form, "FORM %p has %d (of %d) usable rows\n", + form, remaining, LINES ); + if ( minimum > remaining ) + return -ERANGE; + remaining -= minimum; + + /* Insert blank row between controls, if space exists */ + between = ( form->dynui->count - 1 ); + if ( between <= remaining ) { + pad_control = 1; + remaining -= between; + DBGC ( form, "FORM %p padding between controls\n", form ); + } + + /* Insert blank row after label, if space exists */ + if ( labels <= remaining ) { + pad_label = 1; + remaining -= labels; + DBGC ( form, "FORM %p padding after labels\n", form ); + } + + /* Centre on screen */ + DBGC ( form, "FORM %p has %d spare rows\n", form, remaining ); + row = ( START_ROW + ( remaining / 2 ) ); + + /* Position each control */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + item = control->item; + if ( item->text[0] ) { + control->row = row; + row++; /* Label text */ + row += pad_label; + } + if ( item->name ) { + flags = ( ( item->flags & DYNUI_SECRET ) ? + WIDGET_SECRET : 0 ); + init_editbox ( &control->editbox, row, INPUT_COL, + INPUT_WIDTH, flags, &control->value ); + row++; /* Edit box */ + row++; /* Error message (if any) */ + } + row += pad_control; + } + assert ( row <= END_ROW ); + + return 0; +} + +/** + * Draw form + * + * @v form Form + */ +static void draw_form ( struct form *form ) { + struct form_control *control; + unsigned int i; + + /* Clear screen */ + color_set ( CPAIR_NORMAL, NULL ); + erase(); + + /* Draw title, if any */ + attron ( A_BOLD ); + if ( form->dynui->title ) + msg ( TITLE_ROW, "%s", form->dynui->title ); + attroff ( A_BOLD ); + + /* Draw controls */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + + /* Draw label, if any */ + if ( control->row ) + msg ( control->row, "%s", control->item->text ); + + /* Draw input, if any */ + if ( control->name ) + draw_widget ( &control->editbox.widget ); + } + + /* Draw instructions */ + msg ( INSTRUCTION_ROW, "%s", "Ctrl-X - save changes" + INSTRUCTION_PAD "Ctrl-C - discard changes" ); +} + +/** + * Draw (or clear) error messages + * + * @v form Form + */ +static void draw_errors ( struct form *form ) { + struct form_control *control; + unsigned int row; + unsigned int i; + + /* Draw (or clear) errors */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + + /* Skip non-input controls */ + if ( ! control->name ) + continue; + + /* Draw or clear error message as appropriate */ + row = ( control->editbox.widget.row + 1 ); + if ( control->rc != 0 ) { + color_set ( CPAIR_ALERT, NULL ); + msg ( row, " %s ", strerror ( control->rc ) ); + color_set ( CPAIR_NORMAL, NULL ); + } else { + clearmsg ( row ); + } + } +} + +/** + * Parse setting names + * + * @v form Form + * @ret rc Return status code + */ +static int parse_names ( struct form *form ) { + struct form_control *control; + unsigned int i; + int rc; + + /* Parse all setting names */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + + /* Skip labels */ + if ( ! control->name ) { + DBGC ( form, "FORM %p item %d is a label\n", form, i ); + continue; + } + + /* Parse setting name */ + DBGC ( form, "FORM %p item %d is for %s\n", + form, i, control->name ); + if ( ( rc = parse_setting_name ( control->name, + autovivify_child_settings, + &control->settings, + &control->setting ) ) != 0 ) + return rc; + + /* Apply default type if necessary */ + if ( ! control->setting.type ) + control->setting.type = &setting_type_string; + } + + return 0; +} + +/** + * Load current input values + * + * @v form Form + */ +static void load_values ( struct form *form ) { + struct form_control *control; + unsigned int i; + + /* Fetch all current setting values */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + if ( ! control->name ) + continue; + fetchf_setting_copy ( control->settings, &control->setting, + NULL, &control->setting, + &control->value ); + } +} + +/** + * Store current input values + * + * @v form Form + * @ret rc Return status code + */ +static int save_values ( struct form *form ) { + struct form_control *control; + unsigned int i; + int rc = 0; + + /* Store all current setting values */ + for ( i = 0 ; i < form->dynui->count ; i++ ) { + control = &form->controls[i]; + if ( ! control->name ) + continue; + control->rc = storef_setting ( control->settings, + &control->setting, + control->value ); + if ( control->rc != 0 ) + rc = control->rc; + } + + return rc; +} + +/** + * Submit form + * + * @v form Form + * @ret rc Return status code + */ +static int submit_form ( struct form *form ) { + int rc; + + /* Attempt to save values */ + rc = save_values ( form ); + + /* Draw (or clear) errors */ + draw_errors ( form ); + + return rc; +} + +/** + * Form main loop + * + * @v form Form + * @ret rc Return status code + */ +static int form_loop ( struct form *form ) { + struct jump_scroller *scroll = &form->scroll; + struct form_control *control; + struct dynamic_item *item; + unsigned int move; + unsigned int i; + int key; + int rc; + + /* Main loop */ + while ( 1 ) { + + /* Draw current input */ + control = &form->controls[scroll->current]; + draw_widget ( &control->editbox.widget ); + + /* Process keypress */ + key = edit_widget ( &control->editbox.widget, getkey ( 0 ) ); + + /* Handle scroll keys */ + move = jump_scroll_key ( &form->scroll, key ); + + /* Handle special keys */ + switch ( key ) { + case CTRL_C: + case ESC: + /* Cancel form */ + return -ECANCELED; + case KEY_ENTER: + /* Attempt to do the most intuitive thing when + * Enter is pressed. If we are on the last + * input, then submit the form. If we are + * editing an input which failed, then + * resubmit the form. Otherwise, move to the + * next input. + */ + if ( ( control->rc == 0 ) && + ( scroll->current < ( scroll->count - 1 ) ) ) { + move = SCROLL_DOWN; + break; + } + /* fall through */ + case CTRL_X: + /* Submit form */ + if ( ( rc = submit_form ( form ) ) == 0 ) + return 0; + /* If current input is not the problem, move + * to the first input that needs fixing. + */ + if ( control->rc == 0 ) { + for ( i = 0 ; i < form->dynui->count ; i++ ) { + if ( form->controls[i].rc != 0 ) { + scroll->current = i; + break; + } + } + } + break; + default: + /* Move to input with matching shortcut key, if any */ + item = dynui_shortcut ( form->dynui, key ); + if ( item && ( item->flags & DYNUI_HIDDEN ) == 0 ) { + scroll->current = item->index; + if ( ! item->name ) + move = SCROLL_DOWN; + } + break; + } + + /* Move selection, if applicable */ + while ( move ) { + move = jump_scroll_move ( &form->scroll, move ); + control = &form->controls[scroll->current]; + if ( control->name ) + break; + } + } +} + +/** + * Show form + * + * @v dynui Dynamic user interface + * @ret rc Return status code + */ +int show_form ( struct dynamic_ui *dynui ) { + struct form *form; + int rc; + + /* Allocate and initialise structure */ + form = alloc_form ( dynui ); + if ( ! form ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Parse setting names and load current values */ + if ( ( rc = parse_names ( form ) ) != 0 ) + goto err_parse_names; + load_values ( form ); + + /* Lay out form on screen */ + if ( ( rc = layout_form ( form ) ) != 0 ) + goto err_layout; + + /* Draw initial form */ + initscr(); + start_color(); + draw_form ( form ); + + /* Run main loop */ + if ( ( rc = form_loop ( form ) ) != 0 ) + goto err_loop; + + err_loop: + color_set ( CPAIR_NORMAL, NULL ); + endwin(); + err_layout: + err_parse_names: + free_form ( form ); + err_alloc: + return rc; +} diff --git a/src/hci/tui/login_ui.c b/src/hci/tui/login_ui.c index 56fc2fa97..e265f81b0 100644 --- a/src/hci/tui/login_ui.c +++ b/src/hci/tui/login_ui.c @@ -22,6 +22,7 @@ */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +FILE_SECBOOT ( PERMITTED ); /** @file * @@ -29,117 +30,56 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -#include <string.h> -#include <errno.h> -#include <curses.h> -#include <ipxe/console.h> -#include <ipxe/settings.h> -#include <ipxe/editbox.h> -#include <ipxe/keys.h> -#include <ipxe/ansicol.h> +#include <ipxe/dynui.h> #include <ipxe/login_ui.h> -/* Screen layout */ -#define USERNAME_LABEL_ROW ( ( LINES / 2U ) - 4U ) -#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 +static struct dynamic_item username; +static struct dynamic_item password; -int login_ui ( int nouser ) { - char username[64]; - char password[64]; - struct edit_box username_box; - struct edit_box password_box; - struct edit_box *current_box = nouser ? &password_box : &username_box; - int key; - 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 ) ); - - /* 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 ); - - /* Draw initial UI */ - color_set ( CPAIR_NORMAL, NULL ); - erase(); - attron ( A_BOLD ); - if ( ! nouser ) { - mvprintw ( USERNAME_LABEL_ROW, LABEL_COL, "Username:" ); - } - mvprintw ( PASSWORD_LABEL_ROW, LABEL_COL, "Password:" ); - attroff ( A_BOLD ); - color_set ( CPAIR_EDIT, NULL ); - if ( ! nouser ) { - draw_editbox ( &username_box ); - } - draw_editbox ( &password_box ); +static struct dynamic_ui login = { + .items = { + .prev = &password.list, + .next = &username.list, + }, + .hidden_items = { + .prev = &login.hidden_items, + .next = &login.hidden_items, + }, + .count = 2, +}; - /* Main loop */ - while ( rc == -EINPROGRESS ) { +static struct dynamic_item username = { + .list = { + .prev = &login.items, + .next = &password.list, + }, + .name = "username", + .text = "Username", + .index = 0, +}; - draw_editbox ( current_box ); +static struct dynamic_item password = { + .list = { + .prev = &username.list, + .next = &login.items, + }, + .name = "password", + .text = "Password", + .index = 1, + .flags = DYNUI_SECRET, +}; - key = getkey ( 0 ); - switch ( key ) { - case KEY_DOWN: - current_box = &password_box; - break; - case KEY_UP: - if ( ! nouser ) { - current_box = &username_box; - } - break; - case TAB: - if ( ! nouser ) { - 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; - } +int login_ui ( int nouser ) { + if ( nouser ) { + password.index = 0; + password.list.prev = &login.items; + login.count = 1; + login.items.next = &password.list; + } else { + password.index = 1; + password.list.prev = &username.list; + login.count = 2; + login.items.next = &username.list; } - - /* Terminate UI */ - color_set ( CPAIR_NORMAL, NULL ); - erase(); - endwin(); - - if ( rc != 0 ) - return rc; - - /* 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; - - return 0; + return show_form ( &login ); } diff --git a/src/hci/tui/menu_ui.c b/src/hci/tui/menu_ui.c index cb4edbbc8..e8672ed08 100644 --- a/src/hci/tui/menu_ui.c +++ b/src/hci/tui/menu_ui.c @@ -22,6 +22,7 @@ */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +FILE_SECBOOT ( PERMITTED ); /** @file * @@ -37,7 +38,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/console.h> #include <ipxe/ansicol.h> #include <ipxe/jumpscroll.h> -#include <ipxe/menu.h> +#include <ipxe/dynui.h> /* Screen layout */ #define TITLE_ROW 1U @@ -49,40 +50,24 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** A menu user interface */ struct menu_ui { - /** Menu */ - struct menu *menu; + /** Dynamic user interface */ + struct dynamic_ui *dynui; /** Jump scroller */ struct jump_scroller scroll; - /** Timeout (0=indefinite) */ + /** Remaining timeout (0=indefinite) */ unsigned long timeout; + /** Post-activity timeout (0=indefinite) */ + unsigned long retimeout; }; /** - * Return a numbered menu item - * - * @v menu Menu - * @v index Index - * @ret item Menu item, or NULL - */ -static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) { - struct menu_item *item; - - list_for_each_entry ( item, &menu->items, list ) { - if ( index-- == 0 ) - return item; - } - - return NULL; -} - -/** * Draw a numbered menu item * * @v ui Menu user interface * @v index Index */ static void draw_menu_item ( struct menu_ui *ui, unsigned int index ) { - struct menu_item *item; + struct dynamic_item *item; unsigned int row_offset; char buf[ MENU_COLS + 1 /* NUL */ ]; char timeout_buf[6]; /* "(xxx)" + NUL */ @@ -95,11 +80,11 @@ static void draw_menu_item ( struct menu_ui *ui, unsigned int index ) { move ( ( MENU_ROW + row_offset ), MENU_COL ); /* Get menu item */ - item = menu_item ( ui->menu, index ); + item = dynui_item ( ui->dynui, index ); if ( item ) { /* Draw separators in a different colour */ - if ( ! item->label ) + if ( ! item->name ) color_set ( CPAIR_SEPARATOR, NULL ); /* Highlight if this is the selected item */ @@ -171,13 +156,12 @@ static void draw_menu_items ( struct menu_ui *ui ) { * @ret selected Selected item * @ret rc Return status code */ -static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { - struct menu_item *item; +static int menu_loop ( struct menu_ui *ui, struct dynamic_item **selected ) { + struct dynamic_item *item; unsigned long timeout; unsigned int previous; + unsigned int move; int key; - int i; - int move; int chosen = 0; int rc = 0; @@ -192,15 +176,15 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { ui->timeout -= timeout; /* Get key */ - move = 0; + move = SCROLL_NONE; key = getkey ( timeout ); if ( key < 0 ) { /* Choose default if we finally time out */ if ( ui->timeout == 0 ) chosen = 1; } else { - /* Cancel any timeout */ - ui->timeout = 0; + /* Reset timeout after activity */ + ui->timeout = ui->retimeout; /* Handle scroll keys */ move = jump_scroll_key ( &ui->scroll, key ); @@ -216,31 +200,17 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { chosen = 1; break; default: - i = 0; - - /* Check for shortcut. Visible items */ - list_for_each_entry ( item, &ui->menu->items, - list ) { - if ( ! ( item->shortcut && - ( item->shortcut == key ) ) ) { - i++; - continue; + item = dynui_shortcut ( ui->dynui, key ); + if ( item ) { + ui->scroll.current = item->index; + if ( item->flags & DYNUI_HIDDEN ) { + *selected = item; + goto hidden_entry_selected; } - ui->scroll.current = i; - if ( item->label ) { + if ( item->name ) { chosen = 1; } else { - move = +1; - } - } - - /* Hidden items */ - list_for_each_entry ( item, &ui->menu->hidden_items, - list ) { - if ( item->shortcut && - ( item->shortcut == key ) ) { - *selected = item; - goto hidden_entry_selected; + move = SCROLL_DOWN; } } break; @@ -250,8 +220,8 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { /* Move selection, if applicable */ while ( move ) { move = jump_scroll_move ( &ui->scroll, move ); - item = menu_item ( ui->menu, ui->scroll.current ); - if ( item->label ) + item = dynui_item ( ui->dynui, ui->scroll.current ); + if ( item->name ) break; } @@ -264,9 +234,9 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { } /* Record selection */ - item = menu_item ( ui->menu, ui->scroll.current ); + item = dynui_item ( ui->dynui, ui->scroll.current ); assert ( item != NULL ); - assert ( item->label != NULL ); + assert ( item->name != NULL ); *selected = item; } while ( ( rc == 0 ) && ! chosen ); @@ -278,43 +248,47 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { /** * Show menu * - * @v menu Menu - * @v timeout Timeout period, in ticks (0=indefinite) + * @v dynui Dynamic user interface + * @v timeout Initial timeout period, in ticks (0=indefinite) + * @v retimeout Post-activity timeout period, in ticks (0=indefinite) * @ret selected Selected item * @ret rc Return status code */ -int show_menu ( struct menu *menu, unsigned long timeout, - const char *select, struct menu_item **selected ) { - struct menu_item *item; +int show_menu ( struct dynamic_ui *dynui, unsigned long timeout, + unsigned long retimeout, const char *select, + struct dynamic_item **selected ) { + struct dynamic_item *item; struct menu_ui ui; char buf[ MENU_COLS + 1 /* NUL */ ]; - int labelled_count = 0; + int named_count = 0; int rc; /* Initialise UI */ memset ( &ui, 0, sizeof ( ui ) ); - ui.menu = menu; + ui.dynui = dynui; ui.scroll.rows = MENU_ROWS; ui.timeout = timeout; - list_for_each_entry ( item, &menu->items, list ) { - if ( item->label ) { - if ( ! labelled_count ) + ui.retimeout = retimeout; + + list_for_each_entry ( item, &dynui->items, list ) { + if ( item->name ) { + if ( ! named_count ) ui.scroll.current = ui.scroll.count; - labelled_count++; + named_count++; if ( select ) { - if ( strcmp ( select, item->label ) == 0 ) + if ( strcmp ( select, item->name ) == 0 ) ui.scroll.current = ui.scroll.count; } else { - if ( item->is_default ) + if ( item->flags & DYNUI_DEFAULT ) ui.scroll.current = ui.scroll.count; } } ui.scroll.count++; } - if ( ! labelled_count ) { - /* Menus with no labelled items cannot be selected - * from, and will seriously confuse the navigation - * logic. Refuse to display any such menus. + if ( ! named_count ) { + /* Menus with no named items cannot be selected from, + * and will seriously confuse the navigation logic. + * Refuse to display any such menus. */ return -ENOENT; } @@ -328,7 +302,7 @@ int show_menu ( struct menu *menu, unsigned long timeout, /* Draw initial content */ attron ( A_BOLD ); - snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title ); + snprintf ( buf, sizeof ( buf ), "%s", ui.dynui->title ); mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf ); attroff ( A_BOLD ); jump_scroll ( &ui.scroll ); diff --git a/src/hci/tui/message.c b/src/hci/tui/message.c new file mode 100644 index 000000000..89c6f7703 --- /dev/null +++ b/src/hci/tui/message.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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_SECBOOT ( PERMITTED ); + +/** @file + * + * Message printing + * + */ + +#include <stddef.h> +#include <stdint.h> +#include <stdarg.h> +#include <unistd.h> +#include <ipxe/ansicol.h> +#include <ipxe/message.h> + +/** + * 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 be421cc0a..a069c527d 100644 --- a/src/hci/tui/settings_ui.c +++ b/src/hci/tui/settings_ui.c @@ -22,9 +22,11 @@ */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +FILE_SECBOOT ( PERMITTED ); #include <stdio.h> #include <stdarg.h> +#include <stdlib.h> #include <unistd.h> #include <string.h> #include <curses.h> @@ -34,6 +36,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/keys.h> #include <ipxe/ansicol.h> #include <ipxe/jumpscroll.h> +#include <ipxe/message.h> #include <ipxe/settings_ui.h> #include <config/branding.h> @@ -58,12 +61,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 +98,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 +127,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,18 +159,18 @@ 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, + 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; } @@ -197,7 +201,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 +211,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 +227,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 = "<not specified>"; - 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 */ @@ -247,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->row.editbox.widget, key ); } /** @@ -257,76 +263,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 ); -} - -/** - * 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 ); + return storef_setting ( ui->settings, &ui->row.setting, ui->row.buf ); } /** @@ -443,8 +380,8 @@ static void select_settings ( struct settings_ui *ui, static int main_loop ( struct settings *settings ) { struct settings_ui ui; unsigned int previous; + unsigned int move; int redraw = 1; - int move; int key; int rc; @@ -474,17 +411,17 @@ 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.row.editbox.widget ); /* Process keypress */ key = edit_setting ( &ui, getkey ( 0 ) ); 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 ); @@ -521,12 +458,13 @@ 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; break; case CTRL_X: + select_setting_row ( &ui, -1U ); return 0; case CR: case LF: |
