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