From fc9d64ffcd94c1e22675830eae13956cc4fd822d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 23 Mar 2013 14:50:40 +0000 Subject: menu: Add support for user defined item choice function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selecting menu items is currently done in menu_interactive_choice() by reading the user input strings from standard input. Extend menu_interactive_choice() to support user defined function for selecting menu items. This function and its argument can be specified when creating the menu. Signed-off-by: Pali Rohár Signed-off-by: Anatolij Gustschin --- common/cmd_pxe.c | 3 ++- common/menu.c | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 12 deletions(-) (limited to 'common') diff --git a/common/cmd_pxe.c b/common/cmd_pxe.c index ee75db9685..2dbd49cbd6 100644 --- a/common/cmd_pxe.c +++ b/common/cmd_pxe.c @@ -1280,7 +1280,8 @@ static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg) /* * Create a menu and add items for all the labels. */ - m = menu_create(cfg->title, cfg->timeout, cfg->prompt, label_print); + m = menu_create(cfg->title, cfg->timeout, cfg->prompt, label_print, + NULL, NULL); if (!m) return NULL; diff --git a/common/menu.c b/common/menu.c index 6b2a2db3e0..322b75e62f 100644 --- a/common/menu.c +++ b/common/menu.c @@ -47,6 +47,8 @@ struct menu { char *title; int prompt; void (*item_data_print)(void *); + char *(*item_choice)(void *); + void *item_choice_data; struct list_head items; }; @@ -204,18 +206,26 @@ static inline int menu_interactive_choice(struct menu *m, void **choice) menu_display(m); - readret = readline_into_buffer("Enter choice: ", cbuf, - m->timeout / 10); + if (!m->item_choice) { + readret = readline_into_buffer("Enter choice: ", cbuf, + m->timeout / 10); - if (readret >= 0) { - choice_item = menu_item_by_key(m, cbuf); - - if (!choice_item) { - printf("%s not found\n", cbuf); - m->timeout = 0; + if (readret >= 0) { + choice_item = menu_item_by_key(m, cbuf); + if (!choice_item) + printf("%s not found\n", cbuf); + } else { + return menu_default_choice(m, choice); } - } else - return menu_default_choice(m, choice); + } else { + char *key = m->item_choice(m->item_choice_data); + + if (key) + choice_item = menu_item_by_key(m, key); + } + + if (!choice_item) + m->timeout = 0; } *choice = choice_item->data; @@ -348,11 +358,19 @@ int menu_item_add(struct menu *m, char *item_key, void *item_data) * what must be entered to select an item, the item_data_print function should * make it obvious what the key for each entry is. * + * item_choice - If not NULL, will be called when asking the user to choose an + * item. Returns a key string corresponding to the choosen item or NULL if + * no item has been selected. + * + * item_choice_data - Will be passed as the argument to the item_choice function + * * Returns a pointer to the menu if successful, or NULL if there is * insufficient memory available to create the menu. */ struct menu *menu_create(char *title, int timeout, int prompt, - void (*item_data_print)(void *)) + void (*item_data_print)(void *), + char *(*item_choice)(void *), + void *item_choice_data) { struct menu *m; @@ -365,6 +383,8 @@ struct menu *menu_create(char *title, int timeout, int prompt, m->prompt = prompt; m->timeout = timeout; m->item_data_print = item_data_print; + m->item_choice = item_choice; + m->item_choice_data = item_choice_data; if (title) { m->title = strdup(title); -- cgit v1.2.3 From 6a3439fdadb7b98e72883b90eb717a04cd748e44 Mon Sep 17 00:00:00 2001 From: Anatolij Gustschin Date: Sat, 23 Mar 2013 14:52:04 +0000 Subject: menu: export menu_default_choice() function Checking the default menu item and obtaining its data can be useful in custom menu code. Export menu_default_choice() function which serves this purpose. Signed-off-by: Anatolij Gustschin --- common/menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'common') diff --git a/common/menu.c b/common/menu.c index 322b75e62f..64b461abb4 100644 --- a/common/menu.c +++ b/common/menu.c @@ -176,7 +176,7 @@ static inline struct menu_item *menu_item_by_key(struct menu *m, * Set *choice to point to the default item's data, if any default item was * set, and returns 1. If no default item was set, returns -ENOENT. */ -static inline int menu_default_choice(struct menu *m, void **choice) +int menu_default_choice(struct menu *m, void **choice) { if (m->default_item) { *choice = m->default_item->data; -- cgit v1.2.3 From e7abe919673994e8040aba0273ceb9a5b2609806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 23 Mar 2013 14:53:08 +0000 Subject: New command bootmenu: ANSI terminal boot menu support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "bootmenu" command uses U-Boot menu interfaces and provides a simple mechanism for creating menus with several boot items. When running this command the menu will be assembled as defined by a set of environment variables which contain a title and command key-value pairs. The "Up" and "Down" keys are used for navigation through the items. Current active menu item is highlighted and can be selected using the "Enter" key. The command interprets and generates various ANSI escape sequencies, so for proper menu rendering and item selection the used terminal should support them. Signed-off-by: Pali Rohár [agust: various fixes and documentation updates] Signed-off-by: Anatolij Gustschin --- common/Makefile | 1 + common/cmd_bootmenu.c | 517 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 518 insertions(+) create mode 100644 common/cmd_bootmenu.c (limited to 'common') diff --git a/common/Makefile b/common/Makefile index 1abf4ea794..f6313110c9 100644 --- a/common/Makefile +++ b/common/Makefile @@ -75,6 +75,7 @@ COBJS-$(CONFIG_CMD_SOURCE) += cmd_source.o COBJS-$(CONFIG_CMD_BDI) += cmd_bdinfo.o COBJS-$(CONFIG_CMD_BEDBUG) += bedbug.o cmd_bedbug.o COBJS-$(CONFIG_CMD_BMP) += cmd_bmp.o +COBJS-$(CONFIG_CMD_BOOTMENU) += cmd_bootmenu.o COBJS-$(CONFIG_CMD_BOOTLDR) += cmd_bootldr.o COBJS-$(CONFIG_CMD_BOOTSTAGE) += cmd_bootstage.o COBJS-$(CONFIG_CMD_CACHE) += cmd_cache.o diff --git a/common/cmd_bootmenu.c b/common/cmd_bootmenu.c new file mode 100644 index 0000000000..a3cbffab24 --- /dev/null +++ b/common/cmd_bootmenu.c @@ -0,0 +1,517 @@ +/* + * (C) Copyright 2011-2013 Pali Rohár + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* maximum bootmenu entries */ +#define MAX_COUNT 99 + +/* maximal size of bootmenu env + * 9 = strlen("bootmenu_") + * 2 = strlen(MAX_COUNT) + * 1 = NULL term + */ +#define MAX_ENV_SIZE (9 + 2 + 1) + +struct bootmenu_entry { + unsigned short int num; /* unique number 0 .. MAX_COUNT */ + char key[3]; /* key identifier of number */ + char *title; /* title of entry */ + char *command; /* hush command of entry */ + struct bootmenu_data *menu; /* this bootmenu */ + struct bootmenu_entry *next; /* next menu entry (num+1) */ +}; + +struct bootmenu_data { + int delay; /* delay for autoboot */ + int active; /* active menu entry */ + int count; /* total count of menu entries */ + struct bootmenu_entry *first; /* first menu entry */ +}; + +enum bootmenu_key { + KEY_NONE = 0, + KEY_UP, + KEY_DOWN, + KEY_SELECT, +}; + +static char *bootmenu_getoption(unsigned short int n) +{ + char name[MAX_ENV_SIZE] = "bootmenu_"; + + if (n > MAX_COUNT) + return NULL; + + sprintf(name + 9, "%d", n); + return getenv(name); +} + +static void bootmenu_print_entry(void *data) +{ + struct bootmenu_entry *entry = data; + int reverse = (entry->menu->active == entry->num); + + /* + * Move cursor to line where the entry will be drown (entry->num) + * First 3 lines contain bootmenu header + 1 empty line + */ + printf(ANSI_CURSOR_POSITION, entry->num + 4, 1); + + puts(" "); + + if (reverse) + puts(ANSI_COLOR_REVERSE); + + puts(entry->title); + + if (reverse) + puts(ANSI_COLOR_RESET); +} + +static void bootmenu_autoboot_loop(struct bootmenu_data *menu, + enum bootmenu_key *key, int *esc) +{ + int i, c; + + if (menu->delay > 0) { + printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); + printf(" Hit any key to stop autoboot: %2d ", menu->delay); + } + + while (menu->delay > 0) { + for (i = 0; i < 100; ++i) { + if (!tstc()) { + WATCHDOG_RESET(); + mdelay(10); + continue; + } + + menu->delay = -1; + c = getc(); + + switch (c) { + case '\e': + *esc = 1; + *key = KEY_NONE; + break; + case '\r': + *key = KEY_SELECT; + break; + default: + *key = KEY_NONE; + break; + } + + break; + } + + if (menu->delay < 0) + break; + + --menu->delay; + printf("\b\b\b%2d ", menu->delay); + } + + printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); + puts(ANSI_CLEAR_LINE); + + if (menu->delay == 0) + *key = KEY_SELECT; +} + +static void bootmenu_loop(struct bootmenu_data *menu, + enum bootmenu_key *key, int *esc) +{ + int c; + + while (!tstc()) { + WATCHDOG_RESET(); + mdelay(10); + } + + c = getc(); + + switch (*esc) { + case 0: + /* First char of ANSI escape sequence '\e' */ + if (c == '\e') { + *esc = 1; + *key = KEY_NONE; + } + break; + case 1: + /* Second char of ANSI '[' */ + if (c == '[') { + *esc = 2; + *key = KEY_NONE; + } else { + *esc = 0; + } + break; + case 2: + case 3: + /* Third char of ANSI (number '1') - optional */ + if (*esc == 2 && c == '1') { + *esc = 3; + *key = KEY_NONE; + break; + } + + *esc = 0; + + /* ANSI 'A' - key up was pressed */ + if (c == 'A') + *key = KEY_UP; + /* ANSI 'B' - key down was pressed */ + else if (c == 'B') + *key = KEY_DOWN; + /* other key was pressed */ + else + *key = KEY_NONE; + + break; + } + + /* enter key was pressed */ + if (c == '\r') + *key = KEY_SELECT; +} + +static char *bootmenu_choice_entry(void *data) +{ + struct bootmenu_data *menu = data; + struct bootmenu_entry *iter; + enum bootmenu_key key = KEY_NONE; + int esc = 0; + int i; + + while (1) { + if (menu->delay >= 0) { + /* Autoboot was not stopped */ + bootmenu_autoboot_loop(menu, &key, &esc); + } else { + /* Some key was pressed, so autoboot was stopped */ + bootmenu_loop(menu, &key, &esc); + } + + switch (key) { + case KEY_UP: + if (menu->active > 0) + --menu->active; + /* no menu key selected, regenerate menu */ + return NULL; + case KEY_DOWN: + if (menu->active < menu->count - 1) + ++menu->active; + /* no menu key selected, regenerate menu */ + return NULL; + case KEY_SELECT: + iter = menu->first; + for (i = 0; i < menu->active; ++i) + iter = iter->next; + return iter->key; + default: + break; + } + } + + /* never happens */ + debug("bootmenu: this should not happen"); + return NULL; +} + +static void bootmenu_destroy(struct bootmenu_data *menu) +{ + struct bootmenu_entry *iter = menu->first; + struct bootmenu_entry *next; + + while (iter) { + next = iter->next; + free(iter->title); + free(iter->command); + free(iter); + iter = next; + } + free(menu); +} + +static struct bootmenu_data *bootmenu_create(int delay) +{ + unsigned short int i = 0; + const char *option; + struct bootmenu_data *menu; + struct bootmenu_entry *iter = NULL; + + int len; + char *sep; + struct bootmenu_entry *entry; + + menu = malloc(sizeof(struct bootmenu_data)); + if (!menu) + return NULL; + + menu->delay = delay; + menu->active = 0; + menu->first = NULL; + + while ((option = bootmenu_getoption(i))) { + sep = strchr(option, '='); + if (!sep) { + printf("Invalid bootmenu entry: %s\n", option); + break; + } + + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + goto cleanup; + + len = sep-option; + entry->title = malloc(len + 1); + if (!entry->title) { + free(entry); + goto cleanup; + } + memcpy(entry->title, option, len); + entry->title[len] = 0; + + len = strlen(sep + 1); + entry->command = malloc(len + 1); + if (!entry->command) { + free(entry->title); + free(entry); + goto cleanup; + } + memcpy(entry->command, sep + 1, len); + entry->command[len] = 0; + + sprintf(entry->key, "%d", i); + + entry->num = i; + entry->menu = menu; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + ++i; + + if (i == MAX_COUNT - 1) + break; + } + + /* Add U-Boot console entry at the end */ + if (i <= MAX_COUNT - 1) { + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + goto cleanup; + + entry->title = strdup("U-Boot console"); + if (!entry->title) { + free(entry); + goto cleanup; + } + + entry->command = strdup(""); + if (!entry->command) { + free(entry->title); + free(entry); + goto cleanup; + } + + sprintf(entry->key, "%d", i); + + entry->num = i; + entry->menu = menu; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + ++i; + } + + menu->count = i; + return menu; + +cleanup: + bootmenu_destroy(menu); + return NULL; +} + +static void bootmenu_show(int delay) +{ + int init = 0; + void *choice = NULL; + char *title = NULL; + char *command = NULL; + struct menu *menu; + struct bootmenu_data *bootmenu; + struct bootmenu_entry *iter; + char *option, *sep; + + /* If delay is 0 do not create menu, just run first entry */ + if (delay == 0) { + option = bootmenu_getoption(0); + if (!option) { + puts("bootmenu option 0 was not found\n"); + return; + } + sep = strchr(option, '='); + if (!sep) { + puts("bootmenu option 0 is invalid\n"); + return; + } + run_command(sep+1, 0); + return; + } + + bootmenu = bootmenu_create(delay); + if (!bootmenu) + return; + + menu = menu_create(NULL, bootmenu->delay, 1, bootmenu_print_entry, + bootmenu_choice_entry, bootmenu); + if (!menu) { + bootmenu_destroy(bootmenu); + return; + } + + for (iter = bootmenu->first; iter; iter = iter->next) { + if (!menu_item_add(menu, iter->key, iter)) + goto cleanup; + } + + /* Default menu entry is always first */ + menu_default_set(menu, "0"); + + puts(ANSI_CURSOR_HIDE); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + + init = 1; + + if (menu_get_choice(menu, &choice)) { + iter = choice; + title = strdup(iter->title); + command = strdup(iter->command); + } + +cleanup: + menu_destroy(menu); + bootmenu_destroy(bootmenu); + + if (init) { + puts(ANSI_CURSOR_SHOW); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + } + + if (title && command) { + debug("Starting entry '%s'\n", title); + free(title); + run_command(command, 0); + free(command); + } + +#ifdef CONFIG_POSTBOOTMENU + run_command(CONFIG_POSTBOOTMENU, 0); +#endif +} + +void menu_display_statusline(struct menu *m) +{ + struct bootmenu_entry *entry; + struct bootmenu_data *menu; + + if (menu_default_choice(m, (void *)&entry) < 0) + return; + + menu = entry->menu; + + printf(ANSI_CURSOR_POSITION, 1, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, 2, 1); + puts(" *** U-Boot Boot Menu ***"); + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, 3, 1); + puts(ANSI_CLEAR_LINE); + + /* First 3 lines are bootmenu header + 2 empty lines between entries */ + printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, menu->count + 6, 1); + puts(" Press UP/DOWN to move, ENTER to select"); + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, menu->count + 7, 1); + puts(ANSI_CLEAR_LINE); +} + +#ifdef CONFIG_MENU_SHOW +int menu_show(int bootdelay) +{ + bootmenu_show(bootdelay); + return -1; /* -1 - abort boot and run monitor code */ +} +#endif + +int do_bootmenu(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) +{ + char *delay_str = NULL; + int delay = 10; + +#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) + delay = CONFIG_BOOTDELAY; +#endif + + if (argc >= 2) + delay_str = argv[1]; + + if (!delay_str) + delay_str = getenv("bootmenu_delay"); + + if (delay_str) + delay = (int)simple_strtol(delay_str, NULL, 10); + + bootmenu_show(delay); + return 0; +} + +U_BOOT_CMD( + bootmenu, 2, 1, do_bootmenu, + "ANSI terminal bootmenu", + "[delay]\n" + " - show ANSI terminal bootmenu with autoboot delay" +); -- cgit v1.2.3