9851c9cc87
If "\n\r" or "\r\n" is ignore one. Signed-off-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
536 lines
10 KiB
C
536 lines
10 KiB
C
/*
|
|
* (C) Copyright 2009-2010 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
|
|
*
|
|
* 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; version 2 of
|
|
* the License.
|
|
*
|
|
* 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 <common.h>
|
|
#include <command.h>
|
|
#include <environment.h>
|
|
#include <init.h>
|
|
#include <menu.h>
|
|
#include <malloc.h>
|
|
#include <xfuncs.h>
|
|
#include <errno.h>
|
|
#include <readkey.h>
|
|
#include <clock.h>
|
|
#include <linux/err.h>
|
|
#include <libbb.h>
|
|
|
|
static LIST_HEAD(menus);
|
|
|
|
struct menu* menu_get_menus(void)
|
|
{
|
|
if (list_empty(&menus))
|
|
return NULL;
|
|
|
|
return list_entry(&menus, struct menu, list);
|
|
}
|
|
EXPORT_SYMBOL(menu_get_menus);
|
|
|
|
void menu_free(struct menu *m)
|
|
{
|
|
struct menu_entry *me, *tmp;
|
|
|
|
if (!m)
|
|
return;
|
|
free(m->name);
|
|
free(m->display);
|
|
free(m->auto_display);
|
|
free(m->display_buffer);
|
|
|
|
list_for_each_entry_safe(me, tmp, &m->entries, list)
|
|
menu_entry_free(me);
|
|
|
|
free(m);
|
|
}
|
|
EXPORT_SYMBOL(menu_free);
|
|
|
|
int menu_add(struct menu *m)
|
|
{
|
|
if (!m || !m->name)
|
|
return -EINVAL;
|
|
|
|
if (menu_get_by_name(m->name))
|
|
return -EEXIST;
|
|
|
|
list_add_tail(&m->list, &menus);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(menu_add);
|
|
|
|
void menu_remove(struct menu *m)
|
|
{
|
|
if (!m)
|
|
return;
|
|
|
|
list_del(&m->list);
|
|
}
|
|
EXPORT_SYMBOL(menu_remove);
|
|
|
|
int menu_add_entry(struct menu *m, struct menu_entry *me)
|
|
{
|
|
int len;
|
|
|
|
if (!m || !me || !me->display)
|
|
return -EINVAL;
|
|
|
|
len = strlen(me->display);
|
|
|
|
m->width = max(len, m->width);
|
|
|
|
m->nb_entries++;
|
|
me->num = m->nb_entries;
|
|
list_add_tail(&me->list, &m->entries);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(menu_add_entry);
|
|
|
|
void menu_remove_entry(struct menu *m, struct menu_entry *me)
|
|
{
|
|
int i = 1;
|
|
|
|
if (!m || !me)
|
|
return;
|
|
|
|
m->nb_entries--;
|
|
list_del(&me->list);
|
|
|
|
list_for_each_entry(me, &m->entries, list)
|
|
me->num = i++;
|
|
}
|
|
EXPORT_SYMBOL(menu_remove_entry);
|
|
|
|
struct menu* menu_get_by_name(char *name)
|
|
{
|
|
struct menu* m;
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
list_for_each_entry(m, &menus, list) {
|
|
if(strcmp(m->name, name) == 0)
|
|
return m;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(menu_get_by_name);
|
|
|
|
struct menu_entry* menu_entry_get_by_num(struct menu* m, int num)
|
|
{
|
|
struct menu_entry* me;
|
|
|
|
if (!m || num < 1 || num > m->nb_entries)
|
|
return NULL;
|
|
|
|
list_for_each_entry(me, &m->entries, list) {
|
|
if(me->num == num)
|
|
return me;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(menu_entry_get_by_num);
|
|
|
|
void menu_entry_free(struct menu_entry *me)
|
|
{
|
|
if (!me)
|
|
return;
|
|
|
|
me->free(me);
|
|
}
|
|
EXPORT_SYMBOL(menu_entry_free);
|
|
|
|
static void print_menu_entry(struct menu *m, struct menu_entry *me,
|
|
int selected)
|
|
{
|
|
gotoXY(me->num + 1, 3);
|
|
|
|
if (me->type == MENU_ENTRY_BOX) {
|
|
if (me->box_state)
|
|
puts("[*]");
|
|
else
|
|
puts("[ ]");
|
|
} else {
|
|
puts(" ");
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_SHELL_HUSH))
|
|
process_escape_sequence(me->display, m->display_buffer,
|
|
m->display_buffer_size);
|
|
|
|
printf(" %d: ", me->num);
|
|
if (selected)
|
|
puts("\e[7m");
|
|
if (IS_ENABLED(CONFIG_SHELL_HUSH))
|
|
puts(m->display_buffer);
|
|
else
|
|
puts(me->display);
|
|
|
|
if (selected)
|
|
puts("\e[m");
|
|
}
|
|
|
|
int menu_set_selected_entry(struct menu *m, struct menu_entry* me)
|
|
{
|
|
struct menu_entry* tmp;
|
|
|
|
if (!m || !me)
|
|
return -EINVAL;
|
|
|
|
list_for_each_entry(tmp, &m->entries, list) {
|
|
if(me == tmp) {
|
|
m->selected = me;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(menu_set_selected_entry);
|
|
|
|
int menu_set_selected(struct menu *m, int num)
|
|
{
|
|
struct menu_entry *me;
|
|
|
|
me = menu_entry_get_by_num(m, num);
|
|
|
|
if (!me)
|
|
return -EINVAL;
|
|
|
|
m->selected = me;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(menu_set_selected);
|
|
|
|
int menu_set_auto_select(struct menu *m, int delay)
|
|
{
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
m->auto_select = delay;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(menu_set_auto_select);
|
|
|
|
static void print_menu(struct menu *m)
|
|
{
|
|
struct menu_entry *me;
|
|
|
|
clear();
|
|
gotoXY(1, 2);
|
|
if(m->display) {
|
|
if (IS_ENABLED(CONFIG_SHELL_HUSH)) {
|
|
process_escape_sequence(m->display, m->display_buffer,
|
|
m->display_buffer_size);
|
|
puts(m->display_buffer);
|
|
} else {
|
|
puts(m->display);
|
|
}
|
|
} else {
|
|
puts("Menu : ");
|
|
puts(m->name);
|
|
}
|
|
|
|
list_for_each_entry(me, &m->entries, list) {
|
|
if(m->selected != me)
|
|
print_menu_entry(m, me, 0);
|
|
}
|
|
|
|
if (!m->selected) {
|
|
m->selected = list_first_entry(&m->entries,
|
|
struct menu_entry, list);
|
|
}
|
|
|
|
print_menu_entry(m, m->selected, 1);
|
|
}
|
|
|
|
static int menu_alloc_display_buffer(struct menu *m)
|
|
{
|
|
int min_size;
|
|
|
|
if (m->display)
|
|
min_size = max((int)strlen(m->display), m->width);
|
|
else
|
|
min_size = m->width;
|
|
|
|
|
|
if (m->display_buffer) {
|
|
if (m->display_buffer_size >= min_size)
|
|
return 0;
|
|
m->display_buffer = realloc(m->display_buffer, min_size * sizeof(char));
|
|
} else {
|
|
m->display_buffer = calloc(min_size, sizeof(char));
|
|
}
|
|
|
|
if (!m->display_buffer) {
|
|
perror("display_buffer");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
m->display_buffer_size = min_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int menu_show(struct menu *m)
|
|
{
|
|
int ch, ch_previous = 0;
|
|
int escape = 0;
|
|
int countdown;
|
|
int auto_display_len = 16;
|
|
uint64_t start, second;
|
|
int ret;
|
|
|
|
if(!m || list_empty(&m->entries))
|
|
return -EINVAL;
|
|
|
|
ret = menu_alloc_display_buffer(m);
|
|
if (ret)
|
|
return ret;
|
|
|
|
print_menu(m);
|
|
|
|
countdown = m->auto_select;
|
|
if (m->auto_select >= 0) {
|
|
gotoXY(m->nb_entries + 2, 3);
|
|
if (!m->auto_display) {
|
|
printf("Auto Select in");
|
|
} else {
|
|
auto_display_len = strlen(m->auto_display);
|
|
printf(m->auto_display);
|
|
}
|
|
printf(" %2d", countdown--);
|
|
}
|
|
|
|
start = get_time_ns();
|
|
second = start;
|
|
while (m->auto_select > 0 && !is_timeout(start, m->auto_select * SECOND)) {
|
|
if (tstc()) {
|
|
m->auto_select = -1;
|
|
break;
|
|
}
|
|
|
|
if (is_timeout(second, SECOND)) {
|
|
printf("\b\b%2d", countdown--);
|
|
second += SECOND;
|
|
}
|
|
}
|
|
|
|
gotoXY(m->nb_entries + 2, 3);
|
|
printf("%*c", auto_display_len + 4, ' ');
|
|
|
|
gotoXY(m->selected->num + 1, 3);
|
|
|
|
do {
|
|
if (m->auto_select >= 0)
|
|
ch = KEY_RETURN;
|
|
else
|
|
ch = getc();
|
|
|
|
m->auto_select = -1;
|
|
|
|
switch(ch) {
|
|
case 0x1b:
|
|
escape = 1;
|
|
break;
|
|
case '[':
|
|
if (escape)
|
|
break;
|
|
case 'A': /* up */
|
|
escape = 0;
|
|
print_menu_entry(m, m->selected, 0);
|
|
m->selected = list_entry(m->selected->list.prev, struct menu_entry,
|
|
list);
|
|
if (&(m->selected->list) == &(m->entries)) {
|
|
m->selected = list_entry(m->selected->list.prev, struct menu_entry,
|
|
list);
|
|
}
|
|
print_menu_entry(m, m->selected, 1);
|
|
break;
|
|
case 'B': /* down */
|
|
escape = 0;
|
|
print_menu_entry(m, m->selected, 0);
|
|
m->selected = list_entry(m->selected->list.next, struct menu_entry,
|
|
list);
|
|
if (&(m->selected->list) == &(m->entries)) {
|
|
m->selected = list_entry(m->selected->list.next, struct menu_entry,
|
|
list);
|
|
}
|
|
print_menu_entry(m, m->selected, 1);
|
|
break;
|
|
case ' ':
|
|
if (m->selected->type != MENU_ENTRY_BOX)
|
|
break;
|
|
m->selected->box_state = !m->selected->box_state;
|
|
if (m->selected->action)
|
|
m->selected->action(m, m->selected);
|
|
print_menu_entry(m, m->selected, 1);
|
|
break;
|
|
case KEY_ENTER:
|
|
if (ch_previous == KEY_RETURN)
|
|
break;
|
|
case KEY_RETURN:
|
|
if (ch_previous == KEY_ENTER)
|
|
break;
|
|
clear();
|
|
gotoXY(1,1);
|
|
m->selected->action(m, m->selected);
|
|
if (m->selected->non_re_ent)
|
|
return m->selected->num;
|
|
else
|
|
print_menu(m);
|
|
default:
|
|
break;
|
|
}
|
|
ch_previous = ch;
|
|
} while(1);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(menu_show);
|
|
|
|
void menu_action_exit(struct menu *m, struct menu_entry *me) {}
|
|
EXPORT_SYMBOL(menu_action_exit);
|
|
|
|
struct submenu {
|
|
char *submenu;
|
|
struct menu_entry entry;
|
|
};
|
|
|
|
static void menu_action_show(struct menu *m, struct menu_entry *me)
|
|
{
|
|
struct submenu *s = container_of(me, struct submenu, entry);
|
|
struct menu *sm;
|
|
|
|
if (me->type == MENU_ENTRY_BOX && !me->box_state)
|
|
return;
|
|
|
|
sm = menu_get_by_name(s->submenu);
|
|
if (sm)
|
|
menu_show(sm);
|
|
else
|
|
eprintf("no such menu: %s\n", s->submenu);
|
|
}
|
|
|
|
static void submenu_free(struct menu_entry *me)
|
|
{
|
|
struct submenu *s = container_of(me, struct submenu, entry);
|
|
|
|
free(s->entry.display);
|
|
free(s->submenu);
|
|
free(s);
|
|
}
|
|
|
|
struct menu_entry *menu_add_submenu(struct menu *parent, char *submenu, char *display)
|
|
{
|
|
struct submenu *s = calloc(1, sizeof(*s));
|
|
int ret;
|
|
|
|
if (!s)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
s->submenu = strdup(submenu);
|
|
s->entry.action = menu_action_show;
|
|
s->entry.free = submenu_free;
|
|
s->entry.display = strdup(display);
|
|
if (!s->entry.display || !s->submenu) {
|
|
ret = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
ret = menu_add_entry(parent, &s->entry);
|
|
if (ret)
|
|
goto err_free;
|
|
|
|
return &s->entry;
|
|
|
|
err_free:
|
|
submenu_free(&s->entry);
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL(menu_add_submenu);
|
|
|
|
struct action_entry {
|
|
char *command;
|
|
struct menu_entry entry;
|
|
};
|
|
|
|
static void menu_action_command(struct menu *m, struct menu_entry *me)
|
|
{
|
|
struct action_entry *e = container_of(me, struct action_entry, entry);
|
|
int ret;
|
|
const char *s = getenv(e->command);
|
|
|
|
/* can be a command as boot */
|
|
if (!s)
|
|
s = e->command;
|
|
|
|
ret = run_command (s, 0);
|
|
|
|
if (ret < 0)
|
|
udelay(1000000);
|
|
}
|
|
|
|
static void menu_command_free(struct menu_entry *me)
|
|
{
|
|
struct action_entry *e = container_of(me, struct action_entry, entry);
|
|
|
|
free(e->entry.display);
|
|
free(e->command);
|
|
|
|
free(e);
|
|
}
|
|
|
|
struct menu_entry *menu_add_command_entry(struct menu *m, char *display,
|
|
char *command, menu_entry_type type)
|
|
{
|
|
struct action_entry *e = calloc(1, sizeof(*e));
|
|
int ret;
|
|
|
|
if (!e)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
e->command = strdup(command);
|
|
e->entry.action = menu_action_command;
|
|
e->entry.free = menu_command_free;
|
|
e->entry.type = type;
|
|
e->entry.display = strdup(display);
|
|
|
|
if (!e->entry.display || !e->command) {
|
|
ret = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
ret = menu_add_entry(m, &e->entry);
|
|
if (ret)
|
|
goto err_free;
|
|
|
|
return &e->entry;
|
|
err_free:
|
|
menu_command_free(&e->entry);
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL(menu_add_command_entry);
|