2 Generalized consol input/output handler
3 Kern Sibbald, December MMIII
21 /* Global functions imported */
24 extern char *getenv(char *);
26 static void add_smap(char *str, int func);
28 /* From termios library */
32 /* Global variables */
34 static char *t_up = "\n"; /* scroll up character */
35 static char *t_honk = "\007"; /* sound beep */
36 static char *t_flag = "+"; /* mark flag sequence */
37 static char *t_norm = " "; /* clear mark sequence */
38 static char *t_il; /* insert line */
39 static char *t_dl; /* delete line */
40 static char *t_cs; /* clear screen */
41 static char *t_cl; /* clear line */
42 static int t_width = 79; /* terminal width */
43 static int t_height = 24; /* terminal height */
44 static int t_flagl = 1; /* # chars for flag */
45 static int linsdel_ok = 0; /* set if term has line insert & delete fncs */
46 static short dioflag = 1; /* set if ok to use ibm bios calls */
48 static char *t_cm; /* cursor positioning */
49 static char *t_ti; /* init sequence */
50 static char *t_te; /* end sequence */
51 static char *t_do; /* down one line */
52 static char *t_sf; /* scroll screen one line up */
54 /* Keypad and Function Keys */
55 static char *kl; /* left key */
56 static char *kr; /* right */
59 static char *kh; /* home */
60 static char *kb; /* backspace */
61 static char *k0; /* function key 10 */
62 static char *k1; /* function key 1 */
63 static char *k2; /* function key 2 */
64 static char *k3; /* function key 3 */
65 static char *k4; /* function key 4 */
66 static char *k5; /* function key 5 */
67 static char *k6; /* function key 6 */
68 static char *k7; /* function key 7 */
69 static char *k8; /* function key 8 */
70 static char *k9; /* function key 9 */
71 static char *kD; /* delete key */
72 static char *kI; /* insert */
73 static char *kN; /* next page */
74 static char *kP; /* previous page */
75 static char *kH; /* home */
76 static char *kE; /* end */
78 static int use_termcap;
80 #define EOS '\0' /* end of string terminator */
86 * Stab entry. Input chars (str), the length, and the desired
89 typedef struct s_stab {
98 static stab_t **stab = NULL; /* array of stabs by length */
99 static int num_stab; /* size of stab array */
101 /* Local variables */
103 static struct termios old_term_params;
105 /* Maintain lines in a doubly linked circular pool of lines. Each line is
106 preceded by a header defined by the lstr structure */
109 struct lstr { /* line pool structure */
110 struct lstr *prevl; /* link to previous line */
111 struct lstr *nextl; /* link to next line */
112 long len; /* length of line+header */
113 char used; /* set if line valid */
114 char line; /* line is actually varying length */
118 #define POOLEN 128000 /* bytes in line pool */
120 #define POOLEN 500 /* bytes in line pool */
122 char pool[POOLEN]; /* line pool */
123 #define PHDRL ((int)sizeof(struct lstr)) /* length of line header */
125 static struct lstr *lptr; /* current line pointer */
126 static struct lstr *slptr; /* store line pointer */
128 static char *getnext(), *getprev();
129 static int first = 1;
130 static int mode_insert = 0;
131 static int mode_wspace = 1; /* words separated by spaces */
133 /* Forward referenced functions */
134 static void sigintcatcher(int);
136 /* Global variables Exported */
138 static short char_map[600]= {
139 0, F_NXTWRD, /* ^A Next Word */
140 F_SPLIT, /* ^B Split line */ F_EOI, /* ^C Quit */
141 F_DELCHR, /* ^D Delete character */ F_EOF, /* ^E End of file */
142 F_INSCHR, /* ^F Insert character */ F_TABBAK, /* ^G Back tab */
143 F_CSRLFT, /* ^H Left */ F_TAB, /* ^I Tab */
144 F_CSRDWN, /* ^J Down */ F_CSRUP, /* ^K Up */
145 F_CSRRGT, /* ^L Right */ F_RETURN, /* ^M Carriage return */
146 F_EOL, /* ^N End of line */ F_CONCAT, /* ^O Concatenate lines */
147 F_MARK, /* ^P Set marker */ F_TINS, /* ^Q Insert character mode */
148 F_PAGUP, /* ^R Page up */ F_CENTER, /* ^S Center text */
149 F_PAGDWN, /* ^T Page down */ F_SOL, /* ^U Line start */
150 F_DELWRD, /* ^V Delete word */ F_PRVWRD, /* ^W Previous word */
151 F_NXTMCH, /* ^X Next match */ F_DELEOL, /* ^Y Delete to end of line */
152 F_DELLIN, /* ^Z Delete line */ 0x1B, /* ^[=ESC escape */
153 F_TENTRY, /* ^\ Entry mode */ F_PASTECB,/* ^]=paste clipboard */
154 F_HOME, /* ^^ Home */ F_ERSLIN, /* ^_ Erase line */
155 ' ','!','"','#','$','%','&','\047',
156 '(',')','*','+','\054','-','.','/',
157 '0','1','2','3','4','5','6','7',
158 '8','9',':',';','<','=','>','?',
159 '@','A','B','C','D','E','F','G',
160 'H','I','J','K','L','M','N','O',
161 'P','Q','R','S','T','U','V','W',
162 'X','Y','Z','[','\\',']','^','_',
163 '\140','a','b','c','d','e','f','g',
164 'h','i','j','k','l','m','n','o',
165 'p','q','r','s','t','u','v','w',
166 'x','y','z','{','|','}','\176',F_ERSCHR /* erase character */
171 #define NVID 0x1E /* normal video -- blue */
172 #define RVID 0x4F /* reverse video -- red */
173 #define MNVID 0x07 /* normal video (monochrome tube) */
174 #define MRVID 0x70 /* reverse video (monochrome tube) */
177 /* Local variables */
179 #define CR '\r' /* carriage return */
182 /* Function Prototypes */
184 static int input_char(void);
185 static int t_gnc(void);
186 static void insert_space(char *curline, int line_len);
187 static void forward(int i, char *str, int str_len);
188 static void backup(int i);
189 static void delchr(int cnt, char *curline, int line_len);
190 static int iswordc(char c);
191 static int next_word(char *ldb_buf);
192 static int prev_word(char *ldb_buf);
193 static void prtcur(char *str);
194 static void poolinit(void);
195 static char * getnext(void);
196 static char * getprev(void);
197 static void putline(char *newl, int newlen);
198 static void t_honk_horn(void);
199 static void t_insert_line(void);
200 static void t_delete_line(void);
201 static void t_clrline(int pos, int width);
202 static void t_sendl(char *msg, int len);
203 static void t_send(char *msg);
204 static void t_char(char c);
206 static void rawmode(void);
207 static void normode(void);
208 static int t_getch();
209 static void trapctlc();
210 static void asclrl(int pos, int width);
211 static void asinsl();
212 static void asdell();
214 int input_line(char *string, int length);
229 * Guarantee that the string is properly terminated */
230 static char *bstrncpy(char *dest, const char *src, int maxlen)
232 strncpy(dest, src, maxlen-1);
240 * New style string mapping to function code
242 static int do_smap(int c)
261 for (i=len-1; i<MAX_STAB; i++) {
262 for (tstab=stab[i]; tstab; tstab=tstab->next) {
263 if (strncmp(str, tstab->str, len) == 0) {
264 if (len == tstab->len) {
268 break; /* found possibility continue searching */
275 /* found partial match, so get next character and retry */
276 str[len++] = t_gnc();
282 static void dump_stab()
288 for (i=0; i<MAX_STAB; i++) {
289 for (tstab=stab[i]; tstab; tstab=tstab->next) {
290 for (j=0; j<tstab->len; j++) {
292 if (c < 0x20 || c > 0x7F) {
293 sprintf(buf, " 0x%x ", c);
301 sprintf(buf, " func=%d len=%d\n\r", tstab->func, tstab->len);
309 * New routine. Add string to string->func mapping table.
311 static void add_smap(char *str, int func)
321 /* errmsg("String for func %d is zero length\n", func); */
324 tstab = (stab_t *)malloc(sizeof(stab_t));
325 memset(tstab, 0, sizeof(stab_t));
327 tstab->str = (char *)malloc(tstab->len + 1);
328 bstrncpy(tstab->str, str, tstab->len + 1);
330 if (tstab->len > num_stab) {
331 printf("stab string too long %d. Max is %d\n", tstab->len, num_stab);
334 tstab->next = stab[tstab->len-1];
335 stab[tstab->len-1] = tstab;
336 /* printf("Add_smap tstab=%x len=%d func=%d tstab->next=%x\n\r", tstab, len,
337 func, tstab->next); */
342 /* Get the next character from the terminal - performs table lookup on
343 the character to do the desired translation */
349 if ((c=t_gnc()) <= 599) { /* IBM generates codes up to 260 */
351 } else if (c > 1000) { /* stuffed function */
352 c -= 1000; /* convert back to function code */
357 /* if we got a screen size escape sequence, read height, width */
360 y = t_gnc() - 0x20; /* y */
361 x = t_gnc() - 0x20; /* x */
368 /* Get a complete input line */
371 /*FCN*/input_line(char *string, int length)
373 char curline[200]; /* edit buffer */
378 poolinit(); /* build line pool */
381 noline = 1; /* no line fetched yet */
382 for (cl=cp=0; cl<length && cl<(int)sizeof(curline); ) {
383 switch (c=(int)input_char()) {
384 case F_RETURN: /* CR */
385 t_sendl("\r\n", 2); /* yes, print it and */
386 goto done; /* get out */
389 if (noline) { /* no line fetched yet */
390 getnext(); /* getnext so getprev gets current */
391 noline = 0; /* we now have line */
394 bstrncpy(curline, getprev(), sizeof(curline));
398 noline = 0; /* mark line fetched */
399 bstrncpy(curline, getnext(), sizeof(curline));
403 insert_space(curline, sizeof(curline));
406 delchr(1, curline, sizeof(curline)); /* delete one character */
408 case F_CSRLFT: /* Backspace */
412 forward(1,curline, sizeof(curline));
414 case F_ERSCHR: /* Rubout */
416 delchr(1, curline, sizeof(curline));
419 t_clrline(0, t_width);
424 forward(next_word(curline),curline, sizeof(curline));
427 backup(prev_word(curline));
430 delchr(next_word(curline), curline, sizeof(curline)); /* delete word */
432 case F_NXTMCH: /* Ctl-X */
434 *string = EOS; /* terminate string */
435 return(c); /* give it to him */
437 /* Note fall through */
440 backup(cp); /* backup to beginning of line */
441 t_clrline(0,t_width); /* erase line */
443 cl = 0; /* reset cursor counter */
450 forward(1,curline, sizeof(curline));
456 case F_TINS: /* toggle insert mode */
457 mode_insert = !mode_insert; /* flip bit */
460 if (c > 255) { /* function key hit */
461 if (cl==0) { /* if first character then */
462 *string = EOS; /* terminate string */
463 return c; /* return it */
465 t_honk_horn(); /* complain */
468 insert_space(curline, sizeof(curline));
470 curline[cp++] = c; /* store character in line being built */
471 t_char((char)c); /* echo character to terminal */
473 cl = cp; /* keep current length */
479 /* If we fall through here rather than goto done, the line is too long
480 simply return what we have now. */
482 curline[cl++] = EOS; /* terminate */
483 bstrncpy(string,curline,length); /* return line to caller */
484 /* Note, put line zaps curline */
485 putline(curline,cl); /* save line for posterity */
486 return 0; /* give it to him/her */
489 /* Insert a space at the current cursor position */
491 /*FCN*/insert_space(char *curline, int curline_len)
495 if (cp > cl || cl+1 > curline_len) return;
496 /* Note! source and destination overlap */
497 memmove(&curline[cp+1],&curline[cp],i=cl-cp);
501 forward(i,curline, curline_len);
506 /* Move cursor forward keeping characters under it */
508 /*FCN*/forward(int i,char *str, int str_len)
524 /* Backup cursor keeping characters under it */
528 for ( ;i && cp; i--,cp--)
532 /* Delete the character under the cursor */
534 /*FCN*/delchr(int cnt, char *curline, int line_len)
540 if ((i=cl-cp-cnt+1) > 0) {
541 memcpy(&curline[cp],&curline[cp+cnt],i);
543 curline[cl -= cnt] = EOS;
544 t_clrline(0,t_width);
546 forward(i=cl-cp,curline, line_len);
551 /* Determine if character is part of a word */
553 /*FCN*/iswordc(char c)
557 if (c >= '0' && c <= '9')
559 if (c == '$' || c == '%')
564 /* Return number of characters to get to next word */
566 /*FCN*/next_word(char *ldb_buf)
573 for ( ; ncp<cl && iswordc(*(ldb_buf+ncp)); ncp++) ;
574 for ( ; ncp<cl && !iswordc(*(ldb_buf+ncp)); ncp++) ;
578 /* Return number of characters to get to previous word */
580 /*FCN*/prev_word(char *ldb_buf)
584 if (cp == 0) /* if at begin of line stop now */
586 if (cp > cl) /* if past eol start at eol */
590 /* backup to end of previous word - i.e. skip special chars */
591 for (i=ncp-1; i && !iswordc(*(ldb_buf+i)); i--) ;
592 if (i == 0) { /* at beginning of line? */
593 return cp; /* backup to beginning */
595 /* now move back through word to beginning of word */
596 for ( ; i && iswordc(*(ldb_buf+i)); i--) ;
597 ncp = i+1; /* position to first char of word */
598 if (i==0 && iswordc(*ldb_buf)) /* check for beginning of line */
600 return cp-ncp; /* return count */
603 /* Display new current line */
605 /*FCN*/prtcur(char *str)
608 t_clrline(0,t_width);
609 cp = cl = strlen(str);
614 /* Initialize line pool. Split pool into two pieces. */
618 slptr = lptr = (struct lstr *)pool;
627 /* Return pointer to next line in the pool and advance current line pointer */
631 do { /* find next used line */
633 } while (!lptr->used);
634 return (char *)&lptr->line;
637 /* Return pointer to previous line in the pool */
641 do { /* find previous used line */
643 } while (!lptr->used);
644 return (char *)&lptr->line;
648 /*FCN*/putline(char *newl, int newlen)
650 struct lstr *nptr; /* points to next line */
653 lptr = slptr; /* get ptr to last line stored */
654 lptr = lptr->nextl; /* advance pointer */
655 if ((char *)lptr-pool+newlen+PHDRL > POOLEN) { /* not enough room */
656 lptr->used = 0; /* delete line */
657 lptr = (struct lstr *)pool; /* start at beginning of buffer */
659 while (lptr->len < newlen+PHDRL) { /* concatenate buffers */
660 nptr = lptr->nextl; /* point to next line */
661 lptr->nextl = nptr->nextl; /* unlink it from list */
662 nptr->nextl->prevl = lptr;
663 lptr->len += nptr->len;
665 if (lptr->len > newlen + 2 * PHDRL) { /* split buffer */
666 nptr = (struct lstr *)((char *)lptr + newlen + PHDRL);
667 /* Appropriate byte alignment - normally 2 byte, but on
668 sparc we need 4 byte alignment, so we always do 4 */
669 if (((unsigned)nptr & 3) != 0) { /* test four byte alignment */
671 nptr = (struct lstr *)((((unsigned) p) & ~3) + 4);
673 nptr->len = lptr->len - ((char *)nptr - (char *)lptr);
674 lptr->len -= nptr->len;
675 nptr->nextl = lptr->nextl; /* link in new buffer */
676 lptr->nextl->prevl = nptr;
681 memcpy(&lptr->line,newl,newlen);
682 lptr->used = 1; /* mark line used */
683 slptr = lptr; /* save as stored line */
688 /*FCN*/dump(struct lstr *ptr, char *msg)
690 printf("%s buf=%x nextl=%x prevl=%x len=%d used=%d\n",
691 msg,ptr,ptr->nextl,ptr->prevl,ptr->len,ptr->used);
693 printf("line=%s\n",&ptr->line);
695 #endif /* DEBUGOUT */
698 /* Honk horn on terminal */
705 /* Insert line on terminal */
707 /*FCN*/t_insert_line()
712 /* Delete line from terminal */
714 /*FCN*/t_delete_line()
719 /* clear line from pos to width */
721 /*FCN*/t_clrline(int pos, int width)
723 asclrl(pos, width); /* clear to end of line */
726 /* Helper function to add string preceded by
727 * ESC to smap table */
728 static void add_esc_smap(char *str, int func)
731 buf[0] = 0x1B; /* esc */
732 bstrncpy(buf+1, str, sizeof(buf)-1);
736 /* Set raw mode on terminal file. Basically, get the terminal into a
737 mode in which all characters can be read as they are entered. CBREAK
738 mode is not sufficient.
740 /*FCN*/static void rawmode(void)
743 static char term_buf[2048];
744 static char *term_buffer = term_buf;
745 char *termtype = (char *)getenv("TERM");
747 if (tcgetattr(0, &old_term_params) != 0) {
748 printf("Cannot tcgetattr()\n");
752 t.c_cc[VMIN] = 1; /* satisfy read after 1 char */
754 t.c_iflag &= ~(BRKINT | IGNPAR | PARMRK | INPCK |
755 ISTRIP | ICRNL | IXON | IXOFF | INLCR | IGNCR);
757 t.c_oflag &= ~(OPOST); /* no output processing */
758 t.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON |
759 ISIG | NOFLSH | TOSTOP);
760 tcflush(0, TCIFLUSH);
761 if (tcsetattr(0, TCSANOW, &t) == -1) {
762 printf("Cannot tcsetattr()\n");
765 signal(SIGQUIT, SIG_IGN);
766 signal(SIGHUP, SIG_IGN);
767 signal(SIGSTOP, SIG_IGN);
768 signal(SIGINT, sigintcatcher);
769 signal(SIGWINCH, SIG_IGN);
770 signal(SIGQUIT, SIG_IGN);
771 signal(SIGCHLD, SIG_IGN);
772 signal(SIGTSTP, SIG_IGN);
775 printf("Cannot get terminal type.\n");
778 if (tgetent(term_buffer, termtype) < 0) {
779 printf("Cannot get terminal termcap entry.\n");
782 t_width = t_height = -1;
783 t_width = tgetnum("co") - 1;
784 t_height = tgetnum("li");
787 t_cm = (char *)tgetstr("cm", &term_buffer);
788 t_cs = (char *)tgetstr("cl", &term_buffer); /* clear screen */
789 t_cl = (char *)tgetstr("ce", &term_buffer); /* clear line */
790 t_dl = (char *)tgetstr("dl", &term_buffer); /* delete line */
791 t_il = (char *)tgetstr("al", &term_buffer); /* insert line */
792 t_honk = (char *)tgetstr("bl", &term_buffer); /* beep */
793 t_ti = (char *)tgetstr("ti", &term_buffer);
794 t_te = (char *)tgetstr("te", &term_buffer);
795 t_up = (char *)tgetstr("up", &term_buffer);
796 t_do = (char *)tgetstr("do", &term_buffer);
797 t_sf = (char *)tgetstr("sf", &term_buffer);
798 t_flag = (char *)tgetstr("so", &term_buffer);
799 t_norm = (char *)tgetstr("se", &term_buffer);
805 t_flagl = tgetnum("sg");
810 num_stab = MAX_STAB; /* get default stab size */
811 stab = (stab_t **)malloc(sizeof(stab_t *) * num_stab);
812 memset(stab, 0, sizeof(stab_t *) * num_stab);
815 kl = (char *)tgetstr("kl", &term_buffer);
816 kr = (char *)tgetstr("kr", &term_buffer);
817 ku = (char *)tgetstr("ku", &term_buffer);
818 kd = (char *)tgetstr("kd", &term_buffer);
819 kh = (char *)tgetstr("kh", &term_buffer);
820 kb = (char *)tgetstr("kb", &term_buffer);
821 k0 = (char *)tgetstr("k0", &term_buffer);
822 k1 = (char *)tgetstr("k1", &term_buffer);
823 k2 = (char *)tgetstr("k2", &term_buffer);
824 k3 = (char *)tgetstr("k3", &term_buffer);
825 k4 = (char *)tgetstr("k4", &term_buffer);
826 k5 = (char *)tgetstr("k5", &term_buffer);
827 k6 = (char *)tgetstr("k6", &term_buffer);
828 k7 = (char *)tgetstr("k7", &term_buffer);
829 k8 = (char *)tgetstr("k8", &term_buffer);
830 k9 = (char *)tgetstr("k9", &term_buffer);
831 kD = (char *)tgetstr("kD", &term_buffer);
832 kI = (char *)tgetstr("kI", &term_buffer);
833 kN = (char *)tgetstr("kN", &term_buffer);
834 kP = (char *)tgetstr("kP", &term_buffer);
835 kH = (char *)tgetstr("kH", &term_buffer);
836 kE = (char *)tgetstr("kE", &term_buffer);
838 add_smap(kl, F_CSRLFT);
839 add_smap(kr, F_CSRRGT);
840 add_smap(ku, F_CSRUP);
841 add_smap(kd, F_CSRDWN);
842 add_smap(kI, F_TINS);
843 add_smap(kN, F_PAGDWN);
844 add_smap(kP, F_PAGUP);
845 add_smap(kH, F_HOME);
849 add_esc_smap("[A", F_CSRUP);
850 add_esc_smap("[B", F_CSRDWN);
851 add_esc_smap("[C", F_CSRRGT);
852 add_esc_smap("[D", F_CSRLFT);
853 add_esc_smap("[1~", F_HOME);
854 add_esc_smap("[2~", F_TINS);
855 add_esc_smap("[3~", F_DELCHR);
856 add_esc_smap("[4~", F_EOF);
859 for (i=301; i<600; i++) {
860 char_map[i] = i; /* setup IBM function codes */
866 /* Restore tty mode */
867 /*FCN*/static void normode()
869 tcsetattr(0, TCSANOW, &old_term_params);
872 /* Get next character from terminal/script file/unget buffer */
878 while ((ch=t_getch()) == 0) ; /* get next input character */
883 /* Get next character from OS */
884 static int t_getch(void)
888 if (read(0, &c, 1) != 1) {
896 /* window_size -- Return window height and width to caller. */
897 static int window_size(int *height, int *width) /* /window_size/ */
899 *width = tgetnum("co") - 1;
900 *height = tgetnum("li");
905 /* Send message to terminal - primitive routine */
907 /*FCN*/t_sendl(char *msg,int len)
913 /*FCN*/t_send(char *msg)
918 t_sendl(msg, strlen(msg)); /* faster than one char at time */
921 /* Send single character to terminal - primitive routine -
922 NOTE! don't convert this routine to use the dioflag routines unless
923 those routines are made to simulate a backspace. */
925 /*FCN*/t_char(char c)
931 static int brkflg = 0; /* set on user break */
933 /* Routine to return true if user types break */
939 /* Clear break flag */
946 /* Interrupt caught here */
947 static void sigintcatcher(int sig)
950 signal(SIGINT, sigintcatcher);
955 /*FCN*/void trapctlc()
957 signal(SIGINT, sigintcatcher);
961 /* ASCLRL() -- Clear to end of line from current position */
962 static void asclrl(int pos, int width)
967 t_send(t_cl); /* use clear to eol function */
970 if (pos==1 && linsdel_ok) {
971 t_delete_line(); /* delete line */
972 t_insert_line(); /* reinsert it */
975 for (i=1; i<=width-pos+1; i++)
976 t_char(' '); /* last resort, blank it out */
977 for (i=1; i<=width-pos+1; i++) /* backspace to original position */
985 /* ASCURS -- Set cursor position */
986 static void ascurs(int y, int x)
988 t_send((char *)tgoto(t_cm, x, y));
992 /* ASCLRS -- Clear whole screen */
1002 /* ASINSL -- insert new line after cursor */
1003 static void asinsl()
1005 t_clrline(0, t_width);
1006 t_send(t_il); /* insert before */
1009 /* ASDELL -- Delete line at cursor */
1010 static void asdell()