/* $OpenBSD: termout.c,v 1.12 2003/07/18 23:11:43 david Exp $ */ /*- * Copyright (c) 1988 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint /*static char sccsid[] = "from: @(#)termout.c 4.3 (Berkeley) 4/26/91";*/ static char rcsid[] = "$OpenBSD: termout.c,v 1.12 2003/07/18 23:11:43 david Exp $"; #endif /* not lint */ #if defined(unix) #include #include #endif #include #include #include #include #include #include #if defined(ultrix) /* Some version of this OS has a bad definition for nonl() */ #undef nl #undef nonl #define nl() (_tty.sg_flags |= CRMOD,_pfast = _rawmode,stty(_tty_ch, &_tty)) #define nonl() (_tty.sg_flags &= ~CRMOD, _pfast = TRUE, stty(_tty_ch, &_tty)) #endif /* defined(ultrix) */ #include "../general/general.h" #include "terminal.h" #include "../api/disp_asc.h" #include "../ctlr/hostctlr.h" #include "../ctlr/externs.h" #include "../ctlr/declare.h" #include "../ctlr/oia.h" #include "../ctlr/screen.h" #include "../ctlr/scrnctlr.h" #include "../general/globals.h" #include "telextrn.h" #define CorrectTerminalCursor() ((TransparentClock == OutputClock)? \ CursorAddress:UnLocked? CursorAddress: HighestScreen()) static int terminalCursorAddress; /* where the cursor is on term */ static int screenInitd; /* the screen has been initialized */ static int screenStopped; /* the screen has been stopped */ static int max_changes_before_poll; /* how many characters before looking */ /* at terminal and net again */ static int needToRing; /* need to ring terinal bell */ static char *bellSequence = "\07"; /* bell sequence (may be replaced by * VB during initialization) */ static WINDOW *bellwin = 0; /* The window the bell message is in */ int bellwinup = 0; /* Are we up with it or not */ #if defined(unix) static char *myKS, *myKE; #endif /* defined(unix) */ static int inHighlightMode = 0; ScreenImage Terminal[MAXSCREENSIZE]; /* Variables for transparent mode */ #if defined(unix) static int tcflag = -1; /* transparent mode command flag */ static int savefd[2]; /* for storing fds during transcom */ extern int tin, tout; /* file descriptors */ static void aborttc(); #endif /* defined(unix) */ /* * init_screen() * * Initialize variables used by screen. */ void init_screen() { bellwinup = 0; inHighlightMode = 0; ClearArray(Terminal); } /* OurExitString - designed to keep us from going through infinite recursion */ static void OurExitString(string, value) char *string; int value; { static int recursion = 0; if (!recursion) { recursion = 1; ExitString(string, value); } } /* DoARefresh */ static void DoARefresh() { if (ERR == refresh()) { OurExitString("ERR from refresh\n", 1); } } static void GoAway(from, where) char *from; /* routine that gave error */ int where; /* cursor address */ { char foo[100]; snprintf(foo, sizeof foo, "ERR from %s at %d (%d, %d)\n", from, where, ScreenLine(where), ScreenLineOffset(where)); OurExitString(foo, 1); /* NOTREACHED */ } /* What is the screen address of the attribute byte for the terminal */ static int WhereTermAttrByte(p) int p; { int i; i = p; do { if (TermIsStartField(i)) { return(i); } i = ScreenDec(i); } while (i != p); return(LowestScreen()); /* unformatted screen... */ } /* * There are two algorithms for updating the screen. * The first, SlowScreen() optimizes the line between the * computer and the screen (say a 9600 baud line). To do * this, we break out of the loop every so often to look * at any pending input from the network (so that successive * screens will only partially print until the final screen, * the one the user possibly wants to see, is displayed * in its entirety). * * The second algorithm tries to optimize CPU time (by * being simpler) at the cost of the bandwidth to the * screen. * * Of course, curses(3X) gets in here also. */ #if defined(NOT43) static int #else /* defined(NOT43) */ static void #endif /* defined(NOT43) */ SlowScreen() { int is, shouldbe, isattr, shouldattr; int pointer; int fieldattr, termattr; int columnsleft; #define NORMAL 0 #define HIGHLIGHT 1 /* Mask bits */ #define NONDISPLAY 4 /* Mask bits */ #define UNDETERMINED 8 /* Mask bits */ #define DoAttributes(x) \ switch (x&ATTR_DSPD_MASK) { \ case ATTR_DSPD_NONDISPLAY: \ x = NONDISPLAY; \ break; \ case ATTR_DSPD_HIGH: \ x = HIGHLIGHT; \ break; \ default: \ x = 0; \ break; \ } # define SetHighlightMode(x) \ { \ if ((x)&HIGHLIGHT) { \ if (!inHighlightMode) { \ inHighlightMode = HIGHLIGHT; \ standout(); \ } \ } else { \ if (inHighlightMode) { \ inHighlightMode = 0; \ standend(); \ } \ } \ } # define DoCharacterAt(c,p) { \ if (p != HighestScreen()) { \ c = disp_asc[c&0xff]; \ if (terminalCursorAddress != p) { \ if (ERR == mvaddch(ScreenLine(p), \ ScreenLineOffset(p), c)) {\ GoAway("mvaddch", p); \ } \ } else { \ if (ERR == addch(c)) {\ GoAway("addch", p); \ } \ } \ terminalCursorAddress = ScreenInc(p); \ } \ } /* run through screen, printing out non-null lines */ /* There are two separate reasons for wanting to terminate this * loop early. One is to respond to new input (either from * the terminal or from the network [host]). For this reason, * we expect to see 'HaveInput' come true when new input comes in. * * The second reason is a bit more difficult (for me) to understand. * Basically, we don't want to get too far ahead of the characters that * appear on the screen. Ideally, we would type out a few characters, * wait until they appeared on the screen, then type out a few more. * The reason for this is that the user, on seeing some characters * appear on the screen may then start to type something. We would * like to look at what the user types at about the same 'time' * (measured by characters being sent to the terminal) that the * user types them. For this reason, what we would like to do * is update a bit, then call curses to do a refresh, flush the * output to the terminal, then wait until the terminal data * has been sent. * * Note that curses is useful for, among other things, deciding whether * or not to send :ce: (clear to end of line), so we should call curses * at end of lines (beginning of next lines). * * The problems here are the following: If we do lots of write(2)s, * we will be doing lots of context switches, thus lots of overhead * (which we have already). Second, if we do a select to wait for * the output to drain, we have to contend with the fact that NOW * we are scheduled to run, but who knows what the scheduler will * decide when the output has caught up. */ if (Highest >= HighestScreen()) { /* Could be > if screen shrunk... */ Highest = ScreenDec(Highest); /* else, while loop will never end */ } if (Lowest < LowestScreen()) { Lowest = LowestScreen(); /* could be -1 in some cases with * unformatted screens. */ } if (Highest >= (pointer = Lowest)) { /* if there is anything to do, do it. We won't terminate * the loop until we've gone at least to Highest. */ while ((pointer <= Highest) && !HaveInput) { /* point at the next place of disagreement */ pointer += (bunequal(Host+pointer, Terminal+pointer, (Highest-pointer+1)*sizeof Host[0])/sizeof Host[0]); /* * How many characters to change until the end of the * current line */ columnsleft = NumberColumns - ScreenLineOffset(pointer); /* * Make sure we are where we think we are. */ move(ScreenLine(pointer), ScreenLineOffset(pointer)); /* what is the field attribute of the current position */ if (FormattedScreen()) { fieldattr = FieldAttributes(pointer); DoAttributes(fieldattr); } else { fieldattr = NORMAL; } if (TerminalFormattedScreen()) { termattr = TermAttributes(pointer); DoAttributes(termattr); } else { termattr = NORMAL; } SetHighlightMode(fieldattr); /* * The following will terminate at least when we get back * to the original 'pointer' location (since we force * things to be equal). */ for (;;) { if (IsStartField(pointer)) { shouldbe = DISP_BLANK; shouldattr = 0; fieldattr = GetHost(pointer); DoAttributes(fieldattr); } else { if (fieldattr&NONDISPLAY) { shouldbe = DISP_BLANK; } else { shouldbe = GetHost(pointer); } shouldattr = fieldattr; } if (TermIsStartField(pointer)) { is = DISP_BLANK; isattr = 0; termattr = UNDETERMINED; /* Need to find out AFTER update */ } else { if (termattr&NONDISPLAY) { is = DISP_BLANK; } else { is = GetTerminal(pointer); } isattr = termattr; } if ((shouldbe == is) && (shouldattr == isattr) && (GetHost(pointer) == GetTerminal(pointer)) && (GetHost(ScreenInc(pointer)) == GetTerminal(ScreenInc(pointer)))) { break; } if (shouldattr^inHighlightMode) { SetHighlightMode(shouldattr); } DoCharacterAt(shouldbe, pointer); if (IsStartField(pointer)) { TermNewField(pointer, FieldAttributes(pointer)); termattr = GetTerminal(pointer); DoAttributes(termattr); } else { SetTerminal(pointer, GetHost(pointer)); /* * If this USED to be a start field location, * recompute the terminal attributes. */ if (termattr == UNDETERMINED) { termattr = WhereTermAttrByte(pointer); if ((termattr != 0) || TermIsStartField(0)) { termattr = GetTerminal(termattr); DoAttributes(termattr); } else { /* Unformatted screen */ termattr = NORMAL; } } } pointer = ScreenInc(pointer); if (!(--columnsleft)) { DoARefresh(); EmptyTerminal(); if (HaveInput) { /* if input came in, take it */ int c, j; /* * We need to start a new terminal field * at this location iff the terminal attributes * of this location are not what we have had * them as (ie: we've overwritten the terminal * start field, a the previous field had different * display characteristics). */ isattr = TermAttributes(pointer); DoAttributes(isattr); if ((!TermIsStartField(pointer)) && (isattr != termattr)) { /* * Since we are going to leave a new field * at this terminal position, we * need to make sure that we get an actual * non-highlighted blank on the screen. */ if ((is != DISP_BLANK) || (termattr&HIGHLIGHT)) { SetHighlightMode(0); /* Turn off highlight */ c = ScreenInc(pointer); j = DISP_BLANK; DoCharacterAt(j, c); } if (termattr&HIGHLIGHT) { termattr = ATTR_DSPD_HIGH; } else if (termattr&NONDISPLAY) { termattr = ATTR_DSPD_NONDISPLAY; } else { termattr = 0; } TermNewField(pointer, termattr); } break; } move(ScreenLine(pointer), 0); columnsleft = NumberColumns; } } /* end of for (;;) */ } /* end of while (...) */ } DoARefresh(); Lowest = pointer; if (Lowest > Highest) { /* if we finished input... */ Lowest = HighestScreen()+1; Highest = LowestScreen()-1; terminalCursorAddress = CorrectTerminalCursor(); if (ERR == move(ScreenLine(terminalCursorAddress), ScreenLineOffset(terminalCursorAddress))) { GoAway("move", terminalCursorAddress); } DoARefresh(); if (needToRing) { StringToTerminal(bellSequence); needToRing = 0; } } EmptyTerminal(); /* move data along */ return; } #if defined(NOT43) static int #else /* defined(NOT43) */ static void #endif /* defined(NOT43) */ FastScreen() { #if defined(MSDOS) #define SaveCorner 0 #else /* defined(MSDOS) */ #define SaveCorner 1 #endif /* defined(MSDOS) */ #define DoAttribute(a) if (IsHighlightedAttr(a)) { \ standout(); \ } else { \ standend(); \ } \ if (IsNonDisplayAttr(a)) { \ a = 0; /* zero == don't display */ \ } \ if (!FormattedScreen()) { \ a = 1; /* one ==> do display on unformatted */\ } ScreenImage *p, *upper; int fieldattr; /* spends most of its time == 0 or 1 */ /* OK. We want to do this a quickly as possible. So, we assume we * only need to go from Lowest to Highest. However, if we find a * field in the middle, we do the whole screen. * * In particular, we separate out the two cases from the beginning. */ if ((Highest != HighestScreen()) || (Lowest != LowestScreen())) { int columnsleft; move(ScreenLine(Lowest), ScreenLineOffset(Lowest)); p = &Host[Lowest]; #if !defined(MSDOS) if (Highest == HighestScreen()) { Highest = ScreenDec(Highest); } #endif /* !defined(MSDOS) */ upper = &Host[Highest]; fieldattr = FieldAttributes(Lowest); DoAttribute(fieldattr); /* Set standout, non-display status */ columnsleft = NumberColumns-ScreenLineOffset(p-Host); while (p <= upper) { if (IsStartFieldPointer(p)) { /* New field? */ Highest = HighestScreen(); Lowest = LowestScreen(); FastScreen(); /* Recurse */ return; } else if (fieldattr) { /* Should we display? */ /* Display translated data */ addch((char)disp_asc[GetTerminalPointer(p)]); } else { addch(' '); /* Display a blank */ } /* If the physical screen is larger than what we * are using, we need to make sure that each line * starts at the beginning of the line. Otherwise, * we will just string all the lines together. */ p++; if (--columnsleft == 0) { int i = p-Host; move(ScreenLine(i), 0); columnsleft = NumberColumns; } } } else { /* Going from Lowest to Highest */ unsigned char tmpbuf[MAXNUMBERCOLUMNS+1]; ScreenImage *End = &Host[ScreenSize]-1-SaveCorner; unsigned char *tmp = tmpbuf, *tmpend = tmpbuf+NumberColumns; *tmpend = 0; /* terminate from the beginning */ move(0,0); p = Host; fieldattr = FieldAttributes(LowestScreen()); DoAttribute(fieldattr); /* Set standout, non-display status */ while (p <= End) { if (IsStartFieldPointer(p)) { /* New field? */ if (tmp != tmpbuf) { *tmp++ = 0; /* close out */ addstr((char *)tmpbuf); tmp = tmpbuf; tmpend = tmpbuf+NumberColumns-ScreenLineOffset(p-Host)-1; } standend(); addch(' '); fieldattr = FieldAttributesPointer(p); /* Get attributes */ DoAttribute(fieldattr); /* Set standout, non-display */ } else { if (fieldattr) { /* Should we display? */ /* Display translated data */ *tmp++ = disp_asc[GetTerminalPointer(p)]; } else { *tmp++ = ' '; } } /* If the physical screen is larger than what we * are using, we need to make sure that each line * starts at the beginning of the line. Otherwise, * we will just string all the lines together. */ p++; if (tmp == tmpend) { int i = p-Host; /* Be sure the "p++" happened first! */ *tmp++ = 0; addstr((char *)tmpbuf); tmp = tmpbuf; move(ScreenLine(i), 0); tmpend = tmpbuf + NumberColumns; } } if (tmp != tmpbuf) { *tmp++ = 0; addstr((char *)tmpbuf); tmp = tmpbuf; } } Lowest = HighestScreen()+1; Highest = LowestScreen()-1; terminalCursorAddress = CorrectTerminalCursor(); if (ERR == move(ScreenLine(terminalCursorAddress), ScreenLineOffset(terminalCursorAddress))) { GoAway("move", terminalCursorAddress); } DoARefresh(); if (needToRing) { StringToTerminal(bellSequence); needToRing = 0; } EmptyTerminal(); /* move data along */ return; } /* TryToSend - send data out to user's terminal */ #if defined(NOT43) int #else /* defined(NOT43) */ void #endif /* defined(NOT43) */ (*TryToSend)() = FastScreen; /*ARGSUSED*/ void ScreenOIA(oia) OIA *oia; { } /* InitTerminal - called to initialize the screen, etc. */ void InitTerminal() { #if defined(unix) struct termios term; speed_t speed; #endif extern void InitMapping(); InitMapping(); /* Go do mapping file (MAP3270) first */ if (!screenInitd) { /* not initialized */ if (initscr() == NULL) {/* Initialize curses to get line size */ ExitString("InitTerminal: Error initializing curses", 1); /*NOTREACHED*/ } MaxNumberLines = LINES; MaxNumberColumns = COLS; ClearArray(Terminal); terminalCursorAddress = SetBufferAddress(0,0); #if defined(unix) signal(SIGHUP, (void (*))abort); #endif TryToSend = FastScreen; #if defined(unix) tcgetattr(1, &term); speed = cfgetospeed(&term); if ((speed < 0) || (speed > 9600)) { max_changes_before_poll = 1920; } else { max_changes_before_poll = speed/10; if (max_changes_before_poll < 40) { max_changes_before_poll = 40; } TryToSend = SlowScreen; HaveInput = 1; /* get signals going */ } #endif /* defined(unix) */ setcommandmode(); /* * By now, initscr() (in curses) has been called (from telnet.c), * and the screen has been initialized. */ #if defined(unix) nonl(); /* the problem is that curses catches SIGTSTP to * be nice, but it messes us up. */ signal(SIGTSTP, SIG_DFL); if ((myKS = tigetstr("smkx")) != 0) { myKS = strdup(myKS); StringToTerminal(myKS); } if ((myKE = tigetstr("rmkx")) != 0) { myKE = strdup(myKE); } /* XXX - why? */ if (tigetstr("bold") && tigetstr("sgr0")) { enter_standout_mode = strdup(tigetstr("bold")); exit_standout_mode = strdup(tigetstr("sgr0")); } #endif DoARefresh(); setconnmode(); if (flash_screen && *flash_screen) { bellSequence = flash_screen; /* use visual bell */ } screenInitd = 1; screenStopped = 0; /* Not stopped */ } } /* StopScreen - called when we are going away... */ void StopScreen(doNewLine) int doNewLine; { if (screenInitd && !screenStopped) { move(NumberLines-1, 1); standend(); inHighlightMode = 0; DoARefresh(); setcommandmode(); endwin(); setconnmode(); #if defined(unix) if (myKE) { StringToTerminal(myKE); } #endif /* defined(unix) */ if (doNewLine) { StringToTerminal("\r\n"); } EmptyTerminal(); screenStopped = 1; /* This is stopped */ } } /* RefreshScreen - called to cause the screen to be refreshed */ void RefreshScreen() { clearok(curscr, TRUE); (*TryToSend)(); } /* ConnectScreen - called to reconnect to the screen */ void ConnectScreen() { if (screenInitd) { #if defined(unix) if (myKS) { StringToTerminal(myKS); } #endif /* defined(unix) */ RefreshScreen(); (*TryToSend)(); screenStopped = 0; } } /* LocalClearScreen() - clear the whole ball of wax, cheaply */ void LocalClearScreen() { extern void Clear3270(); outputPurge(); /* flush all data to terminal */ clear(); /* clear in curses */ ClearArray(Terminal); Clear3270(); Lowest = HighestScreen()+1; /* everything in sync... */ Highest = LowestScreen()+1; } void BellOff() { if (bellwinup) { delwin(bellwin); bellwin = 0; bellwinup = 0; touchwin(stdscr); DoARefresh(); } } void RingBell(s) char *s; { needToRing = 1; if (s) { int len = strlen(s); if (len > COLS-2) { len = COLS-2; } if ((bellwin = newwin(3, len+2, LINES/2, 0)) == NULL) { OurExitString("Error from newwin in RingBell", 1); } werase(bellwin); wstandout(bellwin); box(bellwin, '|', '-'); if (wmove(bellwin, 1, 1) == ERR) { OurExitString("Error from wmove in RingBell", 1); } while (len--) { if (waddch(bellwin, *s++) == ERR) { OurExitString("Error from waddch in RingBell", 1); } } wstandend(bellwin); if (wrefresh(bellwin) == ERR) { OurExitString("Error from wrefresh in RingBell", 1); } bellwinup = 1; } } /* returns a 1 if no more output available (so, go ahead and block), or a 0 if there is more output available (so, just poll the other sources/destinations, don't block). */ int DoTerminalOutput() { /* called just before a select to conserve IO to terminal */ if (!(screenInitd||screenStopped)) { return 1; /* No output if not initialized */ } if ((Lowest <= Highest) || needToRing || (terminalCursorAddress != CorrectTerminalCursor())) { (*TryToSend)(); } if (Lowest > Highest) { return 1; /* no more output now */ } else { return 0; /* more output for future */ } } /* * The following are defined to handle transparent data. */ void TransStop() { #if defined(unix) if (tcflag == 0) { tcflag = -1; (void) signal(SIGCHLD, SIG_DFL); } else if (tcflag > 0) { setcommandmode(); (void) close(tin); (void) close(tout); tin = savefd[0]; tout = savefd[1]; setconnmode(); tcflag = -1; (void) signal(SIGCHLD, SIG_DFL); } #endif /* defined(unix) */ RefreshScreen(); } void TransOut(buffer, count, kind, control) unsigned char *buffer; int count; int kind; /* 0 or 5 */ int control; /* To see if we are done */ { #if defined(unix) extern char *transcom; int inpipefd[2], outpipefd[2]; #endif /* defined(unix) */ while (DoTerminalOutput() == 0) { #if defined(unix) HaveInput = 0; #endif /* defined(unix) */ } #if defined(unix) if (transcom && tcflag == -1) { while (1) { /* go thru once */ if (pipe(outpipefd) < 0) { break; } if (pipe(inpipefd) < 0) { break; } if ((tcflag = fork()) == 0) { (void) close(outpipefd[1]); (void) close(0); if (dup(outpipefd[0]) < 0) { exit(1); } (void) close(outpipefd[0]); (void) close(inpipefd[0]); (void) close(1); if (dup(inpipefd[1]) < 0) { exit(1); } (void) close(inpipefd[1]); if (execl("/bin/csh", "csh", "-c", transcom, (char *) 0)) { exit(1); } } (void) close(inpipefd[1]); (void) close(outpipefd[0]); savefd[0] = tin; savefd[1] = tout; setcommandmode(); tin = inpipefd[0]; tout = outpipefd[1]; (void) signal(SIGCHLD, aborttc); setconnmode(); tcflag = 1; break; } if (tcflag < 1) { tcflag = 0; } } #endif /* defined(unix) */ (void) DataToTerminal((char *)buffer, count); if (control && (kind == 0)) { /* Send in AID byte */ SendToIBM(); } else { extern void TransInput(); TransInput(1, kind); /* Go get some data */ } } #if defined(unix) static void aborttc() { setcommandmode(); (void) close(tin); (void) close(tout); tin = savefd[0]; tout = savefd[1]; setconnmode(); tcflag = 0; } #endif /* defined(unix) */