3 * Generic menu system module.
5 * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology.
6 * For copying and distribution information, see the file
10 #include <mit-copyright.h>
32 #define getchar() _getch()
33 #define getpid _getpid
42 #define MAX(A, B) ((A) > (B) ? (A) : (B))
43 #define MIN(A, B) ((A) < (B) ? (A) : (B))
44 #define CTL(ch) ((ch) & 037)
46 #define MIN_INPUT 2 /* Minimum number of lines for input window */
48 extern int interrupt; /* will be set if ^C is received */
51 FILE *log_file = (FILE *) NULL; /* file stream of log file */
54 /* Structure for holding current displayed menu */
57 WINDOW *ms_screen; /* Window for this menu */
58 WINDOW *ms_title; /* Title subwindow */
59 WINDOW *ms_menu; /* Menu subwindow */
60 WINDOW *ms_input; /* Input subwindow */
61 #endif /* HAVE_CURSES */
62 int ms_input_y; /* Input subwindow reference coordinate */
69 #define NULLMS ((struct menu_screen *) 0)
71 Menu *top_menu; /* Root for command search */
72 int parsed_argc; /* used by extern routines to get additional */
73 char **parsed_argv; /* comand line input */
75 int Parse_words(char *buf, char *argv[], int n);
76 void refresh_ms(struct menu_screen *ms);
77 void Put_line(char *msg);
78 void menu_com_err_hook(const char *who, long code,
79 const char *fmt, va_list ap);
80 struct menu_screen *make_ms(int length);
81 void destroy_ms(struct menu_screen *ms);
82 struct menu_line *find_command_from(char *c, struct menu *m, int d);
83 struct menu_line *Find_command(Menu *m, char *command);
84 int toggle_logging(int argc, char *argv[]);
87 * Hook function to cause error messages to be printed through
88 * curses instead of around it.
91 void menu_com_err_hook(const char *who, long code, const char *fmt, va_list ap)
93 char buf[BUFSIZ], *cp;
98 for (cp = buf; *cp; cp++)
110 strcpy(cp, error_message(code));
114 vsprintf(cp, fmt, ap);
120 * Start_menu takes a menu as an argument. It initializes curses u.s.w.,
121 * and a quit in any submenu should unwind back to here. (it might not,
122 * if user functions which run their own menus don't cooperate.)
123 * Start_menu should only be called once, at the start of the program.
125 void Start_menu(Menu *m)
127 void (*old_hook)(const char *, long, const char *, va_list) =
128 set_com_err_hook(menu_com_err_hook);
129 #ifdef CURSES_HAS_NEWTERM
130 SCREEN *scrn = newterm(NULL, stdout, stdin);
132 WINDOW *scrn = initscr();
136 fputs("Can't initialize curses!\nReverting to -nomenu mode\n\n", stderr);
141 #ifdef CURSES_HAS_NEWTERM
146 raw(); /* We parse & print everything ourselves */
148 cur_ms = make_ms(0); /* So we always have some current */
151 Do_menu(m, -1, NULL);
154 set_com_err_hook(old_hook);
157 void Cleanup_menu(void)
161 wclear(cur_ms->ms_screen);
162 wrefresh(cur_ms->ms_screen);
168 * Create a new menu screen template with the specified menu length
171 struct menu_screen *make_ms(int length)
173 struct menu_screen *ms;
175 if (MAX_TITLE + length + MIN_INPUT > LINES)
177 fputs("Menu too big!\n", stderr);
181 ms = malloc(sizeof(struct menu_screen));
183 ms->ms_screen = newwin(0, 0, 0, 0);
184 ms->ms_title = subwin(ms->ms_screen, MAX_TITLE, 0, 0, 0);
185 ms->ms_menu = subwin(ms->ms_screen, length, 0, MAX_TITLE, 0);
186 ms->ms_input = subwin(ms->ms_screen, 0, 0,
187 ms->ms_input_y = MAX_TITLE + length,
190 scrollok(ms->ms_input, TRUE);
191 wmove(ms->ms_input, 0, 0);
192 wclear(ms->ms_screen);
198 * This routine destroys a menu_screen.
200 void destroy_ms(struct menu_screen *ms)
202 delwin(ms->ms_title);
204 delwin(ms->ms_input);
205 delwin(ms->ms_screen);
208 #endif /* HAVE_CURSES */
210 /* Like Start_menu, except it doesn't print menus and doesn't use curses */
211 void Start_no_menu(Menu *m)
216 Do_menu(m, -1, NULL);
220 * This guy actually puts up the menu
221 * Note: if margc < 0, no 'r' option will be displayed (i.e., on the
224 int Do_menu(Menu *m, int margc, char *margv[])
226 struct menu_screen *my_ms = NULLMS, *old_cur_ms = NULLMS;
227 char argvals[MAX_ARGC][MAX_ARGLEN]; /* This is where args are stored */
228 char buf[MAX_ARGC * MAX_ARGLEN];
229 char *argv[MAX_ARGC];
232 struct menu_line *command;
234 int quitflag, is_topmenu = (margc < 0);
236 /* Entry function gets called with old menu_screen still current */
237 if (m->m_entry != NULLFUNC)
239 if (m->m_entry(m, margc, margv) == DM_QUIT)
243 margc = parsed_argc + 1;
244 margv = --parsed_argv;
256 /* The following get run only in curses mode */
257 if (cur_ms != NULLMS)
259 /* Get a menu_screen */
261 /* 2 is for the 2 obligatory lines; quit and toggle */
262 cur_ms = my_ms = make_ms(m->m_length + 2 + (is_topmenu ? 0 : 1));
264 /* Now print the title and the menu */
265 wclear(my_ms->ms_screen);
266 wrefresh(my_ms->ms_screen);
267 wmove(my_ms->ms_title, 0, MAX(0, (COLS - (int)strlen(m->m_title)) >> 1));
268 wstandout(my_ms->ms_title);
269 waddstr(my_ms->ms_title, m->m_title);
270 wstandend(my_ms->ms_title);
272 for (line = 0; line < m->m_length; line++)
274 int len = strlen(m->m_lines[line].ml_command);
278 wmove(my_ms->ms_menu, line, 0);
280 wprintw(my_ms->ms_menu, "%2d. (%s)%*s %s.", line + 1,
281 m->m_lines[line].ml_command, 12 - len, "",
282 m->m_lines[line].ml_doc);
284 wmove(my_ms->ms_menu, line++, 0);
287 waddstr(my_ms->ms_menu,
288 " r. (return) Return to previous menu.");
289 wmove(my_ms->ms_menu, line++, 0);
291 waddstr(my_ms->ms_menu, " t. (toggle) Toggle logging on and off.");
292 wmove(my_ms->ms_menu, line, 0);
293 waddstr(my_ms->ms_menu, " q. (quit) Quit.");
296 #endif /* HAVE_CURSES */
298 Put_message(m->m_title);
299 for (line = 0; line < m->m_length; line++)
301 sprintf(buf, "%2d. (%s)%*s %s.", line + 1,
302 m->m_lines[line].ml_command,
303 12 - strlen(m->m_lines[line].ml_command), "",
304 m->m_lines[line].ml_doc);
308 Put_message(" r. (return) Return to previous menu.");
309 Put_message(" t. (toggle) Toggle logging on and off.");
310 Put_message(" q. (quit) Quit.");
311 Put_message(" ?. Print this information.");
316 /* This will be set by a return val from func or submenu */
317 quitflag = DM_NORMAL;
319 /* This is here because we may be coming from another menu */
322 touchwin(my_ms->ms_screen);
323 wrefresh(my_ms->ms_screen);
325 #endif /* HAVE_CURSES */
328 /* Initialize argv */
329 for (argc = 0; argc < MAX_ARGC; argc++)
330 argv[argc] = argvals[argc];
332 for (i = 1; i < margc; i++)
333 strcpy(argvals[i - 1], margv[i]);
339 if (!Prompt_input("Command: ", buf, sizeof(buf)))
341 if (cur_ms == NULLMS && feof(stdin))
342 sprintf(buf, "quit");
346 /* Parse it into the argument list */
347 /* If there's nothing there, try again */
348 /* Initialize argv */
349 for (argc = 0; argc < MAX_ARGC; argc++)
350 argv[argc] = argvals[argc];
352 if ((argc = Parse_words(buf, argv, MAX_ARGLEN)) == 0)
355 if ((line = atoi(argv[0])) > 0 && line <= m->m_length)
356 command = &m->m_lines[line - 1];
357 else if ((!is_topmenu &&
358 (!strcmp(argv[0], "r") || !strcmp(argv[0], "return"))) ||
359 !strcmp(argv[0], "q") || !strcmp(argv[0], "quit"))
361 /* here if it's either return or quit */
363 if (cur_ms != NULLMS)
368 #endif /* HAVE_CURSES */
369 if (m->m_exit != NULLFUNC)
371 return *argv[0] == 'r' ? DM_NORMAL : DM_QUIT;
373 else if (argv[0][0] == '?')
375 for (line = 0; line < m->m_length; line++)
377 sprintf(buf, "%2d. (%s)%*s %s.", line + 1,
378 m->m_lines[line].ml_command,
379 12 - strlen(m->m_lines[line].ml_command), "",
380 m->m_lines[line].ml_doc);
384 Put_message(" r. (return) Return to previous menu.");
385 Put_message(" t. (toggle) Toggle logging on and off.");
386 Put_message(" q. (quit) Quit.");
389 else if (!strcmp(argv[0], "t") || !strcmp(argv[0], "toggle"))
391 toggle_logging(argc, argv);
394 /* finally, try to find it using Find_command */
395 else if (!(command = Find_command(m, argvals[0])))
397 sprintf(buf, "Command not recognized: %s\n", argvals[0]);
401 /* If we got to here, command is a valid menu_line */
402 /* Send the offical command name into the argv */
403 strcpy(argvals[0], command->ml_command);
404 /* Show that we're working on it */
405 Put_message(command->ml_doc);
406 /* Print args that we've already got */
407 for (i = 1; i < argc; i++)
409 if (!command->ml_args[i].ma_prompt)
411 sprintf(buf, "%s%s", command->ml_args[i].ma_prompt, argv[i]);
414 /* Get remaining arguments, if any */
415 for (; argc < command->ml_argc; argc++)
417 if (!Prompt_input(command->ml_args[argc].ma_prompt,
418 argvals[argc], sizeof(argvals[argc])))
421 parsed_argc = argc - command->ml_argc;
422 parsed_argv = &(argv[command->ml_argc]);
423 if (command->ml_function != NULLFUNC)
425 /* If it's got a function, call it */
426 quitflag = command->ml_function(argc, argv);
428 else if (command->ml_submenu != NULLMENU)
430 /* Else see if it is a submenu */
431 quitflag = Do_menu(command->ml_submenu, argc, argv);
435 /* If it's got neither, something is wrong */
436 Put_message("*INTERNAL ERROR: NO FUNCTION OR MENU FOR LINE*");
438 if (quitflag == DM_QUIT)
441 if (cur_ms != NULLMS)
446 #endif /* HAVE_CURSES */
447 if (m->m_exit != NULLFUNC)
457 void refresh_screen(void)
460 if (cur_ms != NULLMS)
462 touchwin(cur_ms->ms_screen);
465 #endif /* HAVE_CURSES */
469 /* Prompt the user for input in the input window of cur_ms */
470 int Prompt_input(char *prompt, char *buf, int buflen)
475 int y, x, oldx, oldy;
477 if (cur_ms != NULLMS)
480 getyx(cur_ms->ms_input, y, x);
481 wmove(cur_ms->ms_input, y, 0);
484 waddstr(cur_ms->ms_input, prompt);
485 getyx(cur_ms->ms_input, y, x);
492 wmove(cur_ms->ms_input, y, x);
493 touchwin(cur_ms->ms_screen);
494 wclrtoeol(cur_ms->ms_input);
495 wrefresh(cur_ms->ms_input);
496 c = getchar() & 0x7f;
503 kill(getpid(), SIGTSTP);
504 touchwin(cur_ms->ms_screen);
507 wclear(cur_ms->ms_input);
508 wmove(cur_ms->ms_input, 0, 0);
509 waddstr(cur_ms->ms_input, prompt);
510 touchwin(cur_ms->ms_input);
511 wrefresh(cur_ms->ms_screen);
512 getyx(cur_ms->ms_input, y, x);
521 /* these should be obtained by doing ioctl() on tty */
530 wmove(cur_ms->ms_input, y, 0);
531 wclrtoeol(cur_ms->ms_input);
533 x = getmaxx(cur_ms->ms_input) - 1;
545 /* (buflen - 1) leaves room for the \0 */
546 if (isprint(c) && (p - buf < buflen - 1))
548 waddch(cur_ms->ms_input, c);
551 if (x >= getmaxx(cur_ms->ms_input))
563 waddch(cur_ms->ms_input, '\n');
565 wclrtoeol(cur_ms->ms_input);
569 strcpy(buf, strtrim(buf));
573 #endif /* HAVE_CURSES */
577 printf("%s", prompt);
578 if (!fgets(bigbuf, BUFSIZ, stdin))
586 strncpy(buf, strtrim(bigbuf), buflen);
587 if (strchr(buf, '\n'))
588 *strchr(buf, '\n') = '\0';
590 buf[buflen - 1] = '\0';
598 /* This routine will cause the most recently put message to be the
599 one at the top of the screen when a ---More--- prompt is displayed */
600 void Start_paging(void)
603 if (cur_ms != NULLMS)
604 lines_left = LINES - cur_ms->ms_input_y - 1;
606 #endif /* HAVE_CURSES */
610 /* Turn off paging */
611 void Stop_paging(void)
616 /* Print a message in the input window of cur_ms. */
617 void Put_message(char *msg)
619 char *copy, *line, *s;
625 /* if we're doing logging; we assume that the file has already
627 fprintf(log_file, "%s\n", msg);
633 if (s - line >= COLS - 1)
635 strncpy(copy, line, COLS - 1);
636 copy[COLS - 1] = '\0';
653 /* Will be truncated to COLS characters. */
654 void Put_line(char *msg)
661 if (--lines_left == 0)
663 /* Give the user a more prompt */
665 if (cur_ms != NULLMS)
670 wstandout(cur_ms->ms_input);
671 wprintw(cur_ms->ms_input, "---More---");
672 wstandend(cur_ms->ms_input);
674 chr = getchar() & 0x7f;/* We do care what it is */
675 if (chr == 'q' || chr == 'Q' || chr == 3 /* ^C */)
680 getyx(cur_ms->ms_input, y, x);
681 /* x is a bitbucket; avoid lint problems */
683 wmove(cur_ms->ms_input, y, 0);
684 wclrtoeol(cur_ms->ms_input);
687 #endif /* HAVE_CURSES */
689 printf("---More (hit return)---");
692 Start_paging(); /* Reset lines_left */
697 if (cur_ms != NULLMS)
702 msg1 = calloc(COLS, 1);
703 strncpy(msg1, msg, COLS - 1);
704 for (i = strlen(msg1); i < COLS - 1; i++)
706 wprintw(cur_ms->ms_input, "%s\n", msg1);
709 #endif /* HAVE_CURSES */
714 /* Refresh a menu_screen onto the real screen */
715 void refresh_ms(struct menu_screen *ms)
717 wrefresh(ms->ms_title);
718 wrefresh(ms->ms_menu);
719 wrefresh(ms->ms_input);
721 #endif /* HAVE_CURSES */
723 /* Parse buf into a list of words, which will be placed in strings specified by
724 argv. Space for these strings must have already been allocated.
725 Only the first n characters of each word will be copied */
726 int Parse_words(char *buf, char *argv[], int n)
728 char *start, *end; /* For sausage machine */
732 for (argc = 0; argc < MAX_ARGC; argc++)
734 while (isspace(*start))
735 start++; /* Kill whitespace */
737 break; /* Nothing left */
738 /* Now find the end of the word */
739 for (end = start; *end != '\0' && !isspace(*end); end++)
741 strncpy(argv[argc], start, MIN(end - start, n)); /* Copy it */
742 argv[argc][MIN(end - start, n - 1)] = '\0'; /* Terminate */
748 /* This is the internal form of Find_command, which recursively searches
749 for a menu_line with command command in the specified menu */
750 /* It will search to a maximum depth of d */
751 struct menu_line *find_command_from(char *c, struct menu *m, int d)
754 struct menu_line *maybe;
757 return NULL; /* Too deep! */
758 for (line = 0; line < m->m_length; line++)
760 if (!strcmp(c, m->m_lines[line].ml_command))
761 return &m->m_lines[line];
763 for (line = 0; line < m->m_length; line++)
765 if (m->m_lines[line].ml_submenu != NULLMENU &&
766 (maybe = find_command_from(c, m->m_lines[line].ml_submenu, d - 1)))
769 /* If we got to here, nothing works */
773 /* Find_command searches down the current menu tree */
774 /* And returns a pointer to a menu_line with the specified command name */
775 /* It returns (struct menu_line *) 0 if none is found */
776 struct menu_line *Find_command(Menu *m, char *command)
781 return find_command_from(command, m, MAX_MENU_DEPTH);
784 static char *get_tmp_dir(void)
787 static char tmp[BUFSIZ];
791 len = GetTempPath(sizeof(tmp), tmp);
792 if (!len || (len > sizeof(tmp)))
801 int toggle_logging(int argc, char *argv[])
807 sprintf(buf, "%s/%s-log.%ld", get_tmp_dir(), whoami, (long)getpid());
810 log_file = fopen(buf, "a");
813 Put_message("Open of log file failed. Logging is not on.");
815 Put_message("Log file successfully opened.");
818 { /* log_file is a valid pointer; turn off logging. */
822 Put_message("Logging off.");