Yet another major rework of the BusyBox config system, using the considerably
[oweals/busybox.git] / scripts / config / mconf.c
1 /*
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
31 static 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",
38 radiolist_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.",
43 inputbox_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.",
47 inputbox_instructions_hex[] =
48         "Please enter a hexadecimal value. "
49         "Use the <TAB> key to move from the input field to the buttons below it.",
50 inputbox_instructions_string[] =
51         "Please enter a string value. "
52         "Use the <TAB> key to move from the input field to the buttons below it.",
53 setmod_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.",
56 nohelp_text[] =
57         "There is no help available for this option.\n",
58 load_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.",
62 load_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",
73 save_config_text[] =
74         "Enter a filename to which this configuration should be saved "
75         "as an alternate.  Leave blank to abort.",
76 save_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
89 static char filename[PATH_MAX+1] = ".config";
90 static int indent = 0;
91 static struct termios ios_org;
92 static int rows, cols;
93 static struct menu *current_menu;
94 static int child_count;
95 static int single_menu_mode;
96
97 static struct dialog_list_item *items[16384]; /* FIXME: This ought to be dynamic. */
98 static int item_no;
99
100 static void conf(struct menu *menu);
101 static void conf_choice(struct menu *menu);
102 static void conf_string(struct menu *menu);
103 static void conf_load(void);
104 static void conf_save(void);
105 static void show_textbox(const char *title, const char *text, int r, int c);
106 static void show_helptext(const char *title, const char *text);
107 static void show_help(struct menu *menu);
108 static void show_readme(void);
109
110 static 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
132 static void cinit(void)
133 {
134         item_no = 0;
135 }
136
137 static 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   
147 static 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   
164 static 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   
178 static 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
191 static 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
322 conf_childs:
323         indent += doint;
324         for (child = menu->list; child; child = child->next)
325                 build_conf(child);
326         indent -= doint;
327 }
328
329 static 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
434 static 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
446 static void show_helptext(const char *title, const char *text)
447 {
448         show_textbox(title, text, rows, cols);
449 }
450
451 static 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
469 static void show_readme(void)
470 {
471         while (dialog_textbox(NULL, "scripts/README.Menuconfig", rows, cols) < 0)
472                 ;
473 }
474
475 static 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
511 static 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
550 static 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
571 static 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
592 static void conf_cleanup(void)
593 {
594         tcsetattr(1, TCSAFLUSH, &ios_org);
595         unlink(".help.tmp");
596         unlink("lxdialog.scrltmp");
597 }
598
599 static 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
623 int 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 }