|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T p
Length: 21268 (0x5314) Types: TextFile Names: »p1trm.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki └─⟦this⟧ »EUUGD11/euug-87hel/sec1/vtrm/p1trm.c«
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* * ibm Pc virtual TeRMinal package. * * An implementation of the VTRM interface for MS-DOS machines. * * This code supports two MODE's of accessing the screen. * The first one (BIOS) will be used, unless the user overwrites this * by setting the SCREEN environment variable. * This variable can also be used to convey a screen size that differs * from the default 25 lines and 80 columns. See below. * * The two modes are: * * 1) IBM BIOS interrupt 10 hex, video io. * (See IBM PC XT Technical Reference 6936833, May 1983, * Appendix A, pages A46-A47). * This is what you really want to use, since it's the only one that * can decently scroll. It cannot insert or delete characters, so * most optimisations from vtrm.c are useless and taken out. * Unfortunately, not every PC-compatible machine supports this BIOS * interrupt, so for these unlucky souls there is the following escape: * * 2) The ANSI.SYS driver. * (See IBM MS-DOS 6936839, Jan 1983, Version 2.00, Chapter 13.) * (Some compatibles don't have a separate ANSI.SYS driver but do the * same escape interpretation by default.) * This works reasonably, apart from scrolling downward, or part of * the screen, which is clumsy. * (The ANSI standard provides an escape sequence for scrolling * but ANSI.SYS does not support it, nor any other way of scrolling.) * * The rest of the interface is the same as described in vtrm.c, * with the following exceptions: * - to ease coding for ansi scrolls, the terminal is supposed to * contain blanks at positions that were not written yet; * the unknown rubbish that is initially on the screen can * only be cleared by the caller by scrolling the whole screen up * by one or more lines; * - the number of lines on the terminal is assumed to be 25; * the number of columns is (1) determined by a BIOS function, or * (2) assumed to be 80 for ANSI; * the user can overwrite this by setting the environment variable: * * SET SCREEN=BIOS x y * or * SET SCREEN=ANSI x y * * where x and y are the number of lines and columns respectively. * * The lines and columns of our virtual terminal are numbered * y = {0...lines-1} from top to bottom, and * x = {0...cols-1} from left to right, * respectively. * * The Visible Procedures in this package are as described in vtrm.c. * */ /* * Includes and data definitions. */ #include <stdio.h> #include <signal.h> #include <ctype.h> #include <dos.h> char *malloc(); #include "trm.h" #ifdef lint #define VOID (void) #else #define VOID #endif #define Forward #define Visible #define Hidden static #define Procedure typedef short intlet; typedef char *string; typedef char bool; #define Yes '\1' #define No '\0' #define Undefined (-1) #define Min(a,b) ((a) <= (b) ? (a) : (b)) #define MESS(number, text) text #ifdef GFX #include "gfx.h" #endif /* terminal status */ Hidden int started = No; Hidden int scr_mode = 0; #define ANSI 'A' #define BIOS 'B' #define Nlines 25 #define Ncols 80 Hidden int lines = Nlines; Hidden int cols = Ncols; Hidden int flags = 0; /* current standout mode */ #define Off 0 #define On 0200 Hidden int so_mode = Off; /* masks for char's and intlet's */ #define NULCHAR '\000' #define CHAR 0177 #define SOBIT On #define SOCHAR 0377 /* current cursor position */ Hidden intlet cur_y = Undefined, cur_x = Undefined; /* "line[y][x]" holds the char on the terminal, with the SOBIT. * the SOBIT tells whether the character is standing out. * "lenline[y]" holds the length of the line. * (Partially) empty lines are distinghuished by "lenline[y] < cols". * Unknown chars will be ' ', so the scrolling routines for ANSI * can use "unwritten" chars (with indent > 0 in trmputdata). * To make the optimising compare in putline fail, lenline[y] is initially 0. * The latter implies that if a line is first addressed with trmputdata, * any rubbish that is on the screen beyond the data that gets put, will * remain there. */ Hidden char **line = 0; Hidden intlet *lenline = 0; /* Make the cursor invisible when trmsync() tries to move outside the screen */ Hidden bool no_cursor = No; /* * Starting, Ending and (fatal) Error. */ /* * Initialization call. * Determine terminal mode. * Start up terminal and internal administration. * Return Yes if succeeded, No if trouble (which doesn't apply here). */ Visible int trmstart(plines, pcols, pflags) int *plines; int *pcols; int *pflags; { static char setup = No; int err; #ifdef TRACE if (!setup) freopen("TRACE.DAT", "a", stderr); fprintf(stderr, "\ttrmstart(&li, &co, &fl);\n"); #endif if (started) return TE_TWICE; #ifdef GFX if (gfx_mode != TEXT_MODE) gfx_mode= SPLIT_MODE; #endif if (!setup) { err= set_screen_up(); if (err != TE_OK) return err; setup = Yes; } err= start_trm(); /* internal administration */ if (err != TE_OK) return err; *plines = lines; *pcols = cols; *pflags = flags; set_handler(); started = Yes; return TE_OK; } Hidden int set_screen_up() { int height; int width; int get_screen_env(); int get_cols(); height = width = 0; scr_mode = get_screen_env(&height, &width); switch (scr_mode) { case BIOS: case TE_OK: cols = get_cols(); flags = HAS_STANDOUT|CAN_SCROLL; break; case ANSI: flags = HAS_STANDOUT; break; default: return scr_mode; /* Error flag */ } /* allow x and y in environment variable SCREEN to override */ if (height > 0) lines = height; if (width > 0) cols = width; return TE_OK; } Hidden int get_screen_env(pheight, pwidth) int *pheight, *pwidth; { string s; int mode; char screrr; string getenv(); string strip(); string skip(); screrr = No; s = getenv("SCREEN"); if (s == NULL) return BIOS; s = strip(s); switch (*s) { case '\0': return BIOS; case 'a': mode = ANSI; s = skip(s, "ansi"); break; case 'A': mode = ANSI; s = skip(s, "ANSI"); break; case 'b': mode = BIOS; s = skip(s, "bios"); break; case 'B': mode = BIOS; s = skip(s, "BIOS"); break; default: mode = BIOS; screrr = Yes; } /* *pheight and *pwidth were set to 0 above */ s = strip(s); while (isdigit(*s)) { *pheight = *pheight * 10 + (*s++ - '0'); } s = strip(s); while (isdigit(*s)) { *pwidth = *pwidth * 10 + (*s++ -'0'); } s = strip(s); if (screrr || *s != '\0') return TE_BADTERM; return mode; } Hidden string strip(s) string s; { while (*s == ' ' || *s == '\t') ++s; return s; } Hidden string skip(s, pat) string s, pat; { while (*s == *pat) ++s, ++pat; return s; } Hidden int /* initialise internal administration */ start_trm() { register int y; if (line == 0) { if ((line = (char**) malloc(lines * sizeof(char*))) == NULL) return TE_NOMEM; for (y = 0; y < lines; y++) { if ((line[y] = malloc(cols * sizeof(char))) == NULL) return TE_NOMEM; } } if (lenline == 0) { if ((lenline = (intlet *) malloc(lines * sizeof(intlet))) == NULL) return TE_NOMEM; } trmundefined(); return TE_OK; } /* * Termination call. * Beware that it might be called by a catched interrupt even in the middle * of trmstart()! */ Visible Procedure trmend() { #ifdef TRACE fprintf(stderr, "\ttrmend();\n"); #endif if (started && so_mode != Off) standend(); if (scr_mode == ANSI) { VOID fflush(stdout); } started = No; } /* * Set all internal statuses to undefined, especially the contents of * the screen, so a hard redraw will not be optimised to heaven. */ Visible Procedure trmundefined() { register int y, x; #ifdef TRACE fprintf(stderr, "\ttrmundefined();\n"); #endif cur_y = cur_x = Undefined; so_mode = Undefined; for (y = 0; y < lines; y++) { for (x = 0; x < cols; x++) line[y][x] = ' '; /* they may get printed in scrolling */ lenline[y] = 0; } } #ifdef DEBUG Hidden Procedure check_started(m) char *m; { if (!started) { printf("Not started: %s\n", m); exit(TE_TWICE); } } #else #define check_started(m) /*empty*/ #endif /* * Sensing the cursor. * (NOT IMPLEMENTED, since there is no way to locally move the cursor.) */ /* * Sense the current (y, x) cursor position, after a possible manual * change by the user with local cursor motions. * If the terminal cannot be asked for the current cursor position, * or if the string returned by the terminal is garbled, * the position is made Undefined. */ Visible Procedure trmsense(py, px) int *py; int *px; { /* bool getpos(); */ #ifdef TRACE fprintf(stderr, "\ttrmsense(&yy, &xx);\n"); #endif check_started(MESS(7904, "trmsense called outside trmstart/trmend")); *py = *px = Undefined; /* * if (flags&CAN_SENSE && getpos(py, px)) { * if (*py < 0 || lines <= *py || *px < 0 || cols <= *px) * *py = *px = Undefined; * } */ cur_y = *py; cur_x = *px; } /* * Putting data on the screen. */ /* * Fill screen area with given data. * Characters with the SO-bit (0200) set are put in standout mode. * (Unfortunately this makes it impossible to display accented characters. * The interface should change.) */ Visible Procedure trmputdata(yfirst, ylast, indent, data) int yfirst; int ylast; register int indent; register string data; { register int y; int x, len, lendata, space; #ifdef TRACE fprintf(stderr, "\ttrmputdata(%d, %d, %d, \"%s\");\n", yfirst, ylast, indent, data); #endif check_started(MESS(7905, "trmputdata called outside trmstart/trmend")); if (yfirst < 0) yfirst = 0; if (ylast >= lines) ylast = lines-1; space = cols*(ylast-yfirst+1) - indent; if (space <= 0) return; yfirst += indent/cols; indent %= cols; y = yfirst; if (data) { x = indent; lendata = strlen(data); if (ylast == lines-1 && lendata >= space) lendata = space - 1; len = Min(lendata, cols-x); while (y <= ylast) { put_line(y, x, data, len); y++; lendata -= len; if (lendata > 0) { x = 0; data += len; len = Min(lendata, cols); } else break; } } if (y <= ylast) clear_lines(y, ylast); } /* * We will try to get the picture: * * op>>>>>>>>>>>op oq * ^ ^ ^ * <xskip><-----m1----><---------------od--------------------> * OLD: "You're in a maze of twisty little pieces of code, all alike" * NEW: "in a maze of little twisting pieces of code, all alike" * <-----m1----><----------------nd---------------------> * ^ ^ ^ * np>>>>>>>>>>>np nq * where * op, oq, np, nq are pointers to start and end of Old and New data, * and * xskip = length of indent to be skipped, * m1 = length of Matching part at start, * od = length of Differing end on screen, * nd = length of Differing end in data to be put. */ Hidden int put_line(y, xskip, data, len) int y, xskip; string data; int len; { register char *op, *oq, *np, *nq; int m1, od, nd, delta; /* calculate the magic parameters */ op = &line[y][xskip]; oq = &line[y][lenline[y]-1]; np = data; nq = data + len - 1; m1 = 0; while ((*op&SOCHAR) == (*np&SOCHAR) && op <= oq && np <= nq) op++, np++, m1++; od = oq - op + 1; nd = nq - np + 1; /* now we have the picture above */ if (od==0 && nd==0) return; delta = nd - od; move(y, xskip + m1); if (nd > 0) { put_str(np, nd); } if (delta < 0) { clr_to_eol(); return; } lenline[y] = xskip + len; if (cur_x == cols) { cur_y++; cur_x = 0; } } /* * Scrolling (part of) the screen up (or down, dy<0). */ Visible Procedure trmscrollup(yfirst, ylast, by) register int yfirst; register int ylast; register int by; { #ifdef TRACE fprintf(stderr, "\ttrmscrollup(%d, %d, %d);\n", yfirst, ylast, by); #endif check_started(MESS(7906, "trmscrollup called outside trmstart/trmend")); if (by == 0) return; if (yfirst < 0) yfirst = 0; if (ylast >= lines) ylast = lines-1; if (yfirst > ylast) return; if (so_mode != Off) standend(); if (by > 0 && yfirst + by > ylast || by < 0 && yfirst - by > ylast) { clear_lines(yfirst, ylast); return; } switch (scr_mode) { case BIOS: biosscrollup(yfirst, ylast, by); break; case ANSI: if (by > 0 && yfirst == 0) { lf_scroll(ylast, by); } else if (by > 0) { move_lines(yfirst+by, yfirst, ylast-yfirst+1-by, 1); clear_lines(ylast-by+1, ylast); } else { move_lines(ylast+by, ylast, ylast-yfirst+1+by, -1); clear_lines(yfirst, yfirst-by-1); } break; } } /* * Synchronization, move cursor to given position (or previous if < 0). */ Visible Procedure trmsync(y, x) int y; int x; { #ifdef TRACE fprintf(stderr, "\ttrmsync(%d, %d);\n", y, x); #endif check_started(MESS(7907, "trmsync called outside trmstart/trmend")); if (0 <= y && y < lines && 0 <= x && x < cols) { move(y, x); } VOID fflush(stdout); } /* * Send a bell, visible if possible. */ Visible Procedure trmbell() { #ifdef TRACE fprintf(stderr, "\ttrmbell();\n"); #endif check_started(MESS(7908, "trmbell called outside trmstart/trmend")); ring_bell(); } /* * Now for the real work: here are all low level routines that really * differ for BIOS or ANSI mode. */ /* * BIOS video io is called by generating an 8086 software interrupt, * using lattice's int86() function. * To ease coding, all routines fill in the apropriate parameters in regs, * and than call bios10(code), where code is to be placed in ah. */ Hidden union REGS regs, outregs; /* A macro for speed */ #define bios10(code) (regs.h.ah = (code), int86(0x10, ®s, ®s)) #define nbios10(code) (regs.h.ah = (code), int86(0x10, ®s, &outregs)) /* Video attributes: (see the BASIC manual) (used for standout mode) */ Hidden int video_attr; #ifndef GFX #define V_NORMAL 7 #else #define V_NORMAL (gfx_mode == TEXT_MODE ? 7 : 0) #endif #define V_STANDOUT (7<<4) /* Some BIOS only routines */ Hidden get_cols() { bios10(15); return regs.h.ah; } /* * ANSI escape sequences */ #define A_CUP "\033[%d;%dH" /* cursor position */ #define A_SGR0 "\033[0m" /* set graphics rendition to normal */ #define A_SGR7 "\033[7m" /* set graphics rendition to standout */ #define A_ED "\033[2J" /* erase display (and cursor home) */ #define A_EL "\033[K" /* erase (to end of) line */ /* * The following routine is the time bottleneck, I believe! */ Hidden Procedure put_str(data, n) char *data; int n; { register char c, so; so = so_mode; if (scr_mode == BIOS) { regs.x.cx = 1; /* repition count */ regs.h.bh = 0; /* page number */ regs.h.bl = video_attr; while (--n >= 0) { c = (*data++)&SOCHAR; if ((c&SOBIT) != so) { so = c&SOBIT; so ? standout() : standend(); regs.h.bl = video_attr; } regs.h.al = c&CHAR; nbios10(9); if (cur_x >= cols-1) { line[cur_y][cols-1] = c; continue; } regs.h.dh = cur_y; regs.h.dl = cur_x + 1; nbios10(2); line[cur_y][cur_x] = c; cur_x++; } } else { while (--n >= 0) { c = (*data++)&SOCHAR; if ((c&SOBIT) != so) { so = c&SOBIT; so ? standout() : standend(); } putch(c&CHAR); line[cur_y][cur_x] = c; cur_x++; } } } /* * Move to position y,x on the screen */ Hidden Procedure move(y, x) int y, x; { if (scr_mode != BIOS && cur_y == y && cur_x == x) return; switch (scr_mode) { case BIOS: regs.h.dh = y; regs.h.dl = x; regs.h.bh = 0; /* Page; must be 0 for graphics */ bios10(2); break; case ANSI: cprintf(A_CUP, y+1, x+1); break; } cur_y = y; cur_x = x; } Hidden Procedure standout() { so_mode = On; switch (scr_mode) { case BIOS: video_attr = V_STANDOUT; break; case ANSI: cputs(A_SGR7); break; } } Hidden Procedure standend() { so_mode = Off; switch (scr_mode) { case BIOS: video_attr = V_NORMAL; break; case ANSI: cputs(A_SGR0); break; } } #ifdef UNUSED Hidden Procedure put_c(c) int c; { int ch; ch = c&CHAR; #ifndef NDEBUG if (!isprint(ch)) { ch = '?'; c = (c&SOBIT)|'?'; } #endif switch (scr_mode) { case BIOS: regs.h.al = ch; regs.h.bl = video_attr; regs.x.cx = 1; /* repition count */ regs.h.bh = 0; /* page number */ bios10(9); if (cur_x >= cols-1) { line[cur_y][cols-1] = c; return; } regs.h.dh = cur_y; regs.h.dl = cur_x + 1; bios10(2); break; case ANSI: putch(ch); break; } line[cur_y][cur_x] = c; cur_x++; } #endif UNUSED Hidden Procedure clear_lines(yfirst, ylast) int yfirst, ylast ; { register int y; if (scr_mode == BIOS) { regs.h.al = 0; /* scroll with al = 0 means blank window */ regs.h.ch = yfirst; regs.h.cl = 0; regs.h.dh = ylast; regs.h.dl = cols-1; regs.h.bh = V_NORMAL; bios10(6); for (y = yfirst; y <= ylast; y++) lenline[y] = 0; return; } /* scr_mode == ANSI */ if (yfirst == 0 && ylast == lines-1) { if (so_mode == On) standend(); move(0, 0); /* since some ANSI'd don't move */ cputs(A_ED); cur_y = cur_x = 0; for (y = yfirst; y < ylast; y++) lenline[y] = 0; return; } for (y = yfirst; y <= ylast; y++) { if (lenline[y] > 0) { move(y, 0); clr_to_eol(); } } } Hidden Procedure clr_to_eol() { if (so_mode == On) standend(); switch (scr_mode) { case BIOS: regs.h.bh = 0; /* page */ regs.x.cx = lenline[cur_y] - cur_x; regs.h.al = ' '; regs.h.bl = V_NORMAL; bios10(9); break; case ANSI: cputs(A_EL); break; } lenline[cur_y] = cur_x; } Hidden Procedure /* scrolling for BIOS */ biosscrollup(yfirst, ylast, by) int yfirst; int ylast; int by; { regs.h.al = (by < 0 ? -by : by); regs.h.ch = yfirst; regs.h.cl = 0; regs.h.dh = ylast; regs.h.dl = cols-1; regs.h.bh= V_NORMAL; bios10(by < 0 ? 7 : 6); cur_y = cur_x = Undefined; if (by > 0) scr_lines(yfirst, ylast, by, 1); else scr_lines(ylast, yfirst, -by, -1); } Hidden Procedure /* Reset internal administration accordingly */ scr_lines(yfrom, yto, n, dy) int yfrom, yto, n, dy; { register int y, x; char *saveln; while (n-- > 0) { saveln = line[yfrom]; for (y = yfrom; y != yto; y += dy) { line[y] = line[y+dy]; lenline[y] = lenline[y+dy]; } line[yto] = saveln; for (x = 0; x < cols; x++ ) line[yto][x] = ' '; lenline[yto] = 0; } } Hidden Procedure lf_scroll(yto, by) int yto; int by; { register int n = by; move(lines-1, 0); while (n-- > 0) { putch('\n'); } scr_lines(0, lines-1, by, 1); move_lines(lines-1-by, lines-1, lines-1-yto, -1); clear_lines(yto-by+1, yto); } Hidden Procedure /* for dumb scrolling, uses and updates */ move_lines(yfrom, yto, n, dy) /* internal administration */ int yfrom; int yto; int n; int dy; { while (n-- > 0) { put_line(yto, 0, line[yfrom], lenline[yfrom]); yfrom += dy; yto += dy; } } Hidden Procedure ring_bell() { switch (scr_mode) { case BIOS: regs.h.al = '\007'; regs.h.bl = V_NORMAL; bios10(14); break; case ANSI: putch('\007'); break; } } /* * Show the current internal statuses of the screen on stderr. * For debugging only. */ #ifdef SHOW Visible Procedure trmshow(s) char *s; { int y, x; fprintf(stderr, "<<< %s >>>\n", s); for (y = 0; y < lines; y++) { for (x = 0; x <= lenline[y] /*** && x < cols ***/ ; x++) { fputc(line[y][x]&CHAR, stderr); } fputc('\n', stderr); for (x = 0; x <= lenline[y] && x < cols-1; x++) { if (line[y][x]&SOBIT) fputc('-', stderr); else fputc(' ', stderr); } fputc('\n', stderr); } fprintf(stderr, "CUR_Y = %d, CUR_X = %d.\n", cur_y, cur_x); VOID fflush(stderr); } #endif /* * Interrupt handling. * * (This has not properly been tested, nor is it clear that * this interface is what we want. Anyway, it's here for you * to experiment with. What does it do, you may ask? * Assume an interactive program which reads its characters * through trminput. Assume ^C is the interrupt character. * Normally, ^C is treated just like any other character: when * typed, it turns up in the input. The program may understand * input ^C as "quit from the current mode". * Occasionally, the program goes into a long period of computation. * Now it would be uninterruptible, except if it calls trminterrupt * at times in its computational loop. Trminterrupt magically looks * ahead in the input queue, and if it sees a ^C, discards all input * before that point and returns Yes. It also sets a flag, so that * the interupt "sticks around" until either trminput or trmavail * is called. It is undefined whether typing ^C several times in * a row is seen as one interrupt, or an interrupt followed by input * of ^C's. A program should be prepared for either.) */ static bool intrflag= No; static handler(sig) int sig; { signal(sig, handler); intrflag= Yes; } static set_handler() { signal(SIGINT, handler); } bool trminterrupt() { /* Force a check for Control-break which will call handler. */ kbhit(); return intrflag; } /* * Terminal input without echo. * (This is a recent addition to this module, but will soon be standard). */ trminput() { intrflag= No; return bdos(0x7, 0, 0) & 0377; /* Input, no echo, no ^C checks */ } /* * Check for character available. * * To do this properly, should call DOS function 6, * but the relevant bit (the Z flag) can't be checked from C. * For now, say we don't know. */ trmavail() { intrflag= No; return -1; } trmsuspend() { /* Not implementable on MS-DOS */ }