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
31static const char menu_instructions[] =
32	"Arrow keys navigate the menu.  "
33	"<Enter> selects submenus --->.  "
34	"Highlighted letters are hotkeys.  "
35	"Pressing <Y> selectes a feature, while <N> will exclude a feature.  "
36	"Press <Esc><Esc> to exit, <?> for Help.  "
37	"Legend: [*] feature is selected  [ ] feature is excluded",
38radiolist_instructions[] =
39	"Use the arrow keys to navigate this window or "
40	"press the hotkey of the item you wish to select "
41	"followed by the <SPACE BAR>. "
42	"Press <?> for additional information about this option.",
43inputbox_instructions_int[] =
44	"Please enter a decimal value. "
45	"Fractions will not be accepted.  "
46	"Use the <TAB> key to move from the input field to the buttons below it.",
47inputbox_instructions_hex[] =
48	"Please enter a hexadecimal value. "
49	"Use the <TAB> key to move from the input field to the buttons below it.",
50inputbox_instructions_string[] =
51	"Please enter a string value. "
52	"Use the <TAB> key to move from the input field to the buttons below it.",
53setmod_text[] =
54	"This feature depends on another which has been configured as a module.\n"
55	"As a result, this feature will be built as a module.",
56nohelp_text[] =
57	"There is no help available for this option.\n",
58load_config_text[] =
59	"Enter the name of the configuration file you wish to load.  "
60	"Accept the name shown to restore the configuration you "
61	"last retrieved.  Leave blank to abort.",
62load_config_help[] =
63	"\n"
64	"For various reasons, one may wish to keep several different\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	"default, entering the name of the file here will allow you\n"
69	"to modify that configuration.\n"
70	"\n"
71	"If you are uncertain, then you have probably never used alternate\n"
72	"configuration files.  You should therefor leave this blank to abort.\n",
73save_config_text[] =
74	"Enter a filename to which this configuration should be saved "
75	"as an alternate.  Leave blank to abort.",
76save_config_help[] =
77	"\n"
78	"For various reasons, one may wish to keep different\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
89static char filename[PATH_MAX+1] = ".config";
90static int indent = 0;
91static struct termios ios_org;
92static int rows, cols;
93struct menu *current_menu;
94static int child_count;
95static int single_menu_mode;
96
97static struct dialog_list_item *items[16384];
98static int item_no;
99
100static void conf(struct menu *menu);
101static void conf_choice(struct menu *menu);
102static void conf_string(struct menu *menu);
103static void conf_load(void);
104static void conf_save(void);
105static void show_textbox(const char *title, const char *text, int r, int c);
106static void show_helptext(const char *title, const char *text);
107static void show_help(struct menu *menu);
108static void show_readme(void);
109
110static 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
132static void cinit(void)
133{
134	item_no = 0;
135}
136
137static 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
147static 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
164static 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
178static 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
191static 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
322conf_childs:
323	indent += doint;
324	for (child = menu->list; child; child = child->next)
325		build_conf(child);
326	indent -= doint;
327}
328
329static 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
434static 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
446static void show_helptext(const char *title, const char *text)
447{
448	show_textbox(title, text, rows, cols);
449}
450
451static 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
469static void show_readme(void)
470{
471	while (dialog_textbox(NULL, "config/README.Menuconfig", rows, cols) < 0)
472		;
473}
474
475static 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
511static 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
550static 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
571static 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
592static void conf_cleanup(void)
593{
594	tcsetattr(1, TCSAFLUSH, &ios_org);
595	unlink(".help.tmp");
596	unlink("lxdialog.scrltmp");
597}
598
599static 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
623int 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, "Tomato Configuration");
636
637	mode = getenv("MENUCONFIG_MODE");
638	if (mode) {
639		if (!strcasecmp(mode, "single_menu"))
640			single_menu_mode = 1;
641	}
642
643	tcgetattr(1, &ios_org);
644	atexit(conf_cleanup);
645	init_wsize();
646	init_dialog();
647	signal(SIGWINCH, winch_handler);
648	conf(&rootmenu);
649	end_dialog();
650
651	/* Restart dialog to act more like when lxdialog was still separate */
652	init_dialog();
653	do {
654		stat = dialog_yesno(NULL,
655				"Do you wish to save your new configuration?", 5, 60);
656	} while (stat < 0);
657	end_dialog();
658
659	if (stat == 0) {
660		conf_write(NULL);
661		printf("\n\n"
662			"*** End of configuration.\n"
663			"*** Check the top-level Makefile for additional configuration options.\n\n");
664	} else
665		printf("\n\nYour configuration changes were NOT saved.\n\n");
666
667	return 0;
668}
669