/* man - display and manage manual pages Author: Dick van Veen */ /* Options: * man <ar-dir>? <ar-name>? <man-page>+ * display <man-page> from <ar-name> in <ar-dir>. * man <ar-dir>? <ar-name>? * display contents of <ar-name> in <ar-dir>, * by using the <cursor-keys> and <return> a page is choosen. * * <ar-dir> is a directory name starting with a '/'. * the default directory is '/usr/man'. * * <ar-name> is a digit, when no digit is used, chapter 1 searched. */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <ctype.h> #include <termcap.h> #include <stdio.h> #ifndef INDEX_H #define INDEX_H #define MAN_DELIM '#' #define NAME_SIZE 12 /* for size in index file */ #define NAME_WIDTH 15 /* for on screen projection */ #define AR_NAME_SIZE 64 /* for file names */ #define AR_DIR "/usr/man" #define AR_NAME "/man" #define AR_INDEX "/._man" struct INDEX { long page_pos; /* position in index file */ char page_name[NAME_SIZE]; /* name of manual page */ }; extern char ar_name[AR_NAME_SIZE]; /* name of man archive file */ extern char ar_index[AR_NAME_SIZE]; /* name of index file */ extern struct INDEX *index_buf; /* contains indices */ extern int max_index_nr; /* number of indices in index_buf */ /* Should be a prototype */ extern void read_index(); #endif #ifndef DISPLAY_H #define DISPLAY_H #define MAX_X (CO/NAME_WIDTH) /* define width of screen */ #define MAX_Y (LI) /* define hight of screen */ #define STOP 256 /* codes returned by get_token */ #define READY 257 #define CURSLEFT 258 #define CURSRIGHT 259 #define CURSUP 260 #define CURSDOWN 261 #define CURSHOME 262 #define CURSEND 263 #define CURSPAGEUP 264 #define CURSPAGEDOWN 265 #define CURSMIDDLE 266 extern int term_dialog; /* do we have a terminal ? */ extern int term_clear; /* do we clear at exit ? */ extern int top_y, cur_x, cur_y; /* current position on the screen */ extern char *CL, *CM, *DL, *SO, *SE; /* for termcap */ extern int CO, LI; /* for termcap */ #define GOTOXY(x, y) fputs(tgoto(CM, (x), (y)), stdout) #define reverse(on) fputs((on)?SO:SE, stdout) #define clrscr() fputs(CL, stdout) /* Should be prototypes */ extern void display(); extern int gettoken(); extern void set_cursor(); extern void term_init(); extern void term_exit(); #endif /* Arguments for do_wait() */ #define WAIT1 "press <return> to go back to menu" #define WAIT2 "press <return> for " #define WAIT3 "press <return> for more ..." #define DO_REVERSE 0x01 /* modes for do_wait() */ #define DO_DELETE 0x02 extern char *malloc(); extern char *getenv(); /* Forward declaration: */ extern void Exit(); static void man(); static void _man(); static void choose(); static int do_choose(); static int do_wait(); main(argc, argv) int argc; char **argv; { FILE *man_fd; argv++; argc--; if (*argv != NULL && **argv == '/') { (void) strcpy(ar_name, *argv); /* get archive directory */ (void) strcpy(ar_index, *argv); /* get index directory */ argv++; argc--; } else { (void) strcpy(ar_name, AR_DIR); /* use default directory */ (void) strcpy(ar_index, AR_DIR); } (void) strcat(ar_name, AR_NAME); /* get archive name */ (void) strcat(ar_index, AR_INDEX); /* get index name */ if (*argv != NULL && isdigit(**argv)) { (void) strcat(ar_name, *argv); /* get archive name */ (void) strcat(ar_index, *argv); argv++; argc--; } else { (void) strcat(ar_name, "1"); /* default archive */ (void) strcat(ar_index, "1"); } man_fd = fopen(ar_name, "r"); /* open man archive */ if (man_fd == NULL) { fprintf(stderr, "can't open %s\n", ar_name); Exit(1); } read_index(); term_dialog = isatty(0) && isatty(1); term_init(); if (*argv == NULL) choose(man_fd); else man(man_fd, argv); Exit(0); } void Exit(code) int code; { term_exit(); /* return terminal to old status */ exit(code); } static void man(man_fd, man_pages) FILE *man_fd; char **man_pages; { /* copy all requested manual pages to * standard output */ int ch; while (1) { _man(man_fd, *man_pages); man_pages++; if (*man_pages == NULL) break; if (term_dialog) { /* wait before starting next page */ fputs(WAIT2, stdout); if (do_wait(*man_pages, 0)) break; fputc('\n', stdout); } } } static void _man(man_fd, man_page) FILE *man_fd; char *man_page; { /* copy the manual page to standard output */ int index_nr, ch, line_nr = -1; /* Search entries for man_page */ for (index_nr = 0; index_nr < max_index_nr; index_nr++) { if (strncmp(man_page, index_buf[index_nr].page_name, NAME_SIZE) == 0) break; } if (index_nr == max_index_nr) { fprintf(stderr, "manual page for %s not available\n", man_page); return; } if (fseek(man_fd, index_buf[index_nr].page_pos, 0) == -1) { fprintf(stderr, "can't seek in manaul archive\n"); Exit(1); } while ((ch = fgetc(man_fd)) != '\n') { /* skip names on first line */ if (ch == EOF) break; } ch = fgetc(man_fd); /* display manual page */ while (ch != EOF) { fputc(ch, stdout); if (ch == '\n') { ch = fgetc(man_fd); if (ch == MAN_DELIM) break; if (term_dialog) { /* wait after page full */ line_nr++; if (line_nr != LI - 2) continue; if (do_wait(WAIT3, DO_REVERSE | DO_DELETE)) break; line_nr = 0; } } else ch = fgetc(man_fd); } } static void choose(man_fd) FILE *man_fd; { /* driver one time chosing a page on the * screen */ int ch, index, max_x, max_y; if (!term_dialog) { fprintf(stderr, "sorry, no terminal\n"); Exit(1); } term_clear = 1; max_y = (max_index_nr - 1) / MAX_X; /* determine screen sizes */ max_x = max_index_nr % MAX_X; if (max_x == 0) max_x = MAX_X; while (1) { index = do_choose(max_x, max_y); if (index == STOP) break; clrscr(); _man(man_fd, index_buf[index].page_name, 1); (void) do_wait(WAIT1, DO_REVERSE); }; } static int do_choose(max_x, max_y) int max_x, max_y; { /* implements the cursor movement on screen, * returns entry number */ int token; display(1); set_cursor(1); while (1) { fflush(stdout); token = gettoken(); if (token == READY || token == STOP) break; set_cursor(0); switch (token) { case CURSLEFT: if (cur_x > 0) cur_x--; break; case CURSRIGHT: if (cur_x < MAX_X - 1) cur_x++; break; case CURSUP: if (cur_y > top_y) cur_y--; else if (top_y > 0) { cur_y--; top_y--; } break; case CURSDOWN: if (cur_y < (top_y + MAX_Y - 1)) { cur_y++; if (cur_y > max_y) cur_y = max_y; } else if ((top_y + MAX_Y - 1) < max_y) { top_y++; cur_y++; } break; case CURSPAGEUP: top_y -= MAX_Y; if (top_y < 0) { top_y = 0; } cur_y = top_y; cur_x = 0; break; case CURSPAGEDOWN: top_y += MAX_Y; if ((top_y + MAX_Y - 1) >= max_y) { top_y = max_y - (MAX_Y - 1); if (top_y < 0) top_y = 0; } cur_y = top_y; cur_x = 0; break; case CURSHOME: top_y = 0; cur_y = 0; cur_x = 0; break; case CURSEND: top_y = max_y - (MAX_Y - 1); if (top_y < 0) top_y = 0; cur_y = max_y; cur_x = 0; break; case CURSMIDDLE: clrscr(); /* redraw screen */ break; } if (cur_y == max_y && cur_x >= max_x) cur_x = max_x - 1; display(token == CURSMIDDLE); set_cursor(1); } if (token == STOP) return(STOP); else return(cur_x + cur_y * MAX_X); } static int do_wait(message, mode) char *message; int mode; { /* print message and waits for a newline, * only on terminal dialog */ int ch; if (!term_dialog) return(0); if (mode & DO_REVERSE) reverse(1); fputs(message, stdout); if (mode & DO_REVERSE) reverse(0); fflush(stdout); do { ch = getchar(); } while (ch != EOF && ch != 'q' && ch != 'Q' && ch != '\n'); if ((mode & DO_DELETE) && DL) fprintf(stdout, "%s\r", DL); if (ch == '\n') return (0); /* normal end */ return(1); /* abnormal end */ } /* Index.c: a file to handle the index file */ /* #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <ctype.h> #include "index.h" */ #define MAN_FGETC(ch, man_fd) {ch=fgetc(man_fd);man_pos++;} #define INDEX_SIZE() (max_index_nr * sizeof(struct INDEX)) char ar_name[AR_NAME_SIZE]; /* name of man archive file */ char ar_index[AR_NAME_SIZE]; /* name of index file */ struct INDEX *index_buf; /* contains indices */ struct INDEX index_record; /* one index for build_index(); */ int max_index_nr = 0; /* number of indices in index_buf */ static long man_pos; /* position in manual file */ /* Forward declarations: */ static void _read_index(); static void build_index(); static int find_name(); static int find_delim(); static int index_cmp(index1, index2) struct INDEX *index1, *index2; { /* used in qsort to sort the indexes read */ int tmp; tmp = strncmp(index1->page_name, index2->page_name, NAME_SIZE); if (tmp == 0) tmp = index1 - index2; return(tmp); } extern void read_index() { /* read index file, if necessary creat a new * one */ int index_fd, build = 1; /* assume to build a new index */ struct stat name_stat, index_stat; stat(ar_name, &name_stat); if (stat(ar_index, &index_stat) == 0) { if (name_stat.st_mtime < index_stat.st_mtime) build = 0; /* index OK */ } if (build) build_index(ar_name, ar_index); index_fd = open(ar_index, O_RDONLY); /* read the index */ stat(ar_index, &index_stat); _read_index(index_fd, (int) index_stat.st_size); close(index_fd); if (build) { /* write new index out to index file */ qsort(index_buf, max_index_nr, sizeof(struct INDEX), index_cmp); index_fd = creat(ar_index, 0600); if (index_fd == -1) { fprintf(stderr, "can't create index\n"); return; /* not fatal for now */ } if (write(index_fd, index_buf, INDEX_SIZE()) != INDEX_SIZE()) { fprintf(stderr, "write error on index file\n"); unlink(ar_index); /* don't leave partial index */ return; /* not fatal for now */ } close(index_fd); } } static void _read_index(index_fd, index_size) int index_fd; int index_size; { /* allocate index buffer and read index */ index_buf = (struct INDEX *) malloc(index_size); if (index_buf == NULL) { fprintf(stderr, "can't allocate index buffer\n"); Exit(1); } if (read(index_fd, index_buf, index_size) != index_size) { fprintf(stderr, "can't read index file\n"); Exit(1); } max_index_nr = index_size / sizeof(struct INDEX); } static void build_index(ar_name, ar_index) char *ar_name, *ar_index; { /* Create new index by reading manual file and recording start points * to new entries. thes entries are writen to index file, which is * later read, sorten and writen out again. use stdio buffering to * speed up this process. */ FILE *man_fd, *index_fd; int ch; fprintf(stderr, "please wait, rebuilding index\n"); man_fd = fopen(ar_name, "r"); if (man_fd == NULL) { fprintf(stderr, "can't open %s\n", ar_name); Exit(1); } index_fd = fopen(ar_index, "w"); if (index_fd == NULL) { fprintf(stderr, "can't create index file\n"); Exit(1); } man_pos = 0L; /* initialize page pointer */ MAN_FGETC(ch, man_fd); if (ch != MAN_DELIM) ch = find_delim(man_fd, ch); while (ch != EOF) { index_record.page_pos = man_pos; ch = find_name(man_fd, index_fd); ch = find_delim(man_fd, ch); } fclose(man_fd); fclose(index_fd); } static int find_name(man_fd, index_fd) FILE *man_fd, *index_fd; { /* write an index record for all names this * entry is known by. these names are on the * first line following the MAN_DELIM, * separated by comma's. */ int ch, name_size; MAN_FGETC(ch, man_fd); while (ch != EOF && ch != '\n') { name_size = 0; while (ch != '\n' && (isspace(ch) || ch == ',')) MAN_FGETC(ch, man_fd); /* read leading spaces */ if (ch == '\n') break; while (!isspace(ch) && ch != ',' && ch != '\n' && ch != EOF) { if (ch == EOF) break; /* read manual name */ if (name_size < NAME_SIZE) index_record.page_name[name_size++] = ch; MAN_FGETC(ch, man_fd); } while (name_size < NAME_SIZE) /* fill name out */ index_record.page_name[name_size++] = '\0'; if (index_record.page_name[0] == '\0') continue; /* no manual name */ if (fwrite(&index_record, sizeof(struct INDEX), 1, index_fd) != 1) { fprintf(stderr, "write error on index file\n"); unlink(ar_index); /* don't leave partial index */ Exit(1); } } return(ch); } static int find_delim(man_fd, ch) FILE *man_fd; int ch; { /* find start of next manual page. this is * the line on which the MAN_DELIM is the * first character on that line. */ while (ch != EOF) { if (ch == '\n') { /* check for end manual page */ MAN_FGETC(ch, man_fd); if (ch == MAN_DELIM) break; } else MAN_FGETC(ch, man_fd); } return(ch); } /* Display.c: a file to handle the display */ #include <sgtty.h> #include <signal.h> /* #include <stdio.h> #include "index.h" #include "display.h" */ int term_dialog = 0; /* to determine if we have a terminal */ int term_clear = 0; /* do we have to clear the terminal at exit */ int top_y = 0; /* position of top line on screen (absolute) */ int cur_y = 0; /* y-position on screen (absolute) */ int cur_x = 0; /* x-position on screen (absolute) */ char *AL = ""; /* string to insert a line */ char *DL = ""; /* string to delete a line */ char *CL = ""; /* string for clearing the screen */ char *CM = ""; /* string for cursor goto code */ char *SE = ""; /* string to enter standout mode */ char *SO = ""; /* string to end standout mode */ int CO = 0; /* number of columns on screen */ int LI = 0; /* number of lines on screen */ static struct sgttyb termmode; /* contains startup sgtty struct */ static int term_used = 0; /* set when terminal is initialised */ extern char *tgetstr(); /* Forward declarations */ static void disp_all(); static void scr_down(); static void scr_up(); static void disp_fill(); static void disp_elem(); static void get_termcap(); void term_init() { /* initialize terminal and termcap functions */ struct sgttyb argp; static int init_done = 0; extern void Exit(); if (init_done || !term_dialog) return; get_termcap(); setbuf(stdout, malloc(BUFSIZ)); signal(SIGHUP, Exit); /* we got to restore the term mode */ signal(SIGINT, Exit); signal(SIGQUIT, Exit); signal(SIGTERM, Exit); ioctl(fileno(stdout), TIOCGETP, &termmode); argp = termmode; argp.sg_flags |= CBREAK; argp.sg_flags &= ~ECHO; ioctl(fileno(stdout), TIOCSETP, &argp); init_done = 1; term_used = 1; } void term_exit() { if (term_used) ioctl(fileno(stdout), TIOCSETP, &termmode); if (term_used && term_clear) clrscr(); } void display(force) int force; { /* do scrolling by determining direction of * scrolling */ static int prev_y = 0; int diff; diff = prev_y - top_y; /* determine direction */ if (diff < 0) { diff = -diff; if (diff >= MAX_Y) disp_all(); /* distance to large */ else scr_up(diff); } else if (diff == 0) { /* no scrolling */ if (force) disp_all(); /* redraw screen */ } else if (diff >= MAX_Y) { disp_all(); /* distance to large */ } else scr_down(diff); prev_y = top_y; /* remember old top */ } int gettoken() { /* read character from keyboard and decode * cursor keys */ int ch; do { ch = fgetc(stdin); if (ch == '\033') { /* decode the cursor keys */ ch = fgetc(stdin); if (ch != '[') continue; ch = fgetc(stdin); switch (ch & 0377) { case 'H': ch = CURSHOME; break; case 'A': ch = CURSUP; break; case 'V': ch = CURSPAGEUP; break; case 'D': ch = CURSLEFT; break; case 'G': ch = CURSMIDDLE; break; case 'C': ch = CURSRIGHT; break; case 'Y': ch = CURSEND; break; case 'B': ch = CURSDOWN; break; case 'U': ch = CURSPAGEDOWN; break; default: ch = READY; break; } if (ch != READY) return(ch); } else if (ch == EOF || ch == 'q' || ch == 'Q') return(STOP); } while (ch != '\n'); return(READY); } void set_cursor(on) int on; { /* put highlighted menu item on screen */ if (on) reverse(1); disp_elem(cur_x, cur_y - top_y); if (on) reverse(0); } static void scr_up(count) int count; { /* do actual moving of screen object for * scrolling up */ int i; GOTOXY(0, 0); for (i = count; i > 0; i--) fputs(DL, stdout); disp_fill(MAX_Y - count, MAX_Y - 1); } static void scr_down(count) int count; { /* do actual moving of screen object for * scrolling down */ int i; for (i = count; i > 0; i--) { GOTOXY(0, MAX_Y - 1); fputs(DL, stdout); GOTOXY(0, 0); fputs(AL, stdout); } disp_fill(0, count - 1); } static void disp_all() { /* redraw complete screen */ clrscr(); disp_fill(0, MAX_Y - 1); } static void disp_fill(first_y, last_y) int first_y, last_y; { /* fill in cleared space for scrolling with * new items */ int x, y; for (y = first_y; y <= last_y; y++) for (x = 0; x < MAX_X; x++) disp_elem(x, y); } static void disp_elem(x, y) int x, y; { /* put one menu item on correct place on * screen */ register int i, index; index = (top_y + y) * MAX_X + x; if (index >= max_index_nr) { return; /* for simplicity this check is made here */ } GOTOXY(x * NAME_WIDTH, y); for (i = 0; i < NAME_SIZE; i++) { register int ch = index_buf[index].page_name[i]; if (ch != '\0') putc(ch, stdout); else putc(' ', stdout); /* fill name out */ } } static void get_termcap() { /* initialize all needed termcap entries, * assumes all entries are available. */ static char entries[100]; char term_buf[1024]; char *loc = entries; static int init_done = 0; if (init_done) return; init_done = 1; /* Read terminal capabilities */ if (tgetent(term_buf, getenv("TERM")) <= 0) { fprintf(stderr, "Unknown terminal\n"); Exit(-1); } CO = tgetnum("co"); LI = tgetnum("li"); AL = tgetstr("al", &loc); CL = tgetstr("cl", &loc); CM = tgetstr("cm", &loc); DL = tgetstr("dl", &loc); SE = tgetstr("se", &loc); SO = tgetstr("so", &loc); if (CO < NAME_WIDTH || LI < 1) { fprintf(stderr, "sorry, co or li not sufficient in termcap\n"); Exit(-1); } if (AL == 0 || CL == 0 || CM == 0 || DL == 0 || SE == 0 || SO == 0) { fprintf(stderr, "sorry, al, cl, cm, dl, se or so not in termcap\n"); Exit(-1); } }