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