2 * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
3 * Released under the terms of the GNU GPL v2.0.
5 * Introduced single menu mode (show all sub-menus in one large tree).
6 * 2002-11-06 Petr Baudis <pasky@ucw.cz>
8 * Directly use liblxdialog library routines.
9 * 2002-11-14 Petr Baudis <pasky@ucw.cz>
12 #include <sys/ioctl.h>
25 #include "lxdialog/dialog.h"
27 #define LKC_DIRECT_LINK
30 static char menu_backtitle[128];
31 static const char mconf_readme[] =
34 "Some features may be built directly into BusyBox. Some features\n"
35 "may be completely removed altogether. There are also certain\n"
36 "parameters which are not really features, but must be\n"
37 "entered in as decimal or hexadecimal numbers or possibly text.\n"
39 "Menu items beginning with [*] or [ ] represent features\n"
40 "configured to be built in or removed respectively.\n"
42 "To change any of these features, highlight it with the cursor\n"
43 "keys and press <Y> to build it in or <N> to removed it.\n"
44 "You may also press the <Space Bar> to cycle\n"
45 "through the available options (ie. Y->N->Y).\n"
47 "Some additional keyboard hints:\n"
51 "o Use the Up/Down arrow keys (cursor keys) to highlight the item\n"
52 " you wish to change or submenu wish to select and press <Enter>.\n"
53 " Submenus are designated by \"--->\".\n"
55 " Shortcut: Press the option's highlighted letter (hotkey).\n"
56 " Pressing a hotkey more than once will sequence\n"
57 " through all visible items which use that hotkey.\n"
59 " You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n"
60 " unseen options into view.\n"
62 "o To exit a menu use the cursor keys to highlight the <Exit> button\n"
63 " and press <ENTER>.\n"
65 " Shortcut: Press <ESC><ESC> or <E> or <X> if there is no hotkey\n"
66 " using those letters. You may press a single <ESC>, but\n"
67 " there is a delayed response which you may find annoying.\n"
69 " Also, the <TAB> and cursor keys will cycle between <Select>,\n"
70 " <Exit> and <Help>\n"
72 "o To get help with an item, use the cursor keys to highlight <Help>\n"
73 " and Press <ENTER>.\n"
75 " Shortcut: Press <H> or <?>.\n"
78 "Radiolists (Choice lists)\n"
80 "o Use the cursor keys to select the option you wish to set and press\n"
81 " <S> or the <SPACE BAR>.\n"
83 " Shortcut: Press the first letter of the option you wish to set then\n"
84 " press <S> or <SPACE BAR>.\n"
86 "o To see available help for the item, use the cursor keys to highlight\n"
87 " <Help> and Press <ENTER>.\n"
89 " Shortcut: Press <H> or <?>.\n"
91 " Also, the <TAB> and cursor keys will cycle between <Select> and\n"
97 "o Enter the requested information and press <ENTER>\n"
98 " If you are entering hexadecimal values, it is not necessary to\n"
99 " add the '0x' prefix to the entry.\n"
101 "o For help, use the <TAB> or cursor keys to highlight the help option\n"
102 " and press <ENTER>. You can try <TAB><H> as well.\n"
105 "Text Box (Help Window)\n"
107 "o Use the cursor keys to scroll up/down/left/right. The VI editor\n"
108 " keys h,j,k,l function here as do <SPACE BAR> and <B> for those\n"
109 " who are familiar with less and lynx.\n"
111 "o Press <E>, <X>, <Enter> or <Esc><Esc> to exit.\n"
114 "Alternate Configuration Files\n"
115 "-----------------------------\n"
116 "Menuconfig supports the use of alternate configuration files for\n"
117 "those who, for various reasons, find it necessary to switch\n"
118 "between different configurations.\n"
120 "At the end of the main menu you will find two options. One is\n"
121 "for saving the current configuration to a file of your choosing.\n"
122 "The other option is for loading a previously saved alternate\n"
125 "Even if you don't use alternate configuration files, but you\n"
126 "find during a Menuconfig session that you have completely messed\n"
127 "up your settings, you may use the \"Load Alternate...\" option to\n"
128 "restore your previously saved settings from \".config\" without\n"
129 "restarting Menuconfig.\n"
131 "Other information\n"
132 "-----------------\n"
133 "If you use Menuconfig in an XTERM window make sure you have your\n"
134 "$TERM variable set to point to a xterm definition which supports color.\n"
135 "Otherwise, Menuconfig will look rather bad. Menuconfig will not\n"
136 "display correctly in a RXVT window because rxvt displays only one\n"
137 "intensity of color, bright.\n"
139 "Menuconfig will display larger menus on screens or xterms which are\n"
140 "set to display more than the standard 25 row by 80 column geometry.\n"
141 "In order for this to work, the \"stty size\" command must be able to\n"
142 "display the screen's current row and column geometry. I STRONGLY\n"
143 "RECOMMEND that you make sure you do NOT have the shell variables\n"
144 "LINES and COLUMNS exported into your environment. Some distributions\n"
145 "export those variables via /etc/profile. Some ncurses programs can\n"
146 "become confused when those variables (LINES & COLUMNS) don't reflect\n"
147 "the true screen size.\n"
149 "Optional personality available\n"
150 "------------------------------\n"
151 "If you prefer to have all of the options listed in a single\n"
152 "menu, rather than the default multimenu hierarchy, run the menuconfig\n"
153 "with MENUCONFIG_MODE environment variable set to single_menu. Example:\n"
155 "make MENUCONFIG_MODE=single_menu menuconfig\n"
157 "<Enter> will then unroll the appropriate category, or enfold it if it\n"
158 "is already unrolled.\n"
160 "Note that this mode can eventually be a little more CPU expensive\n"
161 "(especially with a larger number of unrolled categories) than the\n"
163 menu_instructions[] =
164 "Arrow keys navigate the menu. "
165 "<Enter> selects submenus --->. "
166 "Highlighted letters are hotkeys. "
167 "Pressing <Y> selectes a feature, while <N> will exclude a feature. "
168 "Press <Esc><Esc> to exit, <?> for Help, </> for Search. "
169 "Legend: [*] feature is selected [ ] feature is excluded",
170 radiolist_instructions[] =
171 "Use the arrow keys to navigate this window or "
172 "press the hotkey of the item you wish to select "
173 "followed by the <SPACE BAR>. "
174 "Press <?> for additional information about this option.",
175 inputbox_instructions_int[] =
176 "Please enter a decimal value. "
177 "Fractions will not be accepted. "
178 "Use the <TAB> key to move from the input field to the buttons below it.",
179 inputbox_instructions_hex[] =
180 "Please enter a hexadecimal value. "
181 "Use the <TAB> key to move from the input field to the buttons below it.",
182 inputbox_instructions_string[] =
183 "Please enter a string value. "
184 "Use the <TAB> key to move from the input field to the buttons below it.",
186 "This feature depends on another which has been configured as a module.\n"
187 "As a result, this feature will be built as a module.",
189 "There is no help available for this option.\n",
191 "Enter the name of the configuration file you wish to load. "
192 "Accept the name shown to restore the configuration you "
193 "last retrieved. Leave blank to abort.",
196 "For various reasons, one may wish to keep several different BusyBox\n"
197 "configurations available on a single machine.\n"
199 "If you have saved a previous configuration in a file other than the\n"
200 "BusyBox's default, entering the name of the file here will allow you\n"
201 "to modify that configuration.\n"
203 "If you are uncertain, then you have probably never used alternate\n"
204 "configuration files. You should therefor leave this blank to abort.\n",
206 "Enter a filename to which this configuration should be saved "
207 "as an alternate. Leave blank to abort.",
210 "For various reasons, one may wish to keep different BusyBox\n"
211 "configurations available on a single machine.\n"
213 "Entering a file name here will allow you to later retrieve, modify\n"
214 "and use the current configuration as an alternate to whatever\n"
215 "configuration options you have selected at that time.\n"
217 "If you are uncertain what all this means then you should probably\n"
218 "leave this blank.\n",
221 "Search for CONFIG_ symbols and display their relations.\n"
222 "Example: search for \"^FOO\"\n"
224 "-----------------------------------------------------------------\n"
226 "Prompt: Foo bus is used to drive the bar HW\n"
227 "Defined at drivers/pci/Kconfig:47\n"
228 "Depends on: X86_LOCAL_APIC && X86_IO_APIC || IA64\n"
230 " -> Bus options (PCI, PCMCIA, EISA, MCA, ISA)\n"
231 " -> PCI support (PCI [=y])\n"
232 " -> PCI access mode (<choice> [=y])\n"
233 "Selects: LIBCRC32\n"
235 "-----------------------------------------------------------------\n"
236 "o The line 'Prompt:' shows the text used in the menu structure for\n"
237 " this CONFIG_ symbol\n"
238 "o The 'Defined at' line tell at what file / line number the symbol\n"
240 "o The 'Depends on:' line tell what symbols needs to be defined for\n"
241 " this symbol to be visible in the menu (selectable)\n"
242 "o The 'Location:' lines tell where in the menu structure this symbol\n"
244 " A location followed by a [=y] indicate that this is a selectable\n"
245 " menu item - and current value is displayed inside brackets.\n"
246 "o The 'Selects:' line tell what symbol will be automatically\n"
247 " selected if this symbol is selected (y or m)\n"
248 "o The 'Selected by' line tell what symbol has selected this symbol\n"
250 "Only relevant lines are shown.\n"
253 "Examples: USB => find all CONFIG_ symbols containing USB\n"
254 " ^USB => find all CONFIG_ symbols starting with USB\n"
255 " USB$ => find all CONFIG_ symbols ending with USB\n"
258 static char filename[PATH_MAX+1] = ".config";
260 static struct termios ios_org;
261 static int rows = 0, cols = 0;
262 static struct menu *current_menu;
263 static int child_count;
264 static int single_menu_mode;
266 static struct dialog_list_item *items[16384]; /* FIXME: This ought to be dynamic. */
269 static void conf(struct menu *menu);
270 static void conf_choice(struct menu *menu);
271 static void conf_string(struct menu *menu);
272 static void conf_load(void);
273 static void conf_save(void);
274 static void show_textbox(const char *title, const char *text, int r, int c);
275 static void show_helptext(const char *title, const char *text);
276 static void show_help(struct menu *menu);
277 static void show_file(const char *filename, const char *title, int r, int c);
279 static void init_wsize(void)
284 if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)) {
290 env = getenv("LINES");
297 env = getenv("COLUMNS");
304 if (rows < 19 || cols < 80) {
305 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
306 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
314 static void cinit(void)
319 static void cmake(void)
321 items[item_no] = malloc(sizeof(struct dialog_list_item));
322 memset(items[item_no], 0, sizeof(struct dialog_list_item));
323 items[item_no]->tag = malloc(32); items[item_no]->tag[0] = 0;
324 items[item_no]->name = malloc(512); items[item_no]->name[0] = 0;
325 items[item_no]->namelen = 0;
329 static int cprint_name(const char *fmt, ...)
337 res = vsnprintf(items[item_no - 1]->name + items[item_no - 1]->namelen,
338 512 - items[item_no - 1]->namelen, fmt, ap);
340 items[item_no - 1]->namelen += res;
346 static int cprint_tag(const char *fmt, ...)
354 res = vsnprintf(items[item_no - 1]->tag, 32, fmt, ap);
360 static void cdone(void)
364 for (i = 0; i < item_no; i++) {
366 free(items[i]->name);
373 static void get_prompt_str(struct gstr *r, struct property *prop)
376 struct menu *submenu[8], *menu;
378 str_printf(r, "Prompt: %s\n", prop->text);
379 str_printf(r, " Defined at %s:%d\n", prop->menu->file->name,
381 if (!expr_is_yes(prop->visible.expr)) {
382 str_append(r, " Depends on: ");
383 expr_gstr_print(prop->visible.expr, r);
386 menu = prop->menu->parent;
387 for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent)
390 str_printf(r, " Location:\n");
391 for (j = 4; --i >= 0; j += 2) {
393 str_printf(r, "%*c-> %s", j, ' ', menu_get_prompt(menu));
395 str_printf(r, " (%s [=%s])", menu->sym->name ?
396 menu->sym->name : "<choice>",
397 sym_get_string_value(menu->sym));
404 static void get_symbol_str(struct gstr *r, struct symbol *sym)
407 struct property *prop;
409 str_printf(r, "Symbol: %s [=%s]\n", sym->name,
410 sym_get_string_value(sym));
411 for_all_prompts(sym, prop)
412 get_prompt_str(r, prop);
414 for_all_properties(sym, prop, P_SELECT) {
416 str_append(r, " Selects: ");
419 str_printf(r, " && ");
420 expr_gstr_print(prop->expr, r);
424 if (sym->rev_dep.expr) {
425 str_append(r, " Selected by: ");
426 expr_gstr_print(sym->rev_dep.expr, r);
429 str_append(r, "\n\n");
432 static struct gstr get_relations_str(struct symbol **sym_arr)
435 struct gstr res = str_new();
438 for (i = 0; sym_arr && (sym = sym_arr[i]); i++)
439 get_symbol_str(&res, sym);
441 str_append(&res, "No matches found.\n");
445 static void search_conf(void)
447 struct symbol **sym_arr;
451 switch (dialog_inputbox("Search Configuration Parameter",
452 "Enter Keyword", 10, 75,
457 show_helptext("Search Configuration", search_help);
463 sym_arr = sym_re_search(dialog_input_result);
464 res = get_relations_str(sym_arr);
466 show_textbox("Search Results", str_get(&res), 0, 0);
470 static void build_conf(struct menu *menu)
473 struct property *prop;
475 int type, tmp, doint = 2;
479 if (!menu_is_visible(menu))
485 if (prop && menu != current_menu) {
486 const char *prompt = menu_get_prompt(menu);
487 switch (prop->type) {
491 cprint_tag("m%p", menu);
493 if (single_menu_mode) {
494 cprint_name("%s%*c%s",
495 menu->data ? "-->" : "++>",
496 indent + 1, ' ', prompt);
498 cprint_name(" %*c%s --->", indent + 1, ' ', prompt);
501 if (single_menu_mode && menu->data)
508 cprint_tag(":%p", menu);
509 cprint_name("---%*c%s", indent + 1, ' ', prompt);
518 type = sym_get_type(sym);
519 if (sym_is_choice(sym)) {
520 struct symbol *def_sym = sym_get_choice_value(sym);
521 struct menu *def_menu = NULL;
524 for (child = menu->list; child; child = child->next) {
525 if (menu_is_visible(child) && child->sym == def_sym)
529 val = sym_get_tristate_value(sym);
530 if (sym_is_changable(sym)) {
531 cprint_tag("t%p", menu);
534 cprint_name("[%c]", val == no ? ' ' : '*');
538 case yes: ch = '*'; break;
539 case mod: ch = 'M'; break;
540 default: ch = ' '; break;
542 cprint_name("<%c>", ch);
546 cprint_tag("%c%p", def_menu ? 't' : ':', menu);
550 cprint_name("%*c%s", indent + 1, ' ', menu_get_prompt(menu));
553 cprint_name(" (%s)", menu_get_prompt(def_menu));
554 cprint_name(" --->");
555 if (def_menu->list) {
557 build_conf(def_menu);
564 if (menu == current_menu) {
565 cprint_tag(":%p", menu);
566 cprint_name("---%*c%s", indent + 1, ' ', menu_get_prompt(menu));
570 val = sym_get_tristate_value(sym);
571 if (sym_is_choice_value(sym) && val == yes) {
572 cprint_tag(":%p", menu);
577 cprint_tag("t%p", menu);
578 if (sym_is_changable(sym))
579 cprint_name("[%c]", val == no ? ' ' : '*');
584 cprint_tag("t%p", menu);
586 case yes: ch = '*'; break;
587 case mod: ch = 'M'; break;
588 default: ch = ' '; break;
590 if (sym_is_changable(sym))
591 cprint_name("<%c>", ch);
596 cprint_tag("s%p", menu);
597 tmp = cprint_name("(%s)", sym_get_string_value(sym));
598 tmp = indent - tmp + 4;
601 cprint_name("%*c%s%s", tmp, ' ', menu_get_prompt(menu),
602 (sym_has_value(sym) || !sym_is_changable(sym)) ?
607 cprint_name("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu),
608 (sym_has_value(sym) || !sym_is_changable(sym)) ?
610 if (menu->prompt->type == P_MENU) {
611 cprint_name(" --->");
618 for (child = menu->list; child; child = child->next)
623 static void conf(struct menu *menu)
625 struct dialog_list_item *active_item = NULL;
626 struct menu *submenu;
627 const char *prompt = menu_get_prompt(menu);
629 char active_entry[40];
632 unlink("lxdialog.scrltmp");
642 if (menu == &rootmenu) {
643 cmake(); cprint_tag(":"); cprint_name("--- ");
644 cmake(); cprint_tag("L"); cprint_name("Load an Alternate Configuration File");
645 cmake(); cprint_tag("S"); cprint_name("Save Configuration to an Alternate File");
648 stat = dialog_menu(prompt ? prompt : "Main Menu",
649 menu_instructions, rows, cols, rows - 10,
650 active_entry, item_no, items);
654 if (stat == 1 || stat == 255)
657 active_item = first_sel_item(item_no, items);
660 active_item->selected = 0;
661 strncpy(active_entry, active_item->tag, sizeof(active_entry));
662 active_entry[sizeof(active_entry)-1] = 0;
663 type = active_entry[0];
669 if (sscanf(active_entry + 1, "%p", &submenu) == 1)
676 if (single_menu_mode)
677 submenu->data = (void *) (long) !submenu->data;
682 if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)
683 conf_choice(submenu);
684 else if (submenu->prompt->type == P_MENU)
688 conf_string(submenu);
702 show_helptext("README", mconf_readme);
706 if (sym_set_tristate_value(sym, yes))
708 if (sym_set_tristate_value(sym, mod))
709 show_textbox(NULL, setmod_text, 6, 74);
714 sym_set_tristate_value(sym, no);
718 sym_set_tristate_value(sym, mod);
722 sym_toggle_tristate_value(sym);
723 else if (type == 'm')
733 static void show_textbox(const char *title, const char *text, int r, int c)
737 fd = creat(".help.tmp", 0777);
738 write(fd, text, strlen(text));
740 show_file(".help.tmp", title, r, c);
744 static void show_helptext(const char *title, const char *text)
746 show_textbox(title, text, 0, 0);
749 static void show_help(struct menu *menu)
751 struct gstr help = str_new();
752 struct symbol *sym = menu->sym;
757 str_printf(&help, "%s:\n\n", sym->name);
758 str_append(&help, sym->help);
759 str_append(&help, "\n");
762 str_append(&help, nohelp_text);
764 get_symbol_str(&help, sym);
765 show_helptext(menu_get_prompt(menu), str_get(&help));
769 static void show_file(const char *filename, const char *title, int r, int c)
771 while (dialog_textbox(title, filename, r ? r : rows, c ? c : cols) < 0)
775 static void conf_choice(struct menu *menu)
777 const char *prompt = menu_get_prompt(menu);
779 struct symbol *active;
781 active = sym_get_choice_value(menu->sym);
785 for (child = menu->list; child; child = child->next) {
786 if (!menu_is_visible(child))
789 cprint_tag("%p", child);
790 cprint_name("%s", menu_get_prompt(child));
791 if (child->sym == sym_get_choice_value(menu->sym))
792 items[item_no - 1]->selected = 1; /* ON */
793 else if (child->sym == active)
794 items[item_no - 1]->selected = 2; /* SELECTED */
796 items[item_no - 1]->selected = 0; /* OFF */
799 switch (dialog_checklist(prompt ? prompt : "Main Menu",
800 radiolist_instructions, 15, 70, 6,
801 item_no, items, FLAG_RADIO)) {
803 if (sscanf(first_sel_item(item_no, items)->tag, "%p", &child) != 1)
805 sym_set_tristate_value(child->sym, yes);
808 if (sscanf(first_sel_item(item_no, items)->tag, "%p", &child) == 1) {
820 static void conf_string(struct menu *menu)
822 const char *prompt = menu_get_prompt(menu);
827 switch (sym_get_type(menu->sym)) {
829 heading = (char *) inputbox_instructions_int;
832 heading = (char *) inputbox_instructions_hex;
835 heading = (char *) inputbox_instructions_string;
838 heading = "Internal mconf error!";
842 switch (dialog_inputbox(prompt ? prompt : "Main Menu",
844 sym_get_string_value(menu->sym))) {
846 if (sym_set_string_value(menu->sym, dialog_input_result))
848 show_textbox(NULL, "You have made an invalid entry.", 5, 43);
859 static void conf_load(void)
862 switch (dialog_inputbox(NULL, load_config_text, 11, 55,
865 if (!dialog_input_result[0])
867 if (!conf_read(dialog_input_result))
869 show_textbox(NULL, "File does not exist!", 5, 38);
872 show_helptext("Load Alternate Configuration", load_config_help);
880 static void conf_save(void)
883 switch (dialog_inputbox(NULL, save_config_text, 11, 55,
886 if (!dialog_input_result[0])
888 if (!conf_write(dialog_input_result))
890 show_textbox(NULL, "Can't create file! Probably a nonexistent directory.", 5, 60);
893 show_helptext("Save Alternate Configuration", save_config_help);
901 static void conf_cleanup(void)
903 tcsetattr(1, TCSAFLUSH, &ios_org);
907 static void winch_handler(int sig)
911 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
919 if (rows < 19 || cols < 80) {
921 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
922 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
931 int main(int ac, char **av)
940 sym = sym_lookup("VERSION", 0);
942 snprintf(menu_backtitle, 128, "BusyBox v%s Configuration",
943 sym_get_string_value(sym));
945 mode = getenv("MENUCONFIG_MODE");
947 if (!strcasecmp(mode, "single_menu"))
948 single_menu_mode = 1;
951 tcgetattr(1, &ios_org);
952 atexit(conf_cleanup);
955 signal(SIGWINCH, winch_handler);
959 /* Restart dialog to act more like when lxdialog was still separate */
962 stat = dialog_yesno(NULL,
963 "Do you wish to save your new BusyBox configuration?", 5, 60);
970 "*** End of BusyBox configuration.\n");
972 printf("\n\nYour BusyBox configuration changes were NOT saved.\n\n");