add menuconfig fix from uclibc buildroot
[openwrt.git] / openwrt / package / 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 Buildroot\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         "Buildroot'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 Buildroot\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                 if (menu == current_menu) {
312                         cprint_tag(":%p", menu);
313                         cprint_name("---%*c%s", indent + 1, ' ', menu_get_prompt(menu));
314                         goto conf_childs;
315                 }
316
317                 child_count++;
318                 val = sym_get_tristate_value(sym);
319                 if (sym_is_choice_value(sym) && val == yes) {
320                         cprint_tag(":%p", menu);
321                         cprint_name("   ");
322                 } else {
323                         switch (type) {
324                         case S_BOOLEAN:
325                                 cprint_tag("t%p", menu);
326                                 if (sym_is_changable(sym))
327                                         cprint_name("[%c]", val == no ? ' ' : '*');
328                                 else
329                                         cprint_name("---");
330                                 break;
331                         case S_TRISTATE:
332                                 cprint_tag("t%p", menu);
333                                 switch (val) {
334                                 case yes: ch = '*'; break;
335                                 case mod: ch = 'M'; break;
336                                 default:  ch = ' '; break;
337                                 }
338                                 if (sym_is_changable(sym))
339                                         cprint_name("<%c>", ch);
340                                 else
341                                         cprint_name("---");
342                                 break;
343                         default:
344                                 cprint_tag("s%p", menu);
345                                 tmp = cprint_name("(%s)", sym_get_string_value(sym));
346                                 tmp = indent - tmp + 4;
347                                 if (tmp < 0)
348                                         tmp = 0;
349                                 cprint_name("%*c%s%s", tmp, ' ', menu_get_prompt(menu),
350                                         (sym_has_value(sym) || !sym_is_changable(sym)) ?
351                                         "" : " (NEW)");
352                                 goto conf_childs;
353                         }
354                 }
355                 cprint_name("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu),
356                         (sym_has_value(sym) || !sym_is_changable(sym)) ?
357                         "" : " (NEW)");
358                 if (menu->prompt->type == P_MENU) {
359                         cprint_name("  --->");
360                         return;
361                 }
362         }
363
364 conf_childs:
365         indent += doint;
366         for (child = menu->list; child; child = child->next)
367                 build_conf(child);
368         indent -= doint;
369 }
370
371 static void conf(struct menu *menu)
372 {
373         struct dialog_list_item *active_item = NULL;
374         struct menu *submenu;
375         const char *prompt = menu_get_prompt(menu);
376         struct symbol *sym;
377         char active_entry[40];
378         int stat, type;
379
380         unlink("lxdialog.scrltmp");
381         active_entry[0] = 0;
382         while (1) {
383                 indent = 0;
384                 child_count = 0;
385                 current_menu = menu;
386                 cdone(); cinit();
387                 build_conf(menu);
388                 if (!child_count)
389                         break;
390                 if (menu == &rootmenu) {
391                         cmake(); cprint_tag(":"); cprint_name("--- ");
392                         cmake(); cprint_tag("L"); cprint_name("Load an Alternate Configuration File");
393                         cmake(); cprint_tag("S"); cprint_name("Save Configuration to an Alternate File");
394                 }
395                 dialog_clear();
396                 stat = dialog_menu(prompt ? prompt : "Main Menu",
397                                 menu_instructions, rows, cols, rows - 10,
398                                 active_entry, item_no, items);
399                 if (stat < 0)
400                         return;
401
402                 if (stat == 1 || stat == 255)
403                         break;
404
405                 active_item = first_sel_item(item_no, items);
406                 if (!active_item)
407                         continue;
408                 active_item->selected = 0;
409                 strncpy(active_entry, active_item->tag, sizeof(active_entry));
410                 active_entry[sizeof(active_entry)-1] = 0;
411                 type = active_entry[0];
412                 if (!type)
413                         continue;
414
415                 sym = NULL;
416                 submenu = NULL;
417                 if (sscanf(active_entry + 1, "%p", &submenu) == 1)
418                         sym = submenu->sym;
419
420                 switch (stat) {
421                 case 0:
422                         switch (type) {
423                         case 'm':
424                                 if (single_menu_mode)
425                                         submenu->data = (void *) (long) !submenu->data;
426                                 else
427                                         conf(submenu);
428                                 break;
429                         case 't':
430                                 if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)
431                                         conf_choice(submenu);
432                                 else if (submenu->prompt->type == P_MENU)
433                                         conf(submenu);
434                                 break;
435                         case 's':
436                                 conf_string(submenu);
437                                 break;
438                         case 'L':
439                                 conf_load();
440                                 break;
441                         case 'S':
442                                 conf_save();
443                                 break;
444                         }
445                         break;
446                 case 2:
447                         if (sym)
448                                 show_help(submenu);
449                         else
450                                 show_readme();
451                         break;
452                 case 3:
453                         if (type == 't') {
454                                 if (sym_set_tristate_value(sym, yes))
455                                         break;
456                                 if (sym_set_tristate_value(sym, mod))
457                                         show_textbox(NULL, setmod_text, 6, 74);
458                         }
459                         break;
460                 case 4:
461                         if (type == 't')
462                                 sym_set_tristate_value(sym, no);
463                         break;
464                 case 5:
465                         if (type == 't')
466                                 sym_set_tristate_value(sym, mod);
467                         break;
468                 case 6:
469                         if (type == 't')
470                                 sym_toggle_tristate_value(sym);
471                         else if (type == 'm')
472                                 conf(submenu);
473                         break;
474                 }
475         }
476 }
477
478 static void show_textbox(const char *title, const char *text, int r, int c)
479 {
480         int fd;
481
482         fd = creat(".help.tmp", 0777);
483         write(fd, text, strlen(text));
484         close(fd);
485         while (dialog_textbox(title, ".help.tmp", r, c) < 0)
486                 ;
487         unlink(".help.tmp");
488 }
489
490 static void show_helptext(const char *title, const char *text)
491 {
492         show_textbox(title, text, rows, cols);
493 }
494
495 static void show_help(struct menu *menu)
496 {
497         const char *help;
498         char *helptext;
499         struct symbol *sym = menu->sym;
500
501         help = sym->help;
502         if (!help)
503                 help = nohelp_text;
504         if (sym->name) {
505                 helptext = malloc(strlen(sym->name) + strlen(help) + 16);
506                 sprintf(helptext, "%s:\n\n%s", sym->name, help);
507                 show_helptext(menu_get_prompt(menu), helptext);
508                 free(helptext);
509         } else
510                 show_helptext(menu_get_prompt(menu), help);
511 }
512
513 static void show_readme(void)
514 {
515         show_helptext("Help", top_menu_help);
516 }
517
518 static void conf_choice(struct menu *menu)
519 {
520         const char *prompt = menu_get_prompt(menu);
521         struct menu *child;
522         struct symbol *active;
523
524         active = sym_get_choice_value(menu->sym);
525         while (1) {
526                 current_menu = menu;
527                 cdone(); cinit();
528                 for (child = menu->list; child; child = child->next) {
529                         if (!menu_is_visible(child))
530                                 continue;
531                         cmake();
532                         cprint_tag("%p", child);
533                         cprint_name("%s", menu_get_prompt(child));
534                         if (child->sym == sym_get_choice_value(menu->sym))
535                                 items[item_no - 1]->selected = 1; /* ON */
536                         else if (child->sym == active)
537                                 items[item_no - 1]->selected = 2; /* SELECTED */
538                         else
539                                 items[item_no - 1]->selected = 0; /* OFF */
540                 }
541
542                 switch (dialog_checklist(prompt ? prompt : "Main Menu",
543                                         radiolist_instructions, 15, 70, 6,
544                                         item_no, items, FLAG_RADIO)) {
545                 case 0:
546                         if (sscanf(first_sel_item(item_no, items)->tag, "%p", &child) != 1)
547                                 break;
548                         sym_set_tristate_value(child->sym, yes);
549                         return;
550                 case 1:
551                         if (sscanf(first_sel_item(item_no, items)->tag, "%p", &child) == 1) {
552                                 show_help(child);
553                                 active = child->sym;
554                         } else
555                                 show_help(menu);
556                         break;
557                 case 255:
558                         return;
559                 }
560         }
561 }
562
563 static void conf_string(struct menu *menu)
564 {
565         const char *prompt = menu_get_prompt(menu);
566
567         while (1) {
568                 char *heading;
569
570                 switch (sym_get_type(menu->sym)) {
571                 case S_INT:
572                         heading = (char *) inputbox_instructions_int;
573                         break;
574                 case S_HEX:
575                         heading = (char *) inputbox_instructions_hex;
576                         break;
577                 case S_STRING:
578                         heading = (char *) inputbox_instructions_string;
579                         break;
580                 default:
581                         heading = "Internal mconf error!";
582                         /* panic? */;
583                 }
584
585                 switch (dialog_inputbox(prompt ? prompt : "Main Menu",
586                                         heading, 10, 75,
587                                         sym_get_string_value(menu->sym))) {
588                 case 0:
589                         if (sym_set_string_value(menu->sym, dialog_input_result))
590                                 return;
591                         show_textbox(NULL, "You have made an invalid entry.", 5, 43);
592                         break;
593                 case 1:
594                         show_help(menu);
595                         break;
596                 case 255:
597                         return;
598                 }
599         }
600 }
601
602 static void conf_load(void)
603 {
604         while (1) {
605                 switch (dialog_inputbox(NULL, load_config_text, 11, 55,
606                                         filename)) {
607                 case 0:
608                         if (!dialog_input_result[0])
609                                 return;
610                         if (!conf_read(dialog_input_result))
611                                 return;
612                         show_textbox(NULL, "File does not exist!", 5, 38);
613                         break;
614                 case 1:
615                         show_helptext("Load Alternate Configuration", load_config_help);
616                         break;
617                 case 255:
618                         return;
619                 }
620         }
621 }
622
623 static void conf_save(void)
624 {
625         while (1) {
626                 switch (dialog_inputbox(NULL, save_config_text, 11, 55,
627                                         filename)) {
628                 case 0:
629                         if (!dialog_input_result[0])
630                                 return;
631                         if (!conf_write(dialog_input_result))
632                                 return;
633                         show_textbox(NULL, "Can't create file!  Probably a nonexistent directory.", 5, 60);
634                         break;
635                 case 1:
636                         show_helptext("Save Alternate Configuration", save_config_help);
637                         break;
638                 case 255:
639                         return;
640                 }
641         }
642 }
643
644 static void conf_cleanup(void)
645 {
646         tcsetattr(1, TCSAFLUSH, &ios_org);
647         unlink(".help.tmp");
648 }
649
650 static void winch_handler(int sig)
651 {
652         struct winsize ws;
653
654         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
655                 rows = 24;
656                 cols = 80;
657         } else {
658                 rows = ws.ws_row;
659                 cols = ws.ws_col;
660         }
661
662         if (rows < 19 || cols < 80) {
663                 end_dialog();
664                 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
665                 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
666                 exit(1);
667         }
668
669         rows -= 4;
670         cols -= 5;
671
672 }
673
674 int main(int ac, char **av)
675 {
676         int stat;
677         char *mode;
678         struct symbol *sym;
679
680         conf_parse(av[1]);
681         conf_read(NULL);
682
683         sym = sym_lookup("VERSION", 0);
684         sym_calc_value(sym);
685         snprintf(menu_backtitle, 128, "Buildroot v%s Configuration",
686                 sym_get_string_value(sym));
687
688         mode = getenv("MENUCONFIG_MODE");
689         if (mode) {
690                 if (!strcasecmp(mode, "single_menu"))
691                         single_menu_mode = 1;
692         }
693
694         tcgetattr(1, &ios_org);
695         atexit(conf_cleanup);
696         init_wsize();
697         init_dialog();
698         signal(SIGWINCH, winch_handler);
699         conf(&rootmenu);
700         end_dialog();
701
702         /* Restart dialog to act more like when lxdialog was still separate */
703         init_dialog();
704         do {
705                 stat = dialog_yesno(NULL,
706                                 "Do you wish to save your new Buildroot configuration?", 5, 60);
707         } while (stat < 0);
708         end_dialog();
709
710         if (stat == 0) {
711                 conf_write(NULL);
712                 printf("\n\n"
713                         "*** End of Buildroot configuration.\n"
714                         "*** Check the top-level Makefile for additional configuration options.\n\n");
715         } else
716                 printf("\n\nYour Buildroot configuration changes were NOT saved.\n\n");
717
718         return 0;
719 }