blob: fe4562b177e45ad73ebe51424cd9038d320bfafc [file] [log] [blame]
Eric Andersenc9f20d92002-12-05 08:41:41 +00001/*
2 * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
3 * Released under the terms of the GNU GPL v2.0.
4 *
5 * Introduced single menu mode (show all sub-menus in one large tree).
6 * 2002-11-06 Petr Baudis <pasky@ucw.cz>
7 *
8 * Directly use liblxdialog library routines.
9 * 2002-11-14 Petr Baudis <pasky@ucw.cz>
10 */
11
12#include <sys/ioctl.h>
13#include <sys/wait.h>
14#include <sys/termios.h>
15#include <ctype.h>
16#include <errno.h>
17#include <fcntl.h>
18#include <limits.h>
19#include <signal.h>
20#include <stdarg.h>
21#include <stdlib.h>
22#include <string.h>
23#include <termios.h>
24#include <unistd.h>
25
26#include "dialog.h"
27
28#define LKC_DIRECT_LINK
29#include "lkc.h"
30
Eric Andersen72d8e442003-08-05 02:18:25 +000031static char menu_backtitle[128];
Eric Andersenc9f20d92002-12-05 08:41:41 +000032static const char menu_instructions[] =
33 "Arrow keys navigate the menu. "
34 "<Enter> selects submenus --->. "
35 "Highlighted letters are hotkeys. "
36 "Pressing <Y> selectes a feature, while <N> will exclude a feature. "
37 "Press <Esc><Esc> to exit, <?> for Help. "
38 "Legend: [*] feature is selected [ ] feature is excluded",
39radiolist_instructions[] =
40 "Use the arrow keys to navigate this window or "
41 "press the hotkey of the item you wish to select "
42 "followed by the <SPACE BAR>. "
43 "Press <?> for additional information about this option.",
44inputbox_instructions_int[] =
45 "Please enter a decimal value. "
46 "Fractions will not be accepted. "
47 "Use the <TAB> key to move from the input field to the buttons below it.",
48inputbox_instructions_hex[] =
49 "Please enter a hexadecimal value. "
50 "Use the <TAB> key to move from the input field to the buttons below it.",
51inputbox_instructions_string[] =
52 "Please enter a string value. "
53 "Use the <TAB> key to move from the input field to the buttons below it.",
54setmod_text[] =
55 "This feature depends on another which has been configured as a module.\n"
56 "As a result, this feature will be built as a module.",
57nohelp_text[] =
58 "There is no help available for this option.\n",
59load_config_text[] =
60 "Enter the name of the configuration file you wish to load. "
61 "Accept the name shown to restore the configuration you "
62 "last retrieved. Leave blank to abort.",
63load_config_help[] =
64 "\n"
65 "For various reasons, one may wish to keep several different BusyBox\n"
66 "configurations available on a single machine.\n"
67 "\n"
68 "If you have saved a previous configuration in a file other than the\n"
Eric Andersen72d8e442003-08-05 02:18:25 +000069 "BusyBox's default, entering the name of the file here will allow you\n"
Eric Andersenc9f20d92002-12-05 08:41:41 +000070 "to modify that configuration.\n"
71 "\n"
72 "If you are uncertain, then you have probably never used alternate\n"
73 "configuration files. You should therefor leave this blank to abort.\n",
74save_config_text[] =
75 "Enter a filename to which this configuration should be saved "
76 "as an alternate. Leave blank to abort.",
77save_config_help[] =
78 "\n"
79 "For various reasons, one may wish to keep different BusyBox\n"
80 "configurations available on a single machine.\n"
81 "\n"
82 "Entering a file name here will allow you to later retrieve, modify\n"
83 "and use the current configuration as an alternate to whatever\n"
84 "configuration options you have selected at that time.\n"
85 "\n"
86 "If you are uncertain what all this means then you should probably\n"
Eric Andersena63d09a2003-06-30 18:14:36 +000087 "leave this blank.\n",
88top_menu_help[] =
89 "\n"
90 "Use the Up/Down arrow keys (cursor keys) to highlight the item\n"
91 "you wish to change or submenu wish to select and press <Enter>.\n"
92 "Submenus are designated by \"--->\".\n"
93 "\n"
94 "Shortcut: Press the option's highlighted letter (hotkey).\n"
95 "\n"
96 "You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n"
97 "unseen options into view.\n"
Eric Andersenc9f20d92002-12-05 08:41:41 +000098;
99
100static char filename[PATH_MAX+1] = ".config";
101static int indent = 0;
102static struct termios ios_org;
103static int rows, cols;
104static struct menu *current_menu;
105static int child_count;
106static int single_menu_mode;
107
108static struct dialog_list_item *items[16384]; /* FIXME: This ought to be dynamic. */
109static int item_no;
110
111static void conf(struct menu *menu);
112static void conf_choice(struct menu *menu);
113static void conf_string(struct menu *menu);
114static void conf_load(void);
115static void conf_save(void);
116static void show_textbox(const char *title, const char *text, int r, int c);
117static void show_helptext(const char *title, const char *text);
118static void show_help(struct menu *menu);
119static void show_readme(void);
120
121static void init_wsize(void)
122{
123 struct winsize ws;
Eric Andersen72d8e442003-08-05 02:18:25 +0000124 char *env;
Eric Andersenc9f20d92002-12-05 08:41:41 +0000125
126 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
127 rows = 24;
128 cols = 80;
129 } else {
130 rows = ws.ws_row;
131 cols = ws.ws_col;
Eric Andersen72d8e442003-08-05 02:18:25 +0000132 if (!rows) {
133 env = getenv("LINES");
134 if (env)
135 rows = atoi(env);
136 if (!rows)
137 rows = 24;
138 }
139 if (!cols) {
140 env = getenv("COLUMNS");
141 if (env)
142 cols = atoi(env);
143 if (!cols)
144 cols = 80;
145 }
Eric Andersenc9f20d92002-12-05 08:41:41 +0000146 }
147
148 if (rows < 19 || cols < 80) {
149 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
150 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
151 exit(1);
152 }
153
154 rows -= 4;
155 cols -= 5;
156}
157
158static void cinit(void)
159{
160 item_no = 0;
161}
162
163static void cmake(void)
164{
165 items[item_no] = malloc(sizeof(struct dialog_list_item));
166 memset(items[item_no], 0, sizeof(struct dialog_list_item));
167 items[item_no]->tag = malloc(32); items[item_no]->tag[0] = 0;
168 items[item_no]->name = malloc(512); items[item_no]->name[0] = 0;
169 items[item_no]->namelen = 0;
170 item_no++;
171}
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000172
Eric Andersenc9f20d92002-12-05 08:41:41 +0000173static int cprint_name(const char *fmt, ...)
174{
175 va_list ap;
176 int res;
177
178 if (!item_no)
179 cmake();
180 va_start(ap, fmt);
181 res = vsnprintf(items[item_no - 1]->name + items[item_no - 1]->namelen,
182 512 - items[item_no - 1]->namelen, fmt, ap);
183 if (res > 0)
184 items[item_no - 1]->namelen += res;
185 va_end(ap);
186
187 return res;
188}
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000189
Eric Andersenc9f20d92002-12-05 08:41:41 +0000190static int cprint_tag(const char *fmt, ...)
191{
192 va_list ap;
193 int res;
194
195 if (!item_no)
196 cmake();
197 va_start(ap, fmt);
198 res = vsnprintf(items[item_no - 1]->tag, 32, fmt, ap);
199 va_end(ap);
200
201 return res;
202}
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000203
Eric Andersenc9f20d92002-12-05 08:41:41 +0000204static void cdone(void)
205{
206 int i;
207
208 for (i = 0; i < item_no; i++) {
209 free(items[i]->tag);
210 free(items[i]->name);
211 free(items[i]);
212 }
213
214 item_no = 0;
215}
216
217static void build_conf(struct menu *menu)
218{
219 struct symbol *sym;
220 struct property *prop;
221 struct menu *child;
222 int type, tmp, doint = 2;
223 tristate val;
224 char ch;
225
226 if (!menu_is_visible(menu))
227 return;
228
229 sym = menu->sym;
230 prop = menu->prompt;
231 if (!sym) {
232 if (prop && menu != current_menu) {
233 const char *prompt = menu_get_prompt(menu);
234 switch (prop->type) {
235 case P_MENU:
236 child_count++;
237 cmake();
238 cprint_tag("m%p", menu);
239
240 if (single_menu_mode) {
241 cprint_name("%s%*c%s",
242 menu->data ? "-->" : "++>",
243 indent + 1, ' ', prompt);
244 } else {
Eric Andersen72d8e442003-08-05 02:18:25 +0000245 cprint_name(" %*c%s --->", indent + 1, ' ', prompt);
Eric Andersenc9f20d92002-12-05 08:41:41 +0000246 }
247
248 if (single_menu_mode && menu->data)
249 goto conf_childs;
250 return;
251 default:
252 if (prompt) {
253 child_count++;
254 cmake();
255 cprint_tag(":%p", menu);
256 cprint_name("---%*c%s", indent + 1, ' ', prompt);
257 }
258 }
259 } else
260 doint = 0;
261 goto conf_childs;
262 }
263
264 cmake();
265 type = sym_get_type(sym);
266 if (sym_is_choice(sym)) {
267 struct symbol *def_sym = sym_get_choice_value(sym);
268 struct menu *def_menu = NULL;
269
270 child_count++;
271 for (child = menu->list; child; child = child->next) {
272 if (menu_is_visible(child) && child->sym == def_sym)
273 def_menu = child;
274 }
275
276 val = sym_get_tristate_value(sym);
277 if (sym_is_changable(sym)) {
278 cprint_tag("t%p", menu);
279 switch (type) {
280 case S_BOOLEAN:
281 cprint_name("[%c]", val == no ? ' ' : '*');
282 break;
283 case S_TRISTATE:
284 switch (val) {
285 case yes: ch = '*'; break;
286 case mod: ch = 'M'; break;
287 default: ch = ' '; break;
288 }
289 cprint_name("<%c>", ch);
290 break;
291 }
292 } else {
293 cprint_tag("%c%p", def_menu ? 't' : ':', menu);
294 cprint_name(" ");
295 }
296
297 cprint_name("%*c%s", indent + 1, ' ', menu_get_prompt(menu));
298 if (val == yes) {
299 if (def_menu) {
300 cprint_name(" (%s)", menu_get_prompt(def_menu));
301 cprint_name(" --->");
302 if (def_menu->list) {
303 indent += 2;
304 build_conf(def_menu);
305 indent -= 2;
306 }
307 }
308 return;
309 }
310 } else {
311 child_count++;
312 val = sym_get_tristate_value(sym);
313 if (sym_is_choice_value(sym) && val == yes) {
314 cprint_tag(":%p", menu);
315 cprint_name(" ");
316 } else {
317 switch (type) {
318 case S_BOOLEAN:
319 cprint_tag("t%p", menu);
Eric Andersen72d8e442003-08-05 02:18:25 +0000320 if (sym_is_changable(sym))
321 cprint_name("[%c]", val == no ? ' ' : '*');
322 else
323 cprint_name("---");
Eric Andersenc9f20d92002-12-05 08:41:41 +0000324 break;
325 case S_TRISTATE:
326 cprint_tag("t%p", menu);
327 switch (val) {
328 case yes: ch = '*'; break;
329 case mod: ch = 'M'; break;
330 default: ch = ' '; break;
331 }
Eric Andersen72d8e442003-08-05 02:18:25 +0000332 if (sym_is_changable(sym))
333 cprint_name("<%c>", ch);
334 else
335 cprint_name("---");
Eric Andersenc9f20d92002-12-05 08:41:41 +0000336 break;
337 default:
338 cprint_tag("s%p", menu);
339 tmp = cprint_name("(%s)", sym_get_string_value(sym));
340 tmp = indent - tmp + 4;
341 if (tmp < 0)
342 tmp = 0;
343 cprint_name("%*c%s%s", tmp, ' ', menu_get_prompt(menu),
Eric Andersen72d8e442003-08-05 02:18:25 +0000344 (sym_has_value(sym) || !sym_is_changable(sym)) ?
345 "" : " (NEW)");
Eric Andersenc9f20d92002-12-05 08:41:41 +0000346 goto conf_childs;
347 }
348 }
349 cprint_name("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu),
Eric Andersen72d8e442003-08-05 02:18:25 +0000350 (sym_has_value(sym) || !sym_is_changable(sym)) ?
351 "" : " (NEW)");
352 if (menu->prompt->type == P_MENU) {
353 cprint_name(" --->");
354 return;
355 }
Eric Andersenc9f20d92002-12-05 08:41:41 +0000356 }
357
358conf_childs:
359 indent += doint;
360 for (child = menu->list; child; child = child->next)
361 build_conf(child);
362 indent -= doint;
363}
364
365static void conf(struct menu *menu)
366{
367 struct dialog_list_item *active_item = NULL;
368 struct menu *submenu;
369 const char *prompt = menu_get_prompt(menu);
370 struct symbol *sym;
371 char active_entry[40];
372 int stat, type;
373
374 unlink("lxdialog.scrltmp");
375 active_entry[0] = 0;
376 while (1) {
377 indent = 0;
378 child_count = 0;
379 current_menu = menu;
380 cdone(); cinit();
381 build_conf(menu);
382 if (!child_count)
383 break;
384 if (menu == &rootmenu) {
385 cmake(); cprint_tag(":"); cprint_name("--- ");
386 cmake(); cprint_tag("L"); cprint_name("Load an Alternate Configuration File");
387 cmake(); cprint_tag("S"); cprint_name("Save Configuration to an Alternate File");
388 }
389 dialog_clear();
390 stat = dialog_menu(prompt ? prompt : "Main Menu",
391 menu_instructions, rows, cols, rows - 10,
392 active_entry, item_no, items);
393 if (stat < 0)
394 return;
395
396 if (stat == 1 || stat == 255)
397 break;
398
399 active_item = first_sel_item(item_no, items);
400 if (!active_item)
401 continue;
402 active_item->selected = 0;
403 strncpy(active_entry, active_item->tag, sizeof(active_entry));
404 active_entry[sizeof(active_entry)-1] = 0;
405 type = active_entry[0];
406 if (!type)
407 continue;
408
409 sym = NULL;
410 submenu = NULL;
411 if (sscanf(active_entry + 1, "%p", &submenu) == 1)
412 sym = submenu->sym;
413
414 switch (stat) {
415 case 0:
416 switch (type) {
417 case 'm':
418 if (single_menu_mode)
Eric Andersen72d8e442003-08-05 02:18:25 +0000419 submenu->data = (void *) (long) !submenu->data;
Eric Andersenc9f20d92002-12-05 08:41:41 +0000420 else
421 conf(submenu);
422 break;
423 case 't':
424 if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)
425 conf_choice(submenu);
Eric Andersen72d8e442003-08-05 02:18:25 +0000426 else if (submenu->prompt->type == P_MENU)
427 conf(submenu);
Eric Andersenc9f20d92002-12-05 08:41:41 +0000428 break;
429 case 's':
430 conf_string(submenu);
431 break;
432 case 'L':
433 conf_load();
434 break;
435 case 'S':
436 conf_save();
437 break;
438 }
439 break;
440 case 2:
441 if (sym)
442 show_help(submenu);
443 else
444 show_readme();
445 break;
446 case 3:
447 if (type == 't') {
448 if (sym_set_tristate_value(sym, yes))
449 break;
450 if (sym_set_tristate_value(sym, mod))
451 show_textbox(NULL, setmod_text, 6, 74);
452 }
453 break;
454 case 4:
455 if (type == 't')
456 sym_set_tristate_value(sym, no);
457 break;
458 case 5:
459 if (type == 't')
460 sym_set_tristate_value(sym, mod);
461 break;
462 case 6:
463 if (type == 't')
464 sym_toggle_tristate_value(sym);
465 else if (type == 'm')
466 conf(submenu);
467 break;
468 }
469 }
470}
471
472static void show_textbox(const char *title, const char *text, int r, int c)
473{
474 int fd;
475
476 fd = creat(".help.tmp", 0777);
477 write(fd, text, strlen(text));
478 close(fd);
479 while (dialog_textbox(title, ".help.tmp", r, c) < 0)
480 ;
481 unlink(".help.tmp");
482}
483
484static void show_helptext(const char *title, const char *text)
485{
486 show_textbox(title, text, rows, cols);
487}
488
489static void show_help(struct menu *menu)
490{
491 const char *help;
492 char *helptext;
493 struct symbol *sym = menu->sym;
494
495 help = sym->help;
496 if (!help)
497 help = nohelp_text;
498 if (sym->name) {
499 helptext = malloc(strlen(sym->name) + strlen(help) + 16);
500 sprintf(helptext, "%s:\n\n%s", sym->name, help);
501 show_helptext(menu_get_prompt(menu), helptext);
502 free(helptext);
503 } else
504 show_helptext(menu_get_prompt(menu), help);
505}
506
507static void show_readme(void)
508{
Eric Andersena63d09a2003-06-30 18:14:36 +0000509 show_helptext("Help", top_menu_help);
Eric Andersenc9f20d92002-12-05 08:41:41 +0000510}
511
512static void conf_choice(struct menu *menu)
513{
514 const char *prompt = menu_get_prompt(menu);
515 struct menu *child;
516 struct symbol *active;
517
518 while (1) {
519 current_menu = menu;
520 active = sym_get_choice_value(menu->sym);
521 cdone(); cinit();
522 for (child = menu->list; child; child = child->next) {
523 if (!menu_is_visible(child))
524 continue;
525 cmake();
526 cprint_tag("%p", child);
527 cprint_name("%s", menu_get_prompt(child));
528 items[item_no - 1]->selected = (child->sym == active);
529 }
530
531 switch (dialog_checklist(prompt ? prompt : "Main Menu",
532 radiolist_instructions, 15, 70, 6,
533 item_no, items, FLAG_RADIO)) {
534 case 0:
535 if (sscanf(first_sel_item(item_no, items)->tag, "%p", &menu) != 1)
536 break;
537 sym_set_tristate_value(menu->sym, yes);
538 return;
539 case 1:
540 show_help(menu);
541 break;
542 case 255:
543 return;
544 }
545 }
546}
547
548static void conf_string(struct menu *menu)
549{
550 const char *prompt = menu_get_prompt(menu);
551
552 while (1) {
553 char *heading;
554
555 switch (sym_get_type(menu->sym)) {
556 case S_INT:
557 heading = (char *) inputbox_instructions_int;
558 break;
559 case S_HEX:
560 heading = (char *) inputbox_instructions_hex;
561 break;
562 case S_STRING:
563 heading = (char *) inputbox_instructions_string;
564 break;
565 default:
566 heading = "Internal mconf error!";
567 /* panic? */;
568 }
569
570 switch (dialog_inputbox(prompt ? prompt : "Main Menu",
571 heading, 10, 75,
572 sym_get_string_value(menu->sym))) {
573 case 0:
574 if (sym_set_string_value(menu->sym, dialog_input_result))
575 return;
576 show_textbox(NULL, "You have made an invalid entry.", 5, 43);
577 break;
578 case 1:
579 show_help(menu);
580 break;
581 case 255:
582 return;
583 }
584 }
585}
586
587static void conf_load(void)
588{
589 while (1) {
590 switch (dialog_inputbox(NULL, load_config_text, 11, 55,
591 filename)) {
592 case 0:
593 if (!dialog_input_result[0])
594 return;
595 if (!conf_read(dialog_input_result))
596 return;
597 show_textbox(NULL, "File does not exist!", 5, 38);
598 break;
599 case 1:
600 show_helptext("Load Alternate Configuration", load_config_help);
601 break;
602 case 255:
603 return;
604 }
605 }
606}
607
608static void conf_save(void)
609{
610 while (1) {
611 switch (dialog_inputbox(NULL, save_config_text, 11, 55,
612 filename)) {
613 case 0:
614 if (!dialog_input_result[0])
615 return;
616 if (!conf_write(dialog_input_result))
617 return;
618 show_textbox(NULL, "Can't create file! Probably a nonexistent directory.", 5, 60);
619 break;
620 case 1:
621 show_helptext("Save Alternate Configuration", save_config_help);
622 break;
623 case 255:
624 return;
625 }
626 }
627}
628
629static void conf_cleanup(void)
630{
631 tcsetattr(1, TCSAFLUSH, &ios_org);
632 unlink(".help.tmp");
Eric Andersenc9f20d92002-12-05 08:41:41 +0000633}
634
635static void winch_handler(int sig)
636{
637 struct winsize ws;
638
639 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
640 rows = 24;
641 cols = 80;
642 } else {
643 rows = ws.ws_row;
644 cols = ws.ws_col;
645 }
646
647 if (rows < 19 || cols < 80) {
648 end_dialog();
649 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
650 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
651 exit(1);
652 }
653
654 rows -= 4;
655 cols -= 5;
656
657}
658
659int main(int ac, char **av)
660{
661 int stat;
662 char *mode;
663 struct symbol *sym;
664
665 conf_parse(av[1]);
666 conf_read(NULL);
667
Eric Andersenc9f20d92002-12-05 08:41:41 +0000668 sym = sym_lookup("VERSION", 0);
669 sym_calc_value(sym);
Eric Andersen72d8e442003-08-05 02:18:25 +0000670 snprintf(menu_backtitle, 128, "BusyBox v%s Configuration",
Eric Andersenc9f20d92002-12-05 08:41:41 +0000671 sym_get_string_value(sym));
672
673 mode = getenv("MENUCONFIG_MODE");
674 if (mode) {
675 if (!strcasecmp(mode, "single_menu"))
676 single_menu_mode = 1;
677 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000678
Eric Andersenc9f20d92002-12-05 08:41:41 +0000679 tcgetattr(1, &ios_org);
680 atexit(conf_cleanup);
681 init_wsize();
682 init_dialog();
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000683 signal(SIGWINCH, winch_handler);
Eric Andersenc9f20d92002-12-05 08:41:41 +0000684 conf(&rootmenu);
685 end_dialog();
686
687 /* Restart dialog to act more like when lxdialog was still separate */
688 init_dialog();
689 do {
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000690 stat = dialog_yesno(NULL,
Eric Andersenc9f20d92002-12-05 08:41:41 +0000691 "Do you wish to save your new BusyBox configuration?", 5, 60);
692 } while (stat < 0);
693 end_dialog();
694
695 if (stat == 0) {
696 conf_write(NULL);
697 printf("\n\n"
698 "*** End of BusyBox configuration.\n"
699 "*** Check the top-level Makefile for additional configuration options.\n\n");
700 } else
701 printf("\n\nYour BusyBox configuration changes were NOT saved.\n\n");
702
703 return 0;
704}