diff options
author | Michael Brown <mcb30@ipxe.org> | 2024-06-20 14:24:53 -0700 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2024-06-20 14:26:06 -0700 |
commit | 5719cde838b6e86a02831373dae81642653b872f (patch) | |
tree | e4159ca36ce356a80df7909dfabeaececc6fefdc /src/hci/commands/dynui_cmd.c | |
parent | 122777f789a68512593e4aa6da3ace3d0d8664ec (diff) | |
download | ipxe-5719cde838b6e86a02831373dae81642653b872f.tar.gz |
[dynui] Generalise the concept of a menu to a dynamic user interface
We currently have an abstract model of a dynamic menu as a list of
items, each of which has a name, a description, and assorted metadata
such as a shortcut key. The "menu" and "item" commands construct
representations in this abstract model, and the "choose" command then
presents the items as a single-choice menu, with the selected item's
name used as the output value.
This same abstraction may be used to model a dynamic form as a list of
editable items, each of which has a corresponding setting name, an
optional description label, and assorted metadata such as a shortcut
key. By defining a "form" command as an alias for the "menu" command,
we could construct and present forms using commands such as:
#!ipxe
form Login to ${url}
item username Username or email address
item --secret password Password
present
or
#!ipxe
form Configure IPv4 networking for ${netX/ifname}
item netX/ip IPv4 address
item netX/netmask Subnet mask
item netX/gateway Gateway address
item netX/dns DNS server address
present
Reusing the same abstract model for both menus and forms allows us to
minimise the increase in code size, since the implementation of the
"form" and "item" commands is essentially zero-cost.
Rename everything within the abstract data model from "menu" to
"dynamic user interface" to reflect this generalisation.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/hci/commands/dynui_cmd.c')
-rw-r--r-- | src/hci/commands/dynui_cmd.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/src/hci/commands/dynui_cmd.c b/src/hci/commands/dynui_cmd.c new file mode 100644 index 000000000..dbaa669ef --- /dev/null +++ b/src/hci/commands/dynui_cmd.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2012 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 + * + * Dynamic user interface commands + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <ipxe/dynui.h> +#include <ipxe/command.h> +#include <ipxe/parseopt.h> +#include <ipxe/settings.h> +#include <ipxe/features.h> + +FEATURE ( FEATURE_MISC, "Menu", DHCP_EB_FEATURE_MENU, 1 ); + +/** "dynui" options */ +struct dynui_options { + /** Name */ + char *name; + /** Delete */ + int delete; +}; + +/** "dynui" option list */ +static struct option_descriptor dynui_opts[] = { + OPTION_DESC ( "name", 'n', required_argument, + struct dynui_options, name, parse_string ), + OPTION_DESC ( "delete", 'd', no_argument, + struct dynui_options, delete, parse_flag ), +}; + +/** "dynui" command descriptor */ +static struct command_descriptor dynui_cmd = + COMMAND_DESC ( struct dynui_options, dynui_opts, 0, MAX_ARGUMENTS, + "[<title>]" ); + +/** + * The "dynui" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Return status code + */ +static int dynui_exec ( int argc, char **argv ) { + struct dynui_options opts; + struct dynamic_ui *dynui; + char *title; + int rc; + + /* Parse options */ + if ( ( rc = parse_options ( argc, argv, &dynui_cmd, &opts ) ) != 0 ) + goto err_parse_options; + + /* Parse title */ + title = concat_args ( &argv[optind] ); + if ( ! title ) { + rc = -ENOMEM; + goto err_parse_title; + } + + /* Create dynamic user interface */ + dynui = create_dynui ( opts.name, title ); + if ( ! dynui ) { + rc = -ENOMEM; + goto err_create_dynui; + } + + /* Destroy dynamic user interface, if applicable */ + if ( opts.delete ) + destroy_dynui ( dynui ); + + /* Success */ + rc = 0; + + err_create_dynui: + free ( title ); + err_parse_title: + err_parse_options: + return rc; +} + +/** "item" options */ +struct item_options { + /** Dynamic user interface name */ + char *dynui; + /** Shortcut key */ + unsigned int key; + /** Use as default */ + int is_default; + /** Use as a separator */ + int is_gap; +}; + +/** "item" option list */ +static struct option_descriptor item_opts[] = { + OPTION_DESC ( "menu", 'm', required_argument, + struct item_options, dynui, parse_string ), + OPTION_DESC ( "key", 'k', required_argument, + struct item_options, key, parse_key ), + OPTION_DESC ( "default", 'd', no_argument, + struct item_options, is_default, parse_flag ), + OPTION_DESC ( "gap", 'g', no_argument, + struct item_options, is_gap, parse_flag ), +}; + +/** "item" command descriptor */ +static struct command_descriptor item_cmd = + COMMAND_DESC ( struct item_options, item_opts, 0, MAX_ARGUMENTS, + "[<name> [<text>]]" ); + +/** + * The "item" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Return status code + */ +static int item_exec ( int argc, char **argv ) { + struct item_options opts; + struct dynamic_ui *dynui; + struct dynamic_item *item; + char *name = NULL; + char *text = NULL; + int rc; + + /* Parse options */ + if ( ( rc = parse_options ( argc, argv, &item_cmd, &opts ) ) != 0 ) + goto err_parse_options; + + /* Parse name, if present */ + if ( ! opts.is_gap ) + name = argv[optind++]; /* May be NULL */ + + /* Parse text, if present */ + if ( optind < argc ) { + text = concat_args ( &argv[optind] ); + if ( ! text ) { + rc = -ENOMEM; + goto err_parse_text; + } + } + + /* Identify dynamic user interface */ + if ( ( rc = parse_dynui ( opts.dynui, &dynui ) ) != 0 ) + goto err_parse_dynui; + + /* Add dynamic user interface item */ + item = add_dynui_item ( dynui, name, ( text ? text : "" ), + opts.key, opts.is_default ); + if ( ! item ) { + rc = -ENOMEM; + goto err_add_dynui_item; + } + + /* Success */ + rc = 0; + + err_add_dynui_item: + err_parse_dynui: + free ( text ); + err_parse_text: + err_parse_options: + return rc; +} + +/** "choose" options */ +struct choose_options { + /** Dynamic user interface name */ + char *dynui; + /** Timeout */ + unsigned long timeout; + /** Default selection */ + char *select; + /** Keep dynamic user interface */ + int keep; +}; + +/** "choose" option list */ +static struct option_descriptor choose_opts[] = { + OPTION_DESC ( "menu", 'm', required_argument, + struct choose_options, dynui, parse_string ), + OPTION_DESC ( "default", 'd', required_argument, + struct choose_options, select, parse_string ), + OPTION_DESC ( "timeout", 't', required_argument, + struct choose_options, timeout, parse_timeout ), + OPTION_DESC ( "keep", 'k', no_argument, + struct choose_options, keep, parse_flag ), +}; + +/** "choose" command descriptor */ +static struct command_descriptor choose_cmd = + COMMAND_DESC ( struct choose_options, choose_opts, 1, 1, "<setting>" ); + +/** + * The "choose" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Return status code + */ +static int choose_exec ( int argc, char **argv ) { + struct choose_options opts; + struct named_setting setting; + struct dynamic_ui *dynui; + struct dynamic_item *item; + int rc; + + /* Parse options */ + if ( ( rc = parse_options ( argc, argv, &choose_cmd, &opts ) ) != 0 ) + goto err_parse_options; + + /* Parse setting name */ + if ( ( rc = parse_autovivified_setting ( argv[optind], + &setting ) ) != 0 ) + goto err_parse_setting; + + /* Identify dynamic user interface */ + if ( ( rc = parse_dynui ( opts.dynui, &dynui ) ) != 0 ) + goto err_parse_dynui; + + /* Show as menu */ + if ( ( rc = show_menu ( dynui, opts.timeout, opts.select, + &item ) ) != 0 ) + goto err_show_menu; + + /* Apply default type if necessary */ + if ( ! setting.setting.type ) + setting.setting.type = &setting_type_string; + + /* Store setting */ + if ( ( rc = storef_setting ( setting.settings, &setting.setting, + item->name ) ) != 0 ) { + printf ( "Could not store \"%s\": %s\n", + setting.setting.name, strerror ( rc ) ); + goto err_store; + } + + /* Success */ + rc = 0; + + err_store: + err_show_menu: + /* Destroy dynamic user interface, if applicable */ + if ( ! opts.keep ) + destroy_dynui ( dynui ); + err_parse_dynui: + err_parse_setting: + err_parse_options: + return rc; +} + +/** Dynamic user interface commands */ +struct command dynui_commands[] __command = { + { + .name = "menu", + .exec = dynui_exec, + }, + { + .name = "item", + .exec = item_exec, + }, + { + .name = "choose", + .exec = choose_exec, + }, +}; |