Make the loop support stuff be much less evil, and make it cope
[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 char menu_backtitle[128];
32 static 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",
39 radiolist_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.",
44 inputbox_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.",
48 inputbox_instructions_hex[] =
49         "Please enter a hexadecimal value. "
50         "Use the <TAB> key to move from the input field to the buttons below it.",
51 inputbox_instructions_string[] =
52         "Please enter a string value. "
53         "Use the <TAB> key to move from the input field to the buttons below it.",
54 setmod_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.",
57 nohelp_text[] =
58         "There is no help available for this option.\n",
59 load_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.",
63 load_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"
69         "BusyBox's default, entering the name of the file here will allow you\n"
70         "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",
74 save_config_text[] =
75         "Enter a filename to which this configuration should be saved "
76         "as an alternate.  Leave blank to abort.",
77 save_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"
87         "leave this blank.\n",
88 top_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"
98 ;
99
100 static char filename[PATH_MAX+1] = ".config";
101 static int indent = 0;
102 static struct termios ios_org;
103 static int rows, cols;
104 static struct menu *current_menu;
105 static int child_count;
106 static int single_menu_mode;
107
108 static struct dialog_list_item *items[16384]; /* FIXME: This ought to be dynamic. */
109 static int item_no;
110
111 static void conf(struct menu *menu);
112 static void conf_choice(struct menu *menu);
113 static void conf_string(struct menu *menu);
114 static void conf_load(void);
115 static void conf_save(void);
116 static void show_textbox(const char *title, const char *text, int r, int c);
117 static void show_helptext(const char *title, const char *text);
118 static void show_help(struct menu *menu);
119 static void show_readme(void);
120
121 static void init_wsize(void)
122 {
123         struct winsize ws;
124         char *env;
125
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;
132                 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                 }
146         }
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
158 static void cinit(void)
159 {
160         item_no = 0;
161 }
162
163 static 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 }
172   
173 static 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 }
189   
190 static 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 }
203   
204 static 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
217 static 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 {
245                                         cprint_name("   %*c%s  --->", indent + 1, ' ', prompt);
246                                 }
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);
320                                 if (sym_is_changable(sym))
321                                         cprint_name("[%c]", val == no ? ' ' : '*');
322                                 else
323                                         cprint_name("---");
324                                 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                                 }
332                                 if (sym_is_changable(sym))
333                                         cprint_name("<%c>", ch);
334                                 else
335                                         cprint_name("---");
336                                 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),
344                                         (sym_has_value(sym) || !sym_is_changable(sym)) ?
345                                         "" : " (NEW)");
346                                 goto conf_childs;
347                         }
348                 }
349                 cprint_name("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu),
350                         (sym_has_value(sym) || !sym_is_changable(sym)) ?
351                         "" : " (NEW)");
352                 if (menu->prompt->type == P_MENU) {
353                         cprint_name("  --->");
354                         return;
355                 }
356         }
357
358 conf_childs:
359         indent += doint;
360         for (child = menu->list; child; child = child->next)
361                 build_conf(child);
362         indent -= doint;
363 }
364
365 static 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)
419                                         submenu->data = (void *) (long) !submenu->data;
420                                 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);
426                                 else if (submenu->prompt->type == P_MENU)
427                                         conf(submenu);
428                                 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
472 static 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
484 static void show_helptext(const char *title, const char *text)
485 {
486         show_textbox(title, text, rows, cols);
487 }
488
489 static 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
507 static void show_readme(void)
508 {
509         show_helptext("Help", top_menu_help);
510 }
511
512 static 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
548 static 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
587 static 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
608 static 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
629 static void conf_cleanup(void)
630 {
631         tcsetattr(1, TCSAFLUSH, &ios_org);
632         unlink(".help.tmp");
633 }
634
635 static 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
659 int 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
668         sym = sym_lookup("VERSION", 0);
669         sym_calc_value(sym);
670         snprintf(menu_backtitle, 128, "BusyBox v%s Configuration",
671                 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         }
678   
679         tcgetattr(1, &ios_org);
680         atexit(conf_cleanup);
681         init_wsize();
682         init_dialog();
683         signal(SIGWINCH, winch_handler); 
684         conf(&rootmenu);
685         end_dialog();
686
687         /* Restart dialog to act more like when lxdialog was still separate */
688         init_dialog();
689         do {
690                 stat = dialog_yesno(NULL, 
691                                 "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 }