2 * Copyright 1987 by the Massachusetts Institute of Technology.
3 * For copying and distribution information, see the file
10 * Generic menu system module.
12 * Basically, we define an enormous tree structure which represents the
13 * menu. Some extra pieces (ml_command, ma_doc) get thrown in so we can
14 * also use the structure for a command-based system.
16 * By making the menu descriptions so general, we can ease porting to just
21 static char rcsid_menu_c[] = "$Header$";
25 #include <mit-copyright.h>
26 #include <sys/types.h>
31 #include <sys/termio.h>
32 #include <sys/ttychars.h>
33 #endif /* _AUX_SOURCE */
44 #define MAX(A,B) ((A) > (B) ? (A) : (B))
45 #define MIN(A,B) ((A) < (B) ? (A) : (B))
46 #define CTL(ch) ((ch) & 037)
48 #define MIN_INPUT 2 /* Minimum number of lines for input window */
50 extern int interrupt; /* will be set if ^C is received */
51 extern FILE *fdopen();
53 extern char *calloc();
56 FILE *log_file = (FILE *) NULL; /* file stream of log file */
59 /* Structure for holding current displayed menu */
61 WINDOW *ms_screen; /* Window for this menu */
62 WINDOW *ms_title; /* Title subwindow */
63 WINDOW *ms_menu; /* Menu subwindow */
64 WINDOW *ms_input; /* Input subwindow */
65 int ms_input_y; /* Input subwindow reference coordinate */
68 #define NULLMS ((struct menu_screen *) 0)
70 Menu *top_menu; /* Root for command search */
71 int parsed_argc; /* used by extern routines to get additional */
72 char **parsed_argv; /* comand line input */
75 * Hook function to cause error messages to be printed through
76 * curses instead of around it. Takes at most 5 args to the
77 * printf string (crock...)
81 menu_com_err_hook(who, code, fmt, arg1, arg2, arg3, arg4, arg5)
85 caddr_t arg1, arg2, arg3, arg4, arg5;
87 char buf[BUFSIZ], *cp;
89 (void) strcpy(buf, who);
90 for (cp = buf; *cp; cp++);
94 (void) strcpy(cp, error_message(code));
98 sprintf(cp, fmt, arg1, arg2, arg3, arg4, arg5);
103 * Start_menu takes a menu as an argument. It initializes curses u.s.w.,
104 * and a quit in any submenu should unwind back to here. (it might not,
105 * if user functions which run their own menus don't cooperate.)
106 * Start_menu should only be called once, at the start of the program.
111 struct menu_screen *make_ms();
113 register void (*old_hook)(const char *, long, const char *, va_list) = set_com_err_hook(menu_com_err_hook);
115 register void (*old_hook)() = set_com_err_hook(menu_com_err_hook);
118 if (initscr() == ERR) {
119 fputs("Can't initialize curses!\n", stderr);
122 (void) raw(); /* We parse & print everything ourselves */
124 cur_ms = make_ms(0); /* So we always have some current */
127 (void) Do_menu(m, -1, (char **) NULL);
129 (void) set_com_err_hook(old_hook);
136 (void) wclear(cur_ms->ms_screen);
137 (void) wrefresh(cur_ms->ms_screen);
143 /* Like Start_menu, except it doesn't print menus and doesn't use curses */
150 (void) Do_menu(m, -1, (char **) NULL);
154 * Create a new menu screen template with the specified menu length
161 struct menu_screen *ms;
164 if (MAX_TITLE + length + MIN_INPUT > LINES) {
165 fputs("Menu too big!\n", stderr);
169 ms = (struct menu_screen *) malloc(sizeof(struct menu_screen));
171 ms->ms_screen = newwin(0, 0, 0, 0);
172 ms->ms_title = subwin(ms->ms_screen, MAX_TITLE, 0, 0, 0);
173 ms->ms_menu = subwin(ms->ms_screen,
174 length, 0, MAX_TITLE, 0);
175 ms->ms_input = subwin(ms->ms_screen, 0, 0,
176 ms->ms_input_y = MAX_TITLE + length,
179 scrollok(ms->ms_input, TRUE);
180 (void) wmove(ms->ms_input, 0, 0);
181 (void) wclear(ms->ms_screen);
187 * This routine destroys a menu_screen.
190 struct menu_screen *ms;
192 delwin(ms->ms_title);
194 delwin(ms->ms_input);
195 delwin(ms->ms_screen);
200 * This guy actually puts up the menu
201 * Note: if margc < 0, no 'r' option will be displayed (i.e., on the
205 Do_menu(m, margc, margv)
210 struct menu_screen *my_ms = NULLMS, *old_cur_ms = NULLMS;
211 char argvals[MAX_ARGC][MAX_ARGLEN]; /* This is where args are stored */
212 char buf[MAX_ARGC * MAX_ARGLEN];
213 char *argv[MAX_ARGC];
216 struct menu_line *command, *Find_command();
218 int quitflag, is_topmenu = (margc < 0);
219 int toggle_logging();
221 /* Entry function gets called with old menu_screen still current */
222 if (m->m_entry != NULLFUNC) {
223 if (m->m_entry(m, margc, margv) == DM_QUIT)
225 if (parsed_argc > 0) {
226 margc = parsed_argc + 1;
227 margv = --parsed_argv;
236 /* The following get run only in curses mode */
237 if (cur_ms != NULLMS) {
238 /* Get a menu_screen */
240 /* 2 is for the 2 obligatory lines; quit and toggle */
241 cur_ms = my_ms = make_ms(m->m_length + 2 + (is_topmenu ? 0 : 1));
243 /* Now print the title and the menu */
244 (void) wclear(my_ms->ms_menu);
245 (void) wmove(my_ms->ms_title, 0, MAX(0, (COLS -
246 strlen(m->m_title)) >> 1));
247 (void) wstandout(my_ms->ms_title);
248 (void) waddstr(my_ms->ms_title, m->m_title);
249 (void) wstandend(my_ms->ms_title);
251 for (line = 0; line < m->m_length; line++) {
252 int len = strlen(m->m_lines[line].ml_command);
253 if (len > 12) len=12;
255 (void) wmove(my_ms->ms_menu, line, 0);
257 (void) wprintw(my_ms->ms_menu, "%2d. (%s)%*s %s.", line + 1,
258 m->m_lines[line].ml_command,
260 m->m_lines[line].ml_doc);
262 (void) wmove(my_ms->ms_menu, line++, 0);
264 (void) waddstr(my_ms->ms_menu,
265 " r. (return) Return to previous menu.");
266 (void) wmove(my_ms->ms_menu, line++, 0);
268 (void) waddstr(my_ms->ms_menu,
269 " t. (toggle) Toggle logging on and off.");
270 (void) wmove(my_ms->ms_menu, line, 0);
271 (void) waddstr(my_ms->ms_menu, " q. (quit) Quit.");
273 Put_message(m->m_title);
274 for (line = 0; line < m->m_length; line++) {
275 sprintf(buf, "%2d. (%s)%*s %s.", line + 1,
276 m->m_lines[line].ml_command,
277 12 - strlen(m->m_lines[line].ml_command), "",
278 m->m_lines[line].ml_doc);
282 Put_message(" r. (return) Return to previous menu.");
283 Put_message(" t. (toggle) Toggle logging on and off.");
284 Put_message(" q. (quit) Quit.");
285 Put_message(" ?. Print this information.");
289 /* This will be set by a return val from func or submenu */
290 quitflag = DM_NORMAL;
291 /* This is here because we may be coming from another menu */
293 touchwin(my_ms->ms_screen);
295 /* Initialize argv */
296 for (argc = 0; argc < MAX_ARGC; argc++)
297 argv[argc] = argvals[argc];
299 for (i = 1; i < margc; i++)
300 strcpy(argvals[i - 1], margv[i]);
304 if (!Prompt_input("Command: ", buf, sizeof(buf))) {
305 if (cur_ms == NULLMS &&
307 sprintf(buf, "quit");
311 /* Parse it into the argument list */
312 /* If there's nothing there, try again */
313 /* Initialize argv */
314 for (argc = 0; argc < MAX_ARGC; argc++)
315 argv[argc] = argvals[argc];
317 if ((argc = Parse_words(buf, argv, MAX_ARGLEN)) == 0)
320 if ((line = atoi(argv[0])) > 0 && line <= m->m_length) {
321 command = &m->m_lines[line - 1];
323 else if ((!is_topmenu &&
324 (!strcmp(argv[0], "r") || !strcmp(argv[0], "return")))
325 || !strcmp(argv[0], "q") || !strcmp(argv[0], "quit")) {
327 /* here if it's either return or quit */
328 if (cur_ms != NULLMS) {
332 if (m->m_exit != NULLFUNC)
334 return (*argv[0] == 'r' ? DM_NORMAL : DM_QUIT);
336 else if (argv[0][0] == '?') {
337 for (line = 0; line < m->m_length; line++) {
338 sprintf(buf, "%2d. (%s)%*s %s.", line + 1,
339 m->m_lines[line].ml_command,
340 12 - strlen(m->m_lines[line].ml_command), "",
341 m->m_lines[line].ml_doc);
345 Put_message(" r. (return) Return to previous menu.");
346 Put_message(" t. (toggle) Toggle logging on and off.");
347 Put_message(" q. (quit) Quit.");
350 else if (!strcmp(argv[0], "t") || !strcmp(argv[0], "toggle")) {
351 toggle_logging(argc, argv);
354 /* finally, try to find it using Find_command */
355 else if ((command = Find_command(m, argvals[0])) ==
356 (struct menu_line *) 0) {
357 sprintf(buf, "Command not recognized: %s\n", argvals[0]);
361 /* If we got to here, command is a valid menu_line */
362 /* Send the offical command name into the argv */
363 (void) strcpy(argvals[0], command->ml_command);
364 /* Show that we're working on it */
365 Put_message(command->ml_doc);
366 /* Print args that we've already got */
367 for (i = 1; i < argc; i++) {
368 if (command->ml_args[i].ma_prompt == NULL)
370 (void) sprintf(buf, "%s%s", command->ml_args[i].ma_prompt,
374 /* Get remaining arguments, if any */
375 for (; argc < command->ml_argc; argc++) {
376 if (!Prompt_input(command->ml_args[argc].ma_prompt,
377 argvals[argc], sizeof(argvals[argc])))
380 parsed_argc = argc - command->ml_argc;
381 parsed_argv = &(argv[command->ml_argc]);
382 if (command->ml_function != NULLFUNC) {
383 /* If it's got a function, call it */
384 quitflag = command->ml_function(argc, argv);
386 else if (command->ml_submenu != NULLMENU) {
387 /* Else see if it is a submenu */
388 quitflag = Do_menu(command->ml_submenu, argc, argv);
391 /* If it's got neither, something is wrong */
392 Put_message("*INTERNAL ERROR: NO FUNCTION OR MENU FOR LINE*");
394 if (quitflag == DM_QUIT) {
395 if (cur_ms != NULLMS) {
399 if (m->m_exit != NULLFUNC)
411 if (cur_ms != NULLMS) {
412 touchwin(cur_ms->ms_screen);
418 /* Prompt the user for input in the input window of cur_ms */
419 int Prompt_input(prompt, buf, buflen)
426 int y, x, oldx, oldy;
428 if (cur_ms != NULLMS) {
430 getyx(cur_ms->ms_input, y, x);
431 (void) wmove(cur_ms->ms_input, y, 0);
434 (void) waddstr(cur_ms->ms_input, prompt);
435 getyx(cur_ms->ms_input, y, x);
441 (void) wmove(cur_ms->ms_input, y, x);
442 (void) touchwin(cur_ms->ms_screen);
443 (void) wclrtoeol(cur_ms->ms_input);
444 (void) wrefresh(cur_ms->ms_input);
445 c = getchar() & 0x7f;
451 (void) kill(getpid(), SIGTSTP);
455 (void) wclear(cur_ms->ms_input);
456 (void) waddstr(cur_ms->ms_input, prompt);
457 (void) touchwin(cur_ms->ms_input);
458 (void) mvcur(0, COLS - 1, LINES - 1, 0);
459 (void) wrefresh(curscr);
460 getyx(cur_ms->ms_input, y, x);
469 /* these should be obtained by doing ioctl() on tty */
476 (void) wmove(cur_ms->ms_input, y, 0);
477 (void) wclrtoeol(cur_ms->ms_input);
479 x = cur_ms->ms_input->_maxx-1;
491 /* (buflen - 1) leaves room for the \0 */
492 if (isprint(c) && (p - buf < buflen - 1)) {
493 (void) waddch(cur_ms->ms_input, c);
496 if (x >= cur_ms->ms_input->_maxx) {
506 (void) waddch(cur_ms->ms_input, '\n');
507 (void) waddch(cur_ms->ms_input, '\r');
509 (void) wclrtoeol(cur_ms->ms_input);
515 printf("%s", prompt);
516 if (gets(buf) == NULL)
526 strcpy(buf, strtrim(buf));
530 /* Prompt the user for input in the input window of cur_ms, but don't echo
531 and allow some control characters */
532 int Password_input(prompt, buf, buflen)
541 if (cur_ms != NULLMS) {
543 getyx(cur_ms->ms_input, y, x);
544 (void) wmove(cur_ms->ms_input, y, 0);
546 touchwin(cur_ms->ms_screen);
548 (void) waddstr(cur_ms->ms_input, prompt);
549 getyx(cur_ms->ms_input, y, x);
552 for (p = buf; p - buf < buflen;) {
553 (void) wmove(cur_ms->ms_input, y, x);
554 (void) wclrtoeol(cur_ms->ms_input);
556 c = getchar() & 0x7f;
561 (void) kill(getpid(), SIGTSTP);
565 (void) wclear(cur_ms->ms_input);
566 (void) waddstr(cur_ms->ms_input, prompt);
568 (void) mvcur(0, COLS - 1, LINES - 1, 0);
569 (void) wrefresh(curscr);
570 getyx(cur_ms->ms_input, y, x);
574 (void) waddch(cur_ms->ms_input, '\n');
575 (void) waddch(cur_ms->ms_input, '\r');
577 (void) wclrtoeol(cur_ms->ms_input);
601 struct termio ttybuf, nttybuf;
603 struct sgttyb ttybuf, nttybuf;
604 #endif /* _AUX_SOURCE */
605 printf("%s", prompt);
606 /* turn off echoing */
608 (void) ioctl(0, TCGETA, (char *)&ttybuf);
610 nttybuf.c_lflag &= ~ECHO;
611 (void)ioctl(0, TCSETA, (char *)&nttybuf);
612 if (gets(buf) == NULL) {
613 (void) ioctl(0, TCSETA, (char *)&ttybuf);
618 (void) ioctl(0, TCSETA, (char *)&ttybuf);
620 (void) ioctl(0, TIOCGETP, (char *)&ttybuf);
622 nttybuf.sg_flags &= ~ECHO;
623 (void)ioctl(0, TIOCSETP, (char *)&nttybuf);
624 if (gets(buf) == NULL) {
625 (void) ioctl(0, TIOCSETP, (char *)&ttybuf);
630 (void) ioctl(0, TIOCSETP, (char *)&ttybuf);
631 #endif /* _AUX_SOURCE */
641 /* This routine will cause the most recently put message to be the
642 one at the top of the screen when a ---More--- prompt is displayed */
645 if (cur_ms != NULLMS) {
646 lines_left = LINES - cur_ms->ms_input_y - 1;
653 /* Turn off paging */
659 /* Print a message in the input window of cur_ms. */
663 char *copy, *line, *s;
665 copy = (char *) malloc((u_int)COLS);
667 if (log_file) { /* if we're doing logging; we assume that the */
668 /* file has already been opened. */
669 (void) fprintf(log_file, "%s\n", msg);
674 if (s - line >= COLS-1) {
675 (void) strncpy(copy, line, COLS-1);
677 } else if (*s == '\n') {
679 (void) strcpy(copy, line);
689 /* Will be truncated to COLS characters. */
699 if (lines_left >= 0) {
700 if (--lines_left == 0) {
701 /* Give the user a more prompt */
702 if (cur_ms != NULLMS) {
703 (void) wstandout(cur_ms->ms_input);
704 (void) wprintw(cur_ms->ms_input, "---More---");
705 (void) wstandend(cur_ms->ms_input);
707 chr = getchar() & 0x7f;/* We do care what it is */
708 if (chr == 'q' || chr == 'Q' || chr == 3 /* ^C */) {
712 getyx(cur_ms->ms_input, y, x);
713 /* x is a bitbucket; avoid lint problems */
715 (void) wmove(cur_ms->ms_input, y, 0);
716 (void) wclrtoeol(cur_ms->ms_input);
719 printf("---More (hit return)---");
722 Start_paging(); /* Reset lines_left */
726 if (cur_ms != NULLMS) {
727 msg1 = (char *) calloc((u_int)COLS, 1);
728 (void) strncpy(msg1, msg, COLS-1);
729 for (i = strlen(msg1); i < COLS - 1; i++)
731 (void) wprintw(cur_ms->ms_input, "%s\n", msg1);
738 /* Refresh a menu_screen onto the real screen */
740 struct menu_screen *ms;
742 (void) wrefresh(ms->ms_title);
743 (void) wrefresh(ms->ms_menu);
744 (void) wrefresh(ms->ms_input);
747 /* Parse buf into a list of words, which will be placed in strings specified by
748 argv. Space for these strings must have already been allocated.
749 Only the first n characters of each word will be copied */
750 Parse_words(buf, argv, n)
756 char *start, *end; /* For sausage machine */
760 for (argc = 0; argc < MAX_ARGC; argc++) {
761 while (isspace(*start))
762 start++; /* Kill whitespace */
764 break; /* Nothing left */
765 /* Now find the end of the word */
766 for (end = start; *end != '\0' && !isspace(*end); end++);
767 (void) strncpy(argv[argc], start, MIN(end - start, n)); /* Copy it */
768 argv[argc][MIN(end - start, n - 1)] = '\0'; /* Terminate */
774 /* This is the internal form of Find_command, which recursively searches
775 for a menu_line with command command in the specified menu */
776 /* It will search to a maximum depth of d */
778 find_command_from(c, m, d)
784 struct menu_line *maybe;
787 return ((struct menu_line *) 0); /* Too deep! */
788 for (line = 0; line < m->m_length; line++) {
789 if (!strcmp(c, m->m_lines[line].ml_command)) {
790 return (&m->m_lines[line]);
793 for (line = 0; line < m->m_length; line++) {
794 if (m->m_lines[line].ml_submenu != NULLMENU &&
795 (maybe = find_command_from(c, m->m_lines[line].ml_submenu, d - 1))
796 != (struct menu_line *) 0) {
800 /* If we got to here, nothing works */
801 return ((struct menu_line *) 0);
804 /* Find_command searches down the current menu tree */
805 /* And returns a pointer to a menu_line with the specified command name */
806 /* It returns (struct menu_line *) 0 if none is found */
808 Find_command(m, command)
813 return ((struct menu_line *) 0);
816 return (find_command_from(command, m, MAX_MENU_DEPTH));
822 toggle_logging(argc, argv)
829 if (log_file == (FILE *) NULL) {
832 Put_message("I've lost my SENSE of DIRECTION! I have no IDEA who I AM!");
833 Put_message("My God... You've turned him into a DEMOCRAT!!");
834 Put_message(" -- Doonesbury");
836 Put_message("translation: your log file can be found in \"/usr/tmp/a.out.pid\".");
838 (void) sprintf(buf, "/usr/tmp/%s-log.%d", whoami, pid);
841 (void) sprintf(buf, "/usr/tmp/%s-log.%d", whoami, pid);
844 log_file = fopen(buf,"a");
846 if (log_file == (FILE *) NULL)
847 Put_message("Open of log file failed. Logging is not on.");
849 Put_message("Log file successfully opened.");
851 else { /* log_file is a valid pointer; turn off logging. */
852 (void) fflush(log_file);
853 (void) fclose(log_file);
854 log_file = (FILE *) NULL;
855 Put_message("Logging off.");