Initial revision
[openwrt.git] / 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                 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         active = sym_get_choice_value(menu->sym);
519         while (1) {
520                 current_menu = menu;
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                         if (child->sym == sym_get_choice_value(menu->sym))
529                                 items[item_no - 1]->selected = 1; /* ON */
530                         else if (child->sym == active)
531                                 items[item_no - 1]->selected = 2; /* SELECTED */
532                         else
533                                 items[item_no - 1]->selected = 0; /* OFF */
534                 }
535
536                 switch (dialog_checklist(prompt ? prompt : "Main Menu",
537                                         radiolist_instructions, 15, 70, 6,
538                                         item_no, items, FLAG_RADIO)) {
539                 case 0:
540                         if (sscanf(first_sel_item(item_no, items)->tag, "%p", &child) != 1)
541                                 break;
542                         sym_set_tristate_value(child->sym, yes);
543                         return;
544                 case 1:
545                         if (sscanf(first_sel_item(item_no, items)->tag, "%p", &child) == 1) {
546                                 show_help(child);
547                                 active = child->sym;
548                         } else
549                                 show_help(menu);
550                         break;
551                 case 255:
552                         return;
553                 }
554         }
555 }
556
557 static void conf_string(struct menu *menu)
558 {
559         const char *prompt = menu_get_prompt(menu);
560
561         while (1) {
562                 char *heading;
563
564                 switch (sym_get_type(menu->sym)) {
565                 case S_INT:
566                         heading = (char *) inputbox_instructions_int;
567                         break;
568                 case S_HEX:
569                         heading = (char *) inputbox_instructions_hex;
570                         break;
571                 case S_STRING:
572                         heading = (char *) inputbox_instructions_string;
573                         break;
574                 default:
575                         heading = "Internal mconf error!";
576                         /* panic? */;
577                 }
578
579                 switch (dialog_inputbox(prompt ? prompt : "Main Menu",
580                                         heading, 10, 75,
581                                         sym_get_string_value(menu->sym))) {
582                 case 0:
583                         if (sym_set_string_value(menu->sym, dialog_input_result))
584                                 return;
585                         show_textbox(NULL, "You have made an invalid entry.", 5, 43);
586                         break;
587                 case 1:
588                         show_help(menu);
589                         break;
590                 case 255:
591                         return;
592                 }
593         }
594 }
595
596 static void conf_load(void)
597 {
598         while (1) {
599                 switch (dialog_inputbox(NULL, load_config_text, 11, 55,
600                                         filename)) {
601                 case 0:
602                         if (!dialog_input_result[0])
603                                 return;
604                         if (!conf_read(dialog_input_result))
605                                 return;
606                         show_textbox(NULL, "File does not exist!", 5, 38);
607                         break;
608                 case 1:
609                         show_helptext("Load Alternate Configuration", load_config_help);
610                         break;
611                 case 255:
612                         return;
613                 }
614         }
615 }
616
617 static void conf_save(void)
618 {
619         while (1) {
620                 switch (dialog_inputbox(NULL, save_config_text, 11, 55,
621                                         filename)) {
622                 case 0:
623                         if (!dialog_input_result[0])
624                                 return;
625                         if (!conf_write(dialog_input_result))
626                                 return;
627                         show_textbox(NULL, "Can't create file!  Probably a nonexistent directory.", 5, 60);
628                         break;
629                 case 1:
630                         show_helptext("Save Alternate Configuration", save_config_help);
631                         break;
632                 case 255:
633                         return;
634                 }
635         }
636 }
637
638 static void conf_cleanup(void)
639 {
640         tcsetattr(1, TCSAFLUSH, &ios_org);
641         unlink(".help.tmp");
642 }
643
644 static void winch_handler(int sig)
645 {
646         struct winsize ws;
647
648         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
649                 rows = 24;
650                 cols = 80;
651         } else {
652                 rows = ws.ws_row;
653                 cols = ws.ws_col;
654         }
655
656         if (rows < 19 || cols < 80) {
657                 end_dialog();
658                 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
659                 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
660                 exit(1);
661         }
662
663         rows -= 4;
664         cols -= 5;
665
666 }
667
668 int main(int ac, char **av)
669 {
670         int stat;
671         char *mode;
672         struct symbol *sym;
673
674         conf_parse(av[1]);
675         conf_read(NULL);
676
677         sym = sym_lookup("VERSION", 0);
678         sym_calc_value(sym);
679         snprintf(menu_backtitle, 128, "Buildroot v%s Configuration",
680                 sym_get_string_value(sym));
681
682         mode = getenv("MENUCONFIG_MODE");
683         if (mode) {
684                 if (!strcasecmp(mode, "single_menu"))
685                         single_menu_mode = 1;
686         }
687
688         tcgetattr(1, &ios_org);
689         atexit(conf_cleanup);
690         init_wsize();
691         init_dialog();
692         signal(SIGWINCH, winch_handler);
693         conf(&rootmenu);
694         end_dialog();
695
696         /* Restart dialog to act more like when lxdialog was still separate */
697         init_dialog();
698         do {
699                 stat = dialog_yesno(NULL,
700                                 "Do you wish to save your new Buildroot configuration?", 5, 60);
701         } while (stat < 0);
702         end_dialog();
703
704         if (stat == 0) {
705                 conf_write(NULL);
706                 printf("\n\n"
707                         "*** End of Buildroot configuration.\n"
708                         "*** Check the top-level Makefile for additional configuration options.\n\n");
709         } else
710                 printf("\n\nYour Buildroot configuration changes were NOT saved.\n\n");
711
712         return 0;
713 }