2 * Copyright (c) 1988, 1993
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 static char sccsid[] = "@(#)termout.c 8.1 (Berkeley) 6/6/93";
45 /* Some version of this OS has a bad definition for nonl() */
49 #define nl() (_tty.sg_flags |= CRMOD,_pfast = _rawmode,stty(_tty_ch, &_tty))
50 #define nonl() (_tty.sg_flags &= ~CRMOD, _pfast = TRUE, stty(_tty_ch, &_tty))
51 #endif /* defined(ultrix) */
53 #include "../general/general.h"
57 #include "../api/disp_asc.h"
59 #include "../ctlr/hostctlr.h"
60 #include "../ctlr/externs.h"
61 #include "../ctlr/declare.h"
62 #include "../ctlr/oia.h"
63 #include "../ctlr/screen.h"
64 #include "../ctlr/scrnctlr.h"
66 #include "../general/globals.h"
70 #define CorrectTerminalCursor() ((TransparentClock == OutputClock)? \
71 CursorAddress:UnLocked? CursorAddress: HighestScreen())
74 static int terminalCursorAddress; /* where the cursor is on term */
75 static int screenInitd; /* the screen has been initialized */
76 static int screenStopped; /* the screen has been stopped */
77 static int max_changes_before_poll; /* how many characters before looking */
78 /* at terminal and net again */
80 static int needToRing; /* need to ring terinal bell */
81 static char *bellSequence = "\07"; /* bell sequence (may be replaced by
82 * VB during initialization)
84 static WINDOW *bellwin = 0; /* The window the bell message is in */
85 int bellwinup = 0; /* Are we up with it or not */
88 static char *myKS, *myKE;
89 #endif /* defined(unix) */
92 static int inHighlightMode = 0;
93 ScreenImage Terminal[MAXSCREENSIZE];
95 /* Variables for transparent mode */
97 static int tcflag = -1; /* transparent mode command flag */
98 static int savefd[2]; /* for storing fds during transcom */
99 extern int tin, tout; /* file descriptors */
100 #endif /* defined(unix) */
106 * Initialize variables used by screen.
114 ClearArray(Terminal);
118 /* OurExitString - designed to keep us from going through infinite recursion */
121 OurExitString(string, value)
125 static int recursion = 0;
129 ExitString(string, value);
139 if (ERR == refresh()) {
140 OurExitString("ERR from refresh\n", 1);
146 char *from; /* routine that gave error */
147 int where; /* cursor address */
151 sprintf(foo, "ERR from %s at %d (%d, %d)\n",
152 from, where, ScreenLine(where), ScreenLineOffset(where));
153 OurExitString(foo, 1);
157 /* What is the screen address of the attribute byte for the terminal */
168 if (TermIsStartField(i)) {
174 return(LowestScreen()); /* unformatted screen... */
178 * There are two algorithms for updating the screen.
179 * The first, SlowScreen() optimizes the line between the
180 * computer and the screen (say a 9600 baud line). To do
181 * this, we break out of the loop every so often to look
182 * at any pending input from the network (so that successive
183 * screens will only partially print until the final screen,
184 * the one the user possibly wants to see, is displayed
187 * The second algorithm tries to optimize CPU time (by
188 * being simpler) at the cost of the bandwidth to the
191 * Of course, curses(3X) gets in here also.
197 #else /* defined(NOT43) */
199 #endif /* defined(NOT43) */
202 register int is, shouldbe, isattr, shouldattr;
203 register int pointer;
204 register int fieldattr, termattr;
205 register int columnsleft;
208 #define HIGHLIGHT 1 /* Mask bits */
209 #define NONDISPLAY 4 /* Mask bits */
210 #define UNDETERMINED 8 /* Mask bits */
212 #define DoAttributes(x) \
213 switch (x&ATTR_DSPD_MASK) { \
214 case ATTR_DSPD_NONDISPLAY: \
217 case ATTR_DSPD_HIGH: \
225 # define SetHighlightMode(x) \
227 if ((x)&HIGHLIGHT) { \
228 if (!inHighlightMode) { \
229 inHighlightMode = HIGHLIGHT; \
233 if (inHighlightMode) { \
234 inHighlightMode = 0; \
240 # define DoCharacterAt(c,p) { \
241 if (p != HighestScreen()) { \
242 c = disp_asc[c&0xff]; \
243 if (terminalCursorAddress != p) { \
244 if (ERR == mvaddch(ScreenLine(p), \
245 ScreenLineOffset(p), c)) {\
246 GoAway("mvaddch", p); \
249 if (ERR == addch(c)) {\
250 GoAway("addch", p); \
253 terminalCursorAddress = ScreenInc(p); \
258 /* run through screen, printing out non-null lines */
260 /* There are two separate reasons for wanting to terminate this
261 * loop early. One is to respond to new input (either from
262 * the terminal or from the network [host]). For this reason,
263 * we expect to see 'HaveInput' come true when new input comes in.
265 * The second reason is a bit more difficult (for me) to understand.
266 * Basically, we don't want to get too far ahead of the characters that
267 * appear on the screen. Ideally, we would type out a few characters,
268 * wait until they appeared on the screen, then type out a few more.
269 * The reason for this is that the user, on seeing some characters
270 * appear on the screen may then start to type something. We would
271 * like to look at what the user types at about the same 'time'
272 * (measured by characters being sent to the terminal) that the
273 * user types them. For this reason, what we would like to do
274 * is update a bit, then call curses to do a refresh, flush the
275 * output to the terminal, then wait until the terminal data
278 * Note that curses is useful for, among other things, deciding whether
279 * or not to send :ce: (clear to end of line), so we should call curses
280 * at end of lines (beginning of next lines).
282 * The problems here are the following: If we do lots of write(2)s,
283 * we will be doing lots of context switches, thus lots of overhead
284 * (which we have already). Second, if we do a select to wait for
285 * the output to drain, we have to contend with the fact that NOW
286 * we are scheduled to run, but who knows what the scheduler will
287 * decide when the output has caught up.
290 if (Highest >= HighestScreen()) { /* Could be > if screen shrunk... */
291 Highest = ScreenDec(Highest); /* else, while loop will never end */
293 if (Lowest < LowestScreen()) {
294 Lowest = LowestScreen(); /* could be -1 in some cases with
295 * unformatted screens.
298 if (Highest >= (pointer = Lowest)) {
299 /* if there is anything to do, do it. We won't terminate
300 * the loop until we've gone at least to Highest.
302 while ((pointer <= Highest) && !HaveInput) {
304 /* point at the next place of disagreement */
305 pointer += (bunequal(Host+pointer, Terminal+pointer,
306 (Highest-pointer+1)*sizeof Host[0])/sizeof Host[0]);
309 * How many characters to change until the end of the
312 columnsleft = NumberColumns - ScreenLineOffset(pointer);
314 * Make sure we are where we think we are.
316 move(ScreenLine(pointer), ScreenLineOffset(pointer));
318 /* what is the field attribute of the current position */
319 if (FormattedScreen()) {
320 fieldattr = FieldAttributes(pointer);
321 DoAttributes(fieldattr);
325 if (TerminalFormattedScreen()) {
326 termattr = TermAttributes(pointer);
327 DoAttributes(termattr);
332 SetHighlightMode(fieldattr);
334 * The following will terminate at least when we get back
335 * to the original 'pointer' location (since we force
336 * things to be equal).
339 if (IsStartField(pointer)) {
340 shouldbe = DISP_BLANK;
342 fieldattr = GetHost(pointer);
343 DoAttributes(fieldattr);
345 if (fieldattr&NONDISPLAY) {
346 shouldbe = DISP_BLANK;
348 shouldbe = GetHost(pointer);
350 shouldattr = fieldattr;
352 if (TermIsStartField(pointer)) {
355 termattr = UNDETERMINED; /* Need to find out AFTER update */
357 if (termattr&NONDISPLAY) {
360 is = GetTerminal(pointer);
364 if ((shouldbe == is) && (shouldattr == isattr)
365 && (GetHost(pointer) == GetTerminal(pointer))
366 && (GetHost(ScreenInc(pointer))
367 == GetTerminal(ScreenInc(pointer)))) {
371 if (shouldattr^inHighlightMode) {
372 SetHighlightMode(shouldattr);
375 DoCharacterAt(shouldbe, pointer);
376 if (IsStartField(pointer)) {
377 TermNewField(pointer, FieldAttributes(pointer));
378 termattr = GetTerminal(pointer);
379 DoAttributes(termattr);
381 SetTerminal(pointer, GetHost(pointer));
383 * If this USED to be a start field location,
384 * recompute the terminal attributes.
386 if (termattr == UNDETERMINED) {
387 termattr = WhereTermAttrByte(pointer);
388 if ((termattr != 0) || TermIsStartField(0)) {
389 termattr = GetTerminal(termattr);
390 DoAttributes(termattr);
391 } else { /* Unformatted screen */
396 pointer = ScreenInc(pointer);
397 if (!(--columnsleft)) {
400 if (HaveInput) { /* if input came in, take it */
404 * We need to start a new terminal field
405 * at this location iff the terminal attributes
406 * of this location are not what we have had
407 * them as (ie: we've overwritten the terminal
408 * start field, a the previous field had different
409 * display characteristics).
412 isattr = TermAttributes(pointer);
413 DoAttributes(isattr);
414 if ((!TermIsStartField(pointer)) &&
415 (isattr != termattr)) {
417 * Since we are going to leave a new field
418 * at this terminal position, we
419 * need to make sure that we get an actual
420 * non-highlighted blank on the screen.
422 if ((is != DISP_BLANK) || (termattr&HIGHLIGHT)) {
423 SetHighlightMode(0); /* Turn off highlight */
424 c = ScreenInc(pointer);
428 if (termattr&HIGHLIGHT) {
429 termattr = ATTR_DSPD_HIGH;
430 } else if (termattr&NONDISPLAY) {
431 termattr = ATTR_DSPD_NONDISPLAY;
435 TermNewField(pointer, termattr);
439 move(ScreenLine(pointer), 0);
440 columnsleft = NumberColumns;
442 } /* end of for (;;) */
443 } /* end of while (...) */
447 if (Lowest > Highest) { /* if we finished input... */
448 Lowest = HighestScreen()+1;
449 Highest = LowestScreen()-1;
450 terminalCursorAddress = CorrectTerminalCursor();
451 if (ERR == move(ScreenLine(terminalCursorAddress),
452 ScreenLineOffset(terminalCursorAddress))) {
453 GoAway("move", terminalCursorAddress);
457 StringToTerminal(bellSequence);
461 EmptyTerminal(); /* move data along */
467 #else /* defined(NOT43) */
469 #endif /* defined(NOT43) */
474 #else /* defined(MSDOS) */
476 #endif /* defined(MSDOS) */
478 #define DoAttribute(a) if (IsHighlightedAttr(a)) { \
483 if (IsNonDisplayAttr(a)) { \
484 a = 0; /* zero == don't display */ \
486 if (!FormattedScreen()) { \
487 a = 1; /* one ==> do display on unformatted */\
489 ScreenImage *p, *upper;
490 int fieldattr; /* spends most of its time == 0 or 1 */
492 /* OK. We want to do this a quickly as possible. So, we assume we
493 * only need to go from Lowest to Highest. However, if we find a
494 * field in the middle, we do the whole screen.
496 * In particular, we separate out the two cases from the beginning.
498 if ((Highest != HighestScreen()) || (Lowest != LowestScreen())) {
499 register int columnsleft;
501 move(ScreenLine(Lowest), ScreenLineOffset(Lowest));
504 if (Highest == HighestScreen()) {
505 Highest = ScreenDec(Highest);
507 #endif /* !defined(MSDOS) */
508 upper = &Host[Highest];
509 fieldattr = FieldAttributes(Lowest);
510 DoAttribute(fieldattr); /* Set standout, non-display status */
511 columnsleft = NumberColumns-ScreenLineOffset(p-Host);
514 if (IsStartFieldPointer(p)) { /* New field? */
515 Highest = HighestScreen();
516 Lowest = LowestScreen();
517 FastScreen(); /* Recurse */
519 } else if (fieldattr) { /* Should we display? */
520 /* Display translated data */
521 addch((char)disp_asc[GetTerminalPointer(p)]);
523 addch(' '); /* Display a blank */
525 /* If the physical screen is larger than what we
526 * are using, we need to make sure that each line
527 * starts at the beginning of the line. Otherwise,
528 * we will just string all the lines together.
531 if (--columnsleft == 0) {
534 move(ScreenLine(i), 0);
535 columnsleft = NumberColumns;
538 } else { /* Going from Lowest to Highest */
539 unsigned char tmpbuf[MAXNUMBERCOLUMNS+1];
540 ScreenImage *End = &Host[ScreenSize]-1-SaveCorner;
541 register unsigned char *tmp = tmpbuf, *tmpend = tmpbuf+NumberColumns;
543 *tmpend = 0; /* terminate from the beginning */
546 fieldattr = FieldAttributes(LowestScreen());
547 DoAttribute(fieldattr); /* Set standout, non-display status */
550 if (IsStartFieldPointer(p)) { /* New field? */
552 *tmp++ = 0; /* close out */
553 addstr((char *)tmpbuf);
555 tmpend = tmpbuf+NumberColumns-ScreenLineOffset(p-Host)-1;
559 fieldattr = FieldAttributesPointer(p); /* Get attributes */
560 DoAttribute(fieldattr); /* Set standout, non-display */
562 if (fieldattr) { /* Should we display? */
563 /* Display translated data */
564 *tmp++ = disp_asc[GetTerminalPointer(p)];
569 /* If the physical screen is larger than what we
570 * are using, we need to make sure that each line
571 * starts at the beginning of the line. Otherwise,
572 * we will just string all the lines together.
576 int i = p-Host; /* Be sure the "p++" happened first! */
579 addstr((char *)tmpbuf);
581 move(ScreenLine(i), 0);
582 tmpend = tmpbuf + NumberColumns;
587 addstr((char *)tmpbuf);
591 Lowest = HighestScreen()+1;
592 Highest = LowestScreen()-1;
593 terminalCursorAddress = CorrectTerminalCursor();
594 if (ERR == move(ScreenLine(terminalCursorAddress),
595 ScreenLineOffset(terminalCursorAddress))) {
596 GoAway("move", terminalCursorAddress);
600 StringToTerminal(bellSequence);
603 EmptyTerminal(); /* move data along */
608 /* TryToSend - send data out to user's terminal */
612 #else /* defined(NOT43) */
614 #endif /* defined(NOT43) */
615 (*TryToSend)() = FastScreen;
625 /* InitTerminal - called to initialize the screen, etc. */
631 struct sgttyb ourttyb;
632 static int speeds[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800,
635 extern void InitMapping();
637 InitMapping(); /* Go do mapping file (MAP3270) first */
638 if (!screenInitd) { /* not initialized */
640 char KSEbuffer[2050];
641 char *lotsofspace = KSEbuffer;
643 extern char *tgetstr();
644 #endif /* defined(unix) */
646 if (initscr() == ERR) { /* Initialize curses to get line size */
647 ExitString("InitTerminal: Error initializing curses", 1);
650 MaxNumberLines = LINES;
651 MaxNumberColumns = COLS;
652 ClearArray(Terminal);
653 terminalCursorAddress = SetBufferAddress(0,0);
655 signal(SIGHUP, abort);
658 TryToSend = FastScreen;
660 ioctl(1, TIOCGETP, (char *) &ourttyb);
661 if ((ourttyb.sg_ospeed < 0) || (ourttyb.sg_ospeed > B9600)) {
662 max_changes_before_poll = 1920;
664 max_changes_before_poll = speeds[ourttyb.sg_ospeed]/10;
665 if (max_changes_before_poll < 40) {
666 max_changes_before_poll = 40;
668 TryToSend = SlowScreen;
669 HaveInput = 1; /* get signals going */
671 #endif /* defined(unix) */
674 * By now, initscr() (in curses) has been called (from telnet.c),
675 * and the screen has been initialized.
679 /* the problem is that curses catches SIGTSTP to
680 * be nice, but it messes us up.
682 signal(SIGTSTP, SIG_DFL);
683 if ((myKS = tgetstr("ks", &lotsofspace)) != 0) {
684 myKS = strsave(myKS);
685 StringToTerminal(myKS);
687 if ((myKE = tgetstr("ke", &lotsofspace)) != 0) {
688 myKE = strsave(myKE);
690 if (tgetstr("md", &lotsofspace) && tgetstr("me", &lotsofspace)) {
691 SO = strsave(tgetstr("md", &lotsofspace));
692 SE = strsave(tgetstr("me", &lotsofspace));
698 bellSequence = VB; /* use visual bell */
701 screenStopped = 0; /* Not stopped */
706 /* StopScreen - called when we are going away... */
709 StopScreen(doNewLine)
712 if (screenInitd && !screenStopped) {
713 move(NumberLines-1, 1);
722 StringToTerminal(myKE);
724 #endif /* defined(unix) */
726 StringToTerminal("\r\n");
729 screenStopped = 1; /* This is stopped */
734 /* RefreshScreen - called to cause the screen to be refreshed */
739 clearok(curscr, TRUE);
744 /* ConnectScreen - called to reconnect to the screen */
752 StringToTerminal(myKS);
754 #endif /* defined(unix) */
761 /* LocalClearScreen() - clear the whole ball of wax, cheaply */
766 extern void Clear3270();
768 outputPurge(); /* flush all data to terminal */
769 clear(); /* clear in curses */
770 ClearArray(Terminal);
772 Lowest = HighestScreen()+1; /* everything in sync... */
773 Highest = LowestScreen()+1;
801 if ((bellwin = newwin(3, len+2, LINES/2, 0)) == NULL) {
802 OurExitString("Error from newwin in RingBell", 1);
806 box(bellwin, '|', '-');
807 if (wmove(bellwin, 1, 1) == ERR) {
808 OurExitString("Error from wmove in RingBell", 1);
811 if (waddch(bellwin, *s++) == ERR) {
812 OurExitString("Error from waddch in RingBell", 1);
816 if (wrefresh(bellwin) == ERR) {
817 OurExitString("Error from wrefresh in RingBell", 1);
824 /* returns a 1 if no more output available (so, go ahead and block),
825 or a 0 if there is more output available (so, just poll the other
826 sources/destinations, don't block).
832 /* called just before a select to conserve IO to terminal */
833 if (!(screenInitd||screenStopped)) {
834 return 1; /* No output if not initialized */
836 if ((Lowest <= Highest) || needToRing ||
837 (terminalCursorAddress != CorrectTerminalCursor())) {
840 if (Lowest > Highest) {
841 return 1; /* no more output now */
843 return 0; /* more output for future */
848 * The following are defined to handle transparent data.
857 (void) signal(SIGCHLD, SIG_DFL);
858 } else if (tcflag > 0) {
866 (void) signal(SIGCHLD, SIG_DFL);
868 #endif /* defined(unix) */
873 TransOut(buffer, count, kind, control)
874 unsigned char *buffer;
876 int kind; /* 0 or 5 */
877 int control; /* To see if we are done */
880 extern char *transcom;
881 int inpipefd[2], outpipefd[2];
882 static void aborttc();
883 #endif /* defined(unix) */
885 while (DoTerminalOutput() == 0) {
888 #endif /* defined(unix) */
891 if (transcom && tcflag == -1) {
892 while (1) { /* go thru once */
893 if (pipe(outpipefd) < 0) {
896 if (pipe(inpipefd) < 0) {
899 if ((tcflag = fork()) == 0) {
900 (void) close(outpipefd[1]);
902 if (dup(outpipefd[0]) < 0) {
905 (void) close(outpipefd[0]);
906 (void) close(inpipefd[0]);
908 if (dup(inpipefd[1]) < 0) {
911 (void) close(inpipefd[1]);
912 if (execl("/bin/csh", "csh", "-c", transcom, (char *) 0)) {
916 (void) close(inpipefd[1]);
917 (void) close(outpipefd[0]);
923 (void) signal(SIGCHLD, aborttc);
932 #endif /* defined(unix) */
933 (void) DataToTerminal((char *)buffer, count);
934 if (control && (kind == 0)) { /* Send in AID byte */
937 extern void TransInput();
939 TransInput(1, kind); /* Go get some data */
957 #endif /* defined(unix) */