/* curses.c */ /* Author: * Steve Kirkendall * 14407 SW Teal Blvd. #C * Beaverton, OR 97005 * kirkenda@cs.pdx.edu */ /* This file contains the functions & variables needed for a tiny subset of * curses. The principle advantage of this version of curses is its * extreme speed. Disadvantages are potentially larger code, few supported * functions, limited compatibility with full curses, and only stdscr. */ #include "config.h" #include "vi.h" #if ANY_UNIX /* The termios/termio/sgtty #ifdefs were a mess, so I removed all but termios. * (KJB) */ # include # if MINIX # include # endif #endif #if TOS # include #endif #if OSK # include #endif #if VMS extern int VMS_read_raw; /* Set in initscr() */ #endif extern char *getenv(); static void starttcap(); /* variables, publicly available & used in the macros */ char *termtype; /* name of terminal entry */ short ospeed; /* speed of the tty, eg B2400 */ #if OSK char PC_; /* Pad char */ char *BC; /* backspace character string */ #else char PC; /* Pad char */ #endif WINDOW *stdscr; /* pointer into kbuf[] */ WINDOW kbuf[KBSIZ]; /* a very large output buffer */ int LINES; /* :li#: number of rows */ int COLS; /* :co#: number of columns */ int AM; /* :am: boolean: auto margins? */ int PT; /* :pt: boolean: physical tabs? */ char *VB; /* :vb=: visible bell */ char *UP; /* :up=: move cursor up */ char *SO = ""; /* :so=: standout start */ char *SE = ""; /* :se=: standout end */ char *US = ""; /* :us=: underline start */ char *UE = ""; /* :ue=: underline end */ char *MD = ""; /* :md=: bold start */ char *ME = ""; /* :me=: bold end */ char *AS = ""; /* :as=: alternate (italic) start */ char *AE = ""; /* :ae=: alternate (italic) end */ #ifndef NO_VISIBLE char *MV; /* :mv=: "visible" selection start */ #endif char *CM; /* :cm=: cursor movement */ char *CE; /* :ce=: clear to end of line */ char *CD; /* :cd=: clear to end of screen */ char *AL; /* :al=: add a line */ char *DL; /* :dl=: delete a line */ #if OSK char *SR_; /* :sr=: scroll reverse */ #else char *SR; /* :sr=: scroll reverse */ #endif char *KS = ""; /* :ks=: init string for cursor */ char *KE = ""; /* :ke=: restore string for cursor */ char *KU; /* :ku=: key sequence sent by up arrow */ char *KD; /* :kd=: key sequence sent by down arrow */ char *KL; /* :kl=: key sequence sent by left arrow */ char *KR; /* :kr=: key sequence sent by right arrow */ char *HM; /* :HM=: key sequence sent by the key */ char *EN; /* :EN=: key sequence sent by the key */ char *PU; /* :PU=: key sequence sent by the key */ char *PD; /* :PD=: key sequence sent by the key */ char *KI; /* :kI=: key sequence sent by the key */ #ifndef NO_FKEY char *FKEY[NFKEYS]; /* :k0=: ... :k9=: sequences sent by function keys */ #endif char *IM = ""; /* :im=: insert mode start */ char *IC = ""; /* :ic=: insert the following character */ char *EI = ""; /* :ei=: insert mode end */ char *DC; /* :dc=: delete a character */ char *TI = ""; /* :ti=: terminal init */ /* GB */ char *TE = ""; /* :te=: terminal exit */ /* GB */ #ifndef NO_CURSORSHAPE #if 1 char *CQ = (char *)0;/* :cQ=: normal cursor */ char *CX = (char *)1;/* :cX=: cursor used for EX command/entry */ char *CV = (char *)2;/* :cV=: cursor used for VI command mode */ char *CI = (char *)3;/* :cI=: cursor used for VI input mode */ char *CR = (char *)4;/* :cR=: cursor used for VI replace mode */ #else char *CQ = ""; /* :cQ=: normal cursor */ char *CX = ""; /* :cX=: cursor used for EX command/entry */ char *CV = ""; /* :cV=: cursor used for VI command mode */ char *CI = ""; /* :cI=: cursor used for VI input mode */ char *CR = ""; /* :cR=: cursor used for VI replace mode */ #endif #endif char *aend = ""; /* end an attribute -- either UE or ME */ char ERASEKEY; /* backspace key taken from ioctl structure */ #ifndef NO_COLOR char normalcolor[16]; char SOcolor[16]; char SEcolor[16]; char UScolor[16]; char UEcolor[16]; char MDcolor[16]; char MEcolor[16]; char AScolor[16]; char AEcolor[16]; # ifndef NO_POPUP char POPUPcolor[16]; # endif # ifndef NO_VISIBLE char VISIBLEcolor[16]; # endif #endif #if ANY_UNIX static struct termios oldtermio; /* original tty mode */ static struct termios newtermio; /* cbreak/noecho tty mode */ #endif #if OSK static struct sgbuf oldsgttyb; /* orginal tty mode */ static struct sgbuf newsgttyb; /* noecho tty mode */ #endif static char *capbuf; /* capability string buffer */ /* Initialize the Curses package. */ void initscr() { /* make sure TERM variable is set */ termtype = getenv("TERM"); #if VMS /* VMS getenv() handles TERM as a environment setting. Foreign * terminal support can be implemented by setting the ELVIS_TERM * logical or symbol to match a tinytcap entry. */ if (!strcmp(termtype,"unknown")) termtype = getenv("ELVIS_TERM"); #endif #if MSDOS /* For MS-DOS, if TERM is unset we can default to "pcbios", or * maybe "rainbow". */ if (!termtype) { #ifdef RAINBOW if (*(unsigned char far*)(0xffff000eL) == 6 /* Rainbow 100a */ || *(unsigned char far*)(0xffff000eL) == 148)/* Rainbow 100b */ { termtype = "rainbow"; } else #endif termtype = "pcbios"; } if (!strcmp(termtype, "pcbios")) #else if (!termtype) #endif { #if ANY_UNIX write(2, "Environment variable TERM must be set\n", (unsigned)38); exit(1); #endif #if OSK writeln(2, "Environment variable TERM must be set\n", (unsigned)38); exit(1); #endif #if AMIGA termtype = TERMTYPE; starttcap(termtype); #endif #if MSDOS starttcap("pcbios"); #endif #if TOS termtype = "vt52"; starttcap(termtype); #endif #if VMS write(2, "UNKNOWN terminal: define ELVIS_TERM\n", (unsigned)36); exit(1); #endif } else { #if MSDOS *o_pcbios = 0; #endif /* start termcap stuff */ starttcap(termtype); } /* create stdscr and curscr */ stdscr = kbuf; /* change the terminal mode to cbreak/noecho */ #if ANY_UNIX tcgetattr(2, &oldtermio); #endif #if OSK _gs_opt(0, &oldsgttyb); #endif #if VMS VMS_read_raw = 1; /* cbreak/noecho */ vms_open_tty(); #endif resume_curses(TRUE); } /* Shut down the Curses package. */ void endwin() { /* change the terminal mode back the way it was */ suspend_curses(); #if AMIGA amiclosewin(); #endif } static int curses_active = FALSE; /* Send any required termination strings. Turn off "raw" mode. */ void suspend_curses() { #ifndef NO_CURSORSHAPE if (has_CQ) { do_CQ(); } #endif if (has_TE) /* GB */ { do_TE(); } if (has_KE) { do_KE(); } #ifndef NO_COLOR quitcolor(); #endif refresh(); /* change the terminal mode back the way it was */ #if ANY_UNIX tcsetattr(2, TCSADRAIN, &oldtermio); #endif #if OSK _ss_opt(0, &oldsgttyb); #endif #if AMIGA ttyshutdown(); #endif #if MSDOS raw_set_stdio(FALSE); #endif #if VMS VMS_read_raw = 0; #endif curses_active = FALSE; } /* put the terminal in RAW mode. If "quietly" is FALSE, then ask the user * to hit a key, and wait for keystroke before returning. */ void resume_curses(quietly) int quietly; { if (!curses_active) { /* change the terminal mode to cbreak/noecho */ #if ANY_UNIX ospeed = cfgetospeed(&oldtermio); ERASEKEY = oldtermio.c_cc[VERASE]; newtermio = oldtermio; newtermio.c_iflag &= (IXON|IXOFF|IXANY|ISTRIP|IGNBRK); newtermio.c_oflag &= ~OPOST; newtermio.c_lflag &= ISIG; newtermio.c_cc[VINTR] = ctrl('C'); /* always use ^C for interrupts */ newtermio.c_cc[VMIN] = 1; newtermio.c_cc[VTIME] = 0; newtermio.c_cc[VSUSP] = 0; tcsetattr(2, TCSADRAIN, &newtermio); #endif #if OSK newsgttyb = oldsgttyb; newsgttyb.sg_echo = 0; newsgttyb.sg_eofch = 0; newsgttyb.sg_kbach = 0; newsgttyb.sg_kbich = ctrl('C'); _ss_opt(0, &newsgttyb); ospeed = oldsgttyb.sg_baud; ERASEKEY = oldsgttyb.sg_bspch; #endif #if AMIGA /* turn on window resize and RAW */ ttysetup(); #endif #if MSDOS raw_set_stdio(TRUE); #endif #if VMS VMS_read_raw = 1; { int c; read(0,&c,0); /* Flush the tty buffer. */ } ERASEKEY = '\177'; /* Accept as <^H> for VMS */ #endif if (has_TI) /* GB */ { do_TI(); } if (has_KS) { do_KS(); } curses_active = TRUE; } /* If we're supposed to quit quietly, then we're done */ if (quietly) { return; } signal(SIGINT, SIG_IGN); move(LINES - 1, 0); do_SO(); #if VMS qaddstr("\n[Press to continue]"); #else qaddstr("[Press to continue]"); #endif do_SE(); refresh(); ttyread(kbuf, 20, 0); /* in RAW mode, so <20 is very likely */ if (kbuf[0] == ':') { mode = MODE_COLON; addch('\n'); refresh(); } else { mode = MODE_VI; redraw(MARK_UNSET, FALSE); } exwrote = FALSE; #if TURBOC || __GNUC__ || _ANSI signal(SIGINT, (void(*)()) trapint); #else signal(SIGINT, trapint); #endif } /* This function fetches an optional string from termcap */ static void mayhave(T, s) char **T; /* where to store the returned pointer */ char *s; /* name of the capability */ { char *val; val = tgetstr(s, &capbuf); if (val) { *T = val; } } /* This function fetches a required string from termcap */ static void musthave(T, s) char **T; /* where to store the returned pointer */ char *s; /* name of the capability */ { mayhave(T, s); if (!*T) { write(2, "This termcap entry lacks the :", (unsigned)30); write(2, s, (unsigned)2); write(2, "=: capability\n", (unsigned)14); #if OSK write(2, "\l", 1); #endif exit(1); } } /* This function fetches a pair of strings from termcap. If one of them is * missing, then the other one is ignored. */ static void pair(T, U, sT, sU) char **T; /* where to store the first pointer */ char **U; /* where to store the second pointer */ char *sT; /* name of the first capability */ char *sU; /* name of the second capability */ { mayhave(T, sT); mayhave(U, sU); if (!**T || !**U) { *T = *U = ""; } } /* Read everything from termcap */ static void starttcap(term) char *term; { static char cbmem[800]; /* allocate memory for capbuf */ capbuf = cbmem; /* get the termcap entry */ switch (tgetent(kbuf, term)) { case -1: write(2, "Can't read /etc/termcap\n", (unsigned)24); #if OSK write(2, "\l", 1); #endif exit(2); case 0: write(2, "Unrecognized TERM type\n", (unsigned)23); #if OSK write(2, "\l", 1); #endif exit(3); } /* get strings */ musthave(&UP, "up"); mayhave(&VB, "vb"); musthave(&CM, "cm"); pair(&SO, &SE, "so", "se"); mayhave(&TI, "ti"); mayhave(&TE, "te"); if (tgetnum("ug") <= 0) { pair(&US, &UE, "us", "ue"); pair(&MD, &ME, "md", "me"); /* get italics, or have it default to underline */ pair(&AS, &AE, "as", "ae"); if (!*AS) { AS = US; AE = UE; } } #ifndef NO_VISIBLE MV = SO; /* by default */ mayhave(&MV, "mv"); #endif mayhave(&AL, "al"); mayhave(&DL, "dl"); musthave(&CE, "ce"); mayhave(&CD, "cd"); #if OSK mayhave(&SR_, "sr"); #else mayhave(&SR, "sr"); #endif pair(&IM, &EI, "im", "ei"); mayhave(&IC, "ic"); mayhave(&DC, "dc"); /* other termcap stuff */ AM = (tgetflag("am") && !tgetflag("xn")); PT = tgetflag("pt"); #if AMIGA amiopenwin(termtype); /* Must run this before ttysetup(); */ ttysetup(); /* Must run this before getsize(0); */ #endif getsize(0); /* Key sequences */ pair(&KS, &KE, "ks", "ke"); mayhave(&KU, "ku"); /* up */ mayhave(&KD, "kd"); /* down */ mayhave(&KL, "kl"); /* left */ mayhave(&KR, "kr"); /* right */ mayhave(&PU, "kP"); /* PgUp */ mayhave(&PD, "kN"); /* PgDn */ mayhave(&HM, "kh"); /* Home */ mayhave(&EN, "kH"); /* End */ mayhave(&KI, "kI"); /* Insert */ #ifndef CRUNCH if (!PU) mayhave(&PU, "K2"); /* "3x3 pad" names for PgUp, etc. */ if (!PD) mayhave(&PD, "K5"); if (!HM) mayhave(&HM, "K1"); if (!EN) mayhave(&EN, "K4"); mayhave(&PU, "PU"); /* old XENIX names for PgUp, etc. */ mayhave(&PD, "PD"); /* (overrides others, if used.) */ mayhave(&HM, "HM"); mayhave(&EN, "EN"); #endif #ifndef NO_FKEY mayhave(&FKEY[0], "k0"); /* function key codes */ mayhave(&FKEY[1], "k1"); mayhave(&FKEY[2], "k2"); mayhave(&FKEY[3], "k3"); mayhave(&FKEY[4], "k4"); mayhave(&FKEY[5], "k5"); mayhave(&FKEY[6], "k6"); mayhave(&FKEY[7], "k7"); mayhave(&FKEY[8], "k8"); mayhave(&FKEY[9], "k9"); # ifndef NO_SHIFT_FKEY mayhave(&FKEY[10], "s0"); /* shift function key codes */ mayhave(&FKEY[11], "s1"); mayhave(&FKEY[12], "s2"); mayhave(&FKEY[13], "s3"); mayhave(&FKEY[14], "s4"); mayhave(&FKEY[15], "s5"); mayhave(&FKEY[16], "s6"); mayhave(&FKEY[17], "s7"); mayhave(&FKEY[18], "s8"); mayhave(&FKEY[19], "s9"); # ifndef NO_CTRL_FKEY mayhave(&FKEY[20], "c0"); /* control function key codes */ mayhave(&FKEY[21], "c1"); mayhave(&FKEY[22], "c2"); mayhave(&FKEY[23], "c3"); mayhave(&FKEY[24], "c4"); mayhave(&FKEY[25], "c5"); mayhave(&FKEY[26], "c6"); mayhave(&FKEY[27], "c7"); mayhave(&FKEY[28], "c8"); mayhave(&FKEY[29], "c9"); # ifndef NO_ALT_FKEY mayhave(&FKEY[30], "a0"); /* alt function key codes */ mayhave(&FKEY[31], "a1"); mayhave(&FKEY[32], "a2"); mayhave(&FKEY[33], "a3"); mayhave(&FKEY[34], "a4"); mayhave(&FKEY[35], "a5"); mayhave(&FKEY[36], "a6"); mayhave(&FKEY[37], "a7"); mayhave(&FKEY[38], "a8"); mayhave(&FKEY[39], "a9"); # endif # endif # endif #endif #ifndef NO_CURSORSHAPE /* cursor shapes */ CQ = tgetstr("cQ", &capbuf); if (has_CQ) { CX = tgetstr("cX", &capbuf); if (!CX) CX = CQ; CV = tgetstr("cV", &capbuf); if (!CV) CV = CQ; CI = tgetstr("cI", &capbuf); if (!CI) CI = CQ; CR = tgetstr("cR", &capbuf); if (!CR) CR = CQ; } # ifndef CRUNCH else { CQ = CV = ""; pair(&CQ, &CV, "ve", "vs"); CX = CI = CR = CQ; } # endif /* !CRUNCH */ #endif /* !NO_CURSORSHAPE */ #ifndef NO_COLOR strcpy(SOcolor, SO); strcpy(SEcolor, SE); strcpy(AScolor, AS); strcpy(AEcolor, AE); strcpy(MDcolor, MD); strcpy(MEcolor, ME); strcpy(UScolor, US); strcpy(UEcolor, UE); # ifndef NO_POPUP strcpy(POPUPcolor, SO); # endif # ifndef NO_VISIBLE strcpy(VISIBLEcolor, MV); # endif #endif } /* This function gets the window size. It uses the TIOCGWINSZ ioctl call if * your system has it, or tgetnum("li") and tgetnum("co") if it doesn't. * This function is called once during initialization, and thereafter it is * called whenever the SIGWINCH signal is sent to this process. */ int getsize(signo) int signo; { int lines; int cols; #ifdef TIOCGWINSZ struct winsize size; #endif #ifdef SIGWINCH /* reset the signal vector */ signal(SIGWINCH, getsize); #endif /* get the window size, one way or another. */ lines = cols = 0; #ifdef TIOCGWINSZ if (ioctl(2, TIOCGWINSZ, &size) >= 0) { lines = size.ws_row; cols = size.ws_col; } #endif #if AMIGA /* Amiga gets window size by asking the console.device */ if (!strcmp(TERMTYPE, termtype)) { auto long len; auto char buf[30]; Write(Output(), "\2330 q", 4); /* Ask the console.device */ len = Read(Input(), buf, 29); buf[len] = '\000'; sscanf(&buf[5], "%d;%d", &lines, &cols); } #endif if ((lines == 0 || cols == 0) && signo == 0) { LINES = tgetnum("li"); COLS = tgetnum("co"); } #if MSDOS # ifdef RAINBOW if (!strcmp(termtype, "rainbow")) { /* Determine whether Rainbow is in 80-column or 132-column mode */ cols = *(unsigned char far *)0xee000f57L; } else # endif { lines = v_rows(); cols = v_cols(); } #endif if (lines >= 2) { LINES = lines; } if (cols >= 30) { COLS = cols; } /* Make sure we got values that we can live with */ if (LINES < 2 || COLS < 30) { write(2, "Screen too small\n", (unsigned)17); #if OSK write(2, "\l", 1); #endif endwin(); exit(2); } #if AMIGA if (*o_lines != LINES || *o_columns != COLS) { *o_lines = LINES; *o_columns = COLS; } #endif return 0; } /* This is a function version of addch() -- it is used by tputs() */ int faddch(ch) int ch; { addch(ch); return 0; } /* This function quickly adds a string to the output queue. It does *NOT* * convert \n into . */ void qaddstr(str) char *str; { REG char *s_, *d_; #if MSDOS if (o_pcbios[0]) { while (*str) qaddch(*str++); return; } #endif for (s_=(str), d_=stdscr; *d_++ = *s_++; ) { } stdscr = d_ - 1; } /* Output the ESC sequence needed to go into any video mode, if supported */ void attrset(a) int a; { do_aend(); if (a == A_BOLD) { do_MD(); aend = ME; } else if (a == A_UNDERLINE) { do_US(); aend = UE; } else if (a == A_ALTCHARSET) { do_AS(); aend = AE; } else { aend = ""; } } /* Insert a single character into the display */ void insch(ch) int ch; { if (has_IM) do_IM(); do_IC(); qaddch(ch); if (has_EI) do_EI(); } void wrefresh() { if (stdscr != kbuf) { VOIDBIOS(;,ttywrite(kbuf, (unsigned)(stdscr - kbuf))); stdscr = kbuf; } } void wqrefresh() { if (stdscr - kbuf > 2000) { VOIDBIOS(stdscr = kbuf, { ttywrite(kbuf, (unsigned)(stdscr - kbuf)); stdscr = kbuf; }); } } #ifndef NO_COLOR /* This function is called during termination. It resets color modes */ int ansiquit() { /* if ANSI color terminal, then reset the colors */ if (!strcmp(UP, "\033[A")) { tputs("\033[37;40m\033[m", 1, faddch); clrtoeol(); return 1; } return 0; } /* This sets the color strings that work for ANSI terminals. If the TERMCAP * doesn't look like an ANSI terminal, then it returns FALSE. If the colors * aren't understood, it also returns FALSE. If all goes well, it returns TRUE */ int ansicolor(cmode, attrbyte) int cmode; /* mode to set, e.g. A_NORMAL */ int attrbyte; /* IBM PC attribute byte */ { char temp[16]; /* hold the new mode string */ /* if not ANSI-ish, then fail */ if (strcmp(UP, "\033[A") && strcmp(UP, "\033OA")) { msg("Don't know how to set colors for this terminal"); return 0; } /* construct the color string */ sprintf(temp, "\033[m\033[3%c;4%c%s%sm", "04261537"[attrbyte & 0x07], "04261537"[(attrbyte >> 4) & 0x07], (attrbyte & 0x08) ? ";1" : "", (attrbyte & 0x80) ? ";5" : ""); /* stick it in the right place */ switch (cmode) { case A_NORMAL: if (!strcmp(MEcolor, normalcolor)) strcpy(MEcolor, temp); if (!strcmp(UEcolor, normalcolor)) strcpy(UEcolor, temp); if (!strcmp(AEcolor, normalcolor)) strcpy(AEcolor, temp); if (!strcmp(SEcolor, normalcolor)) strcpy(SEcolor, temp); strcpy(normalcolor, temp); tputs(normalcolor, 1, faddch); break; case A_BOLD: strcpy(MDcolor, temp); strcpy(MEcolor, normalcolor); break; case A_UNDERLINE: strcpy(UScolor, temp); strcpy(UEcolor, normalcolor); break; case A_ALTCHARSET: strcpy(AScolor, temp); strcpy(AEcolor, normalcolor); break; case A_STANDOUT: strcpy(SOcolor, temp); strcpy(SEcolor, normalcolor); break; #ifndef NO_POPUP case A_POPUP: strcpy(POPUPcolor, temp); break; #endif #ifndef NO_VISIBLE case A_VISIBLE: strcpy(VISIBLEcolor, temp); break; #endif } return 1; } /* This function outputs the ESC sequence needed to switch the screen back * to "normal" mode. On color terminals which haven't had their color set * yet, this is one of the termcap strings; for color terminals that really * have had colors defined, we just the "normal color" escape sequence. */ endcolor() { if (aend == ME) tputs(MEcolor, 1, faddch); else if (aend == UE) tputs(UEcolor, 1, faddch); else if (aend == AE) tputs(AEcolor, 1, faddch); else if (aend == SE) tputs(SEcolor, 1, faddch); aend = ""; return 0; } #endif /* !NO_COLOR */