static const char what[] = "$Id: pscreen.c,v 1.1.1.1 2000/12/16 20:45:29 stuart Exp $"; /* pscreen.c - physical screen interface for character based terminals Copyright 1990 Business Management Systems, Inc. Author: Stuart D. Gathman Function call interface based on "Advanced Programming for Displays" by Marc J. Rochkind. In particular, we use wide characters instead of char,attr structures. Our way is better of course. We export some hooks for efficient curses emulation also. See curses.c */ #include #include #include #include #include "hackterm.h" #include #include /* Set LINEMODE=1 if cookie attributes extend to the end of a line. Cookies are rare now, so it is not worth making this a runtime switch. And there is no terminfo flag for it. */ #define LINEMODE 1 #ifdef TRACE static FILE *tracef = 0; static void dumpcurscr(void); static void dumpline(CHTYPE *cp,int ncols); void PStrace(FILE *f) { tracef = f; } #endif int PSrows, PScols; /* physical screen size */ WINDOW *curscr = 0, *stdscr = 0; static short camode; /* cursor mode flag */ static short prow,pcol,curtype = 1, savcurs; static CHTYPE magic_cookies; static int char_mask = 0xff; static int setcurtype(int); static void mvcur(int oldrow,int oldcol,int row,int col); #ifndef STDIO_WORKS #ifdef _AIX #define __flsbuf flsbuf /* make putc, putchar use our flsbuf */ #else #define _flsbuf flsbuf /* make putc, putchar use our flsbuf */ #endif #define fflush(p) flush(p) /* our fflush() too */ #undef stdout #define stdout &mybuf /* our stdio structure for the above */ static struct myio { int _cnt, _file; unsigned char *_ptr; unsigned char _base[256]; } mybuf = { 256, 1, mybuf._base }; static int flush(struct myio *); static int flsbuf(int, struct myio *); static int flush(struct myio *p) { int i; unsigned len; int rc; unsigned char *buf; len = p->_ptr - p->_base; if (p->_cnt < 0) p->_cnt = 0; p->_cnt += len; buf = p->_ptr = p->_base; do { retry: /* fprintf(stderr,"writing this data:\n"); */ /* write(2,buf,len); */ /* fprintf(stderr,"\n"); */ i = write(p->_file,buf,len); if (i == len) return 0; rc = errno; /* fprintf(stderr,"len = %d, rc = %d, errno = %d\n",len,i,rc); */ if (i <= 0) { /* fprintf(stderr,"retrying with this data:\n"); */ /* write(2,buf,len); */ /* fprintf(stderr,"\n"); */ if (rc == 0 || rc == EINTR) goto retry; if (rc == EAGAIN) { /* should never happen, but */ napms(100); /* work around aix bug. */ goto retry; /* see Motorola AIX4.1.4r5 release guide for PTF */ } break; } buf += i; len -= i; } while (len && i > 0); if (i == -1) perror("pswrite"); return EOF; } static int flsbuf(int c,struct myio *p) { if (fflush(p)) return EOF; return putc(c,p); } int _outchar(int c) { return putchar(c); } int putp(const char *str) { return tputs(str,1,_outchar); } #else #ifdef _AIX extern int _outchar(int); #else int _outchar(int c) { return putchar(c); } #endif #endif /* STDIO_WORKS */ #define mov_curs(r,c) (mvcur(prow,pcol,r,c),prow = r, pcol = c) WINDOW *newwin(int nrows,int ncols,int row,int col) { WINDOW *w = 0; int i; /* check for reasonable arguments and set defaults */ /* if (row < 0 || col < 0) return w; */ if (nrows <= 0) nrows = LINES - row; if (ncols <= 0) ncols = COLS - col; /* if (row + nrows > LINES || col + ncols > COLS) return w; */ if (!(nrows | ncols)) return w; w = (WINDOW *)malloc( sizeof *w - sizeof w->d + nrows * sizeof *w->d); if (!w) return w; w->win = 0; w->rows = nrows; w->cols = ncols; w->prow = row; w->pcol = col; w->crow = w->ccol = 0; w->attr = A_NORMAL; w->data = malloc(nrows * ncols * sizeof (CHTYPE)); if (!w->data) { free(w); return (WINDOW *)0; } for (i = nrows * ncols; --i>=0;) w->data[i] = ' '; w->d[0].data = 0; w->d[0].modc = 0; for (i = 1; id[i].data = w->d[i-1].data + ncols; w->d[i].modc = 0; } return w; } int delwin(WINDOW *w) { free(w->data); free((char *)w); return OK; } #define MAPSIZE 128 /* size of alternate char set */ /* ascii approximations to IBMPC extended ascii character set */ unsigned char def_acs[MAPSIZE] = { 'C','u','e','a','a','a','a','c','e','e','e','i','i','i','A','A', 'E','a','A','o','o','o','u','u','y','O','U','c','L','Y','P','f', 'a','i','o','u','n','N','a','o','?','-','-','2','4','!','<','>', '#','#','#','|','|','|','|','.','\\','|','|','\\','/','|','/',',', '|','|','_','|','_','|','|','|','\\','/','=','=','|','=','|','=', '_','=','_','|','\\','/','.','|','|','|','.','#','=','|','|','=', 'a','B','G','p','S','s','m','t','P','T','W','d','8','p','E','U', '=','+','>','<','f','j','%','~','`','*','-','\\','n','2','*',' ' }; unsigned char *acs_map = def_acs; static void freeMap(void) { if (acs_map && acs_map != def_acs) free(acs_map); acs_map = def_acs; } static void allocMap(void) { if (!acs_map || acs_map == def_acs) { acs_map = (unsigned char *)malloc(MAPSIZE * sizeof *acs_map); memcpy(acs_map,def_acs,MAPSIZE * sizeof *acs_map); } } #ifdef acs_chars /* install unix style vt100 based map */ static void domap(const char *p,unsigned attr) { static char pcmap[] = "h\062a\060f\170g\161j\131k\077l\132m\100n\105q\104t\103u\064v\101w\102x\063~\171\060\133"; /* '\004b\003c\006d\005 */ unsigned char vtmap[MAPSIZE]; char *v; if (acs_map == 0) { acs_map = def_acs; return; } memset(vtmap,0,sizeof vtmap); for (v = pcmap; v[0] && v[1]; v += 2) /* construct vt100 map */ vtmap[v[0]] = v[1]; while (p[0] && p[1]) { acs_map[vtmap[p[0]]] = (p[1]&0xff) | attr; p += 2; } } #endif #ifdef box_chars_1 /* AIX box characters */ static void boxmap(const char *box1,const char *box2,unsigned attr) { int i; static const char b1[11] = { 218, 196, 191, 179, 217, 192, 194, 180, 193, 195, 197 }; static const char b2[11] = { 201, 205, 187, 186, 188, 200, 203, 185, 202, 204, 206 }; if (acs_map == 0) return; if (box1) for (i = 0; i < 11 && box1[i]; ++i) { int j = b1[i] & 127; acs_map[j] = (box1[i]&255) | attr; } if (box2) for (i = 0; i < 11 && box2[i]; ++i) { int j = b2[i] & 127; acs_map[j] = (box2[i]&255) | attr; } } #endif int toprt(CHTYPE ch) { /* map a graphics character to printable ascii */ int i; if (ch&A_ALTCHARSET) { if (acs_map == 0) return ch&255; for (i = 0; i < MAPSIZE; ++i) if (acs_map[i] == ch) return def_acs[i]; return '.'; } return ch & A_CHARTEXT; } void PSbegin(void) { int terr; /* return code from setupterm */ char *savtinfo = getenv("TERMINFO"); char *tinfo = 0; char *bmsinfo = getenv("BMSINFO"); if (bmsinfo) { tinfo = malloc(strlen(bmsinfo) + 10); sprintf(tinfo,"TERMINFO=%s",bmsinfo); putenv(tinfo); } if (!tinfo) { char *bms = getenv("BMS"); if (!bms) bms = "/bms"; tinfo = malloc(strlen(bms) + 19); sprintf(tinfo,"TERMINFO=%s/terminfo",bms); putenv(tinfo); } setupterm((char *)0,1,&terr); /* get terminfo stuff */ if (savtinfo) { putenv(savtinfo - 9); free(tinfo); } if (terr != 1) errpost(321); if ((unsigned)lines > 127 || (unsigned)columns > GIANT) errpost(323); def_prog_mode(); camode = 0; PSrows = lines; PScols = columns; curscr = newwin(0,0,0,0); if (!curscr) { PSend(); errpost(ENOMEM); } #ifdef acs_chars if (acs_chars) { if (strcmp(acs_chars,"IBMPC")) { allocMap(); domap(acs_chars,A_ALTCHARSET); /* install hardware supported codes */ } else /* PC compatible altcharset */ acs_map = 0; } #endif #ifdef box_chars_1 if (acs_map && box_chars_1) { allocMap(); boxmap(box_chars_1,box_chars_2,A_ALTCHARSET); } #endif if (enter_alt_charset_mode && acs_map) char_mask = 0x7f; magic_cookies = vidinit(A_NORMAL); PSrefresh(); savcurs = setcurtype(curtype); return; } void PSend(void) { freeMap(); if (curscr) { setcurtype(savcurs); mov_curs(PSrows-1,0); /* position on last line */ putp(clr_eol); /* erase it */ delwin(curscr); curscr = 0; if (camode) putp(exit_ca_mode); fflush(stdout); reset_shell_mode(); #ifdef PROGTTY del_curterm(cur_term); #endif } return; } /* update terminal with changes to curscr The strategy is that all changed positions are marked with the A_MODIFIED attribute. The d.modc field has the first modified position on each line to avoid searching the entire screen array. */ #define update(cp,col,ch,modc) \ do { \ if (cp[col] != ch) { \ int col1 = col + 1; \ if (col < modc) \ modc = col; \ if (col1 < PScols && ((ch^cp[col1])&magic_cookies) != 0) \ cp[col1] |= A_MODIFIED; \ cp[col] = ch | A_MODIFIED; \ } \ } while (0) void PSsync(void) { /* apply changes to curscr to terminal */ int r, c, i; static int savtype = -1; /* flag no updates performed */ CHTYPE cattr; /* cookie attribute */ CHTYPE lattr; /* last attribute output */ #ifdef TRACE if (tracef) dumpcurscr(); #endif lattr = cattr = A_NORMAL; /* cookies reset at top */ if (!camode) { putp(enter_ca_mode); camode = 1; } for (r = 0; r < PSrows; ++r) { /* for all rows */ register struct rowdata *p; register CHTYPE *cp; p = curscr->d + r; cp = curscr->data + p->data; /* see if row needs updating */ if (p->modc < PScols || p->srow != r) { int y_cnt, n_cnt; char y_move, n_move; if (PSquit()) return; if (savtype < 0) savtype = setcurtype(0); c = p->modc; if (c > 0) cattr = cp[c - 1] & magic_cookies; else if (!LINEMODE && r > 0) cattr = curscr->data[curscr->d[r].data + PScols - 1] & magic_cookies; else cattr = A_NORMAL; /* don't support PAGE mode yet */ /* prescan to estimate whether erase to end of line is better */ y_cnt = n_cnt = 0; if (p->srow >= r) { /* if our row data still on screen */ y_move = n_move = 0; /* init already moved */ y_cnt = 2; /* need EOL also */ for (; c4) ? 4 : n_move; /* penalty for move required */ n_move = 0; } else ++n_move; /* if erase end of line */ if ((cp[c]&~A_MODIFIED) != (' '|cattr)) { ++y_cnt; /* count changes required */ y_cnt += (y_move>4) ? 4 : y_move; y_move = 0; } else ++y_move; } } if (y_cnt <= n_cnt) { /* if erase seems better */ CHTYPE a = cattr; int irows = 0; if (p->srow != r) p->modc = 0; for (i = r; ++i < PSrows;) { if (curscr->d[i].srow == r /* && curscr->d[i].modc > p->modc */) { ++irows; for (;i < PSrows;++i) if (curscr->d[i].srow >= r && ++curscr->d[i].srow >= PSrows) curscr->d[i].srow = -1; /* lost off bottom */ p->modc = 0; break; } } mov_curs(r,0); if (parm_insert_line) putp(tparm(parm_insert_line,irows)); else while (irows--) putp(insert_line); /* give screen line away */ c = p->modc; mov_curs(r,c); vidattr(lattr = a); /* make sure we get expected attrib */ putp(clr_eol); /* do the erase */ for (; cmodc; } else { int drows = 0; while (p->srow > r) { ++drows; for (i = r; i < PSrows; ++i) if (curscr->d[i].srow > r) --curscr->d[i].srow; } mov_curs(r,0); if (parm_delete_line) putp(tparm(parm_delete_line,drows)); else while (drows--) putp(delete_line); /* may need padding */ c = p->modc; mov_curs(r,c); /* move to where change begins */ } p->srow = r; for (; c 0) putchar(27); else ch = ' '; /* Link MC6 ignores NUL completely */ } if (ch == 127) /* terminal specific hack to display DEL */ putp("\033U\177\033u"); else putchar(ch); if (++pcol >= PScols) { if (eat_newline_glitch) pcol = prow = -99; /* don't know where cursor is */ else if (auto_right_margin) { pcol = 0; ++prow; } else --pcol; /* sticks at right margin */ } } cattr = a; /* propagate cookie attribute */ } p->modc = PScols; } } setcurtype(curtype); /* update the cursor if it moved */ if (curtype > 0) r = curscr->crow, c = curscr->ccol; else r = PSrows - 1, c = PScols - 1; /* if we actually updated something, put terminal back in "standby" state */ if (savtype >= 0) { savtype = -1; /* turn off all mode attributes when done */ if (lattr & ~magic_cookies) { /* any modes on ? */ cattr = curscr->data[curscr->d[r].data + c] & magic_cookies; vidattr(cattr); /* turn them off */ } } mov_curs(r,c); fflush(stdout); return; } void PSbeep(int type) { if (type && flash_screen) putp(flash_screen); else if (bell) putp(bell); else putp("\007"); fflush(stdout); } void PSrefresh(void) { int c; int r; CHTYPE cattr; tputs(clear_screen,LINES,_outchar); if (camode) putp(exit_ca_mode); camode = prow = pcol = 0; for (r = 0; r < PSrows; ++r ) { register struct rowdata *p; register CHTYPE *cp; p = curscr->d + r; p->srow = r; cp = curscr->data + p->data; p->modc = PScols; if (LINEMODE) cattr = A_NORMAL; for (c = 0; c < PScols; ++c ) { CHTYPE ch = cp[c] & ~A_MODIFIED; if (ch != (' ' | cattr)) { ch |= A_MODIFIED; if (c < p->modc) p->modc = c; } cp[c] = ch; cattr = ch & magic_cookies; } } } #define MAXROWS PSrows #define MAXCOLS PScols void PSwrite(int row,int col,int ncols,const char *str,CHTYPE att) { struct rowdata *p; CHTYPE *cp, ch; #ifdef TRACE if (tracef) { fprintf(tracef,"PSwrite(%d,%d,%d,'%.*s',%04X)\n", row,col,ncols,ncols,str,att); } #endif /* clip to physical screen */ if (row < 0 || row >= MAXROWS || col >= MAXCOLS) return; if (col + ncols > MAXCOLS) ncols = MAXCOLS - col; if (col < 0) { ncols += col; col = 0; } p = curscr->d + row; cp = curscr->data + p->data; while (ncols-- && *str) { ch = (*str++ & 0xff) | att; if ((ch & A_ALTCHARSET) && acs_map) { /* map alternate character set */ ch = acs_map[ch&A_CHARTEXT] | (ch & ~A_ALTCHARSET & A_ATTRIBUTES); } update(cp,col,ch,p->modc); ++col; } } void PSwrtattr(int row,int col,int ncols,const CHTYPE far *cp) { struct rowdata *p; CHTYPE far *sp; #ifdef TRACE if (tracef) { fprintf(tracef,"PSwrtattr(%d,%d,%d,",row,col,ncols); dumpline(cp,ncols); } #endif /* clip to physical screen */ if (row < 0 || row >= MAXROWS || col >= MAXCOLS) return; if (col + ncols > MAXCOLS) ncols = MAXCOLS - col; if (col < 0) { ncols += col; col = 0; } p = curscr->d + row; sp = curscr->data + p->data; while (ncols--) { CHTYPE ch = *cp++; if (ch & A_ALTCHARSET && acs_map) { /* map alternate character set */ ch = acs_map[ch&A_CHARTEXT] | (ch & ~A_ALTCHARSET & A_ATTRIBUTES); } update(sp,col,ch,p->modc); ++col; } } void PSfill(const RECT *rp,CHTYPE ch) { RECT r; int row,col; #ifdef TRACE if (tracef) { fprintf(tracef,"PSfill([%d,%d,%d,%d],%04X)\n", rp->r1,rp->c1,rp->r2,rp->c2,ch); } #endif if (!clip(rp,MAXROWS,MAXCOLS,&r)) return; if (ch & A_ALTCHARSET && acs_map) { /* map alternate character set */ ch = acs_map[ch&A_CHARTEXT] | (ch & ~A_ALTCHARSET & A_ATTRIBUTES); } for (row = r.r1; row <= r.r2; ++row) { register struct rowdata *p; register CHTYPE *cp; p = curscr->d + row; cp = curscr->data + p->data; for (col = r.c1; col <= r.c2; ++col) { update(cp,col,ch,p->modc); } } } void PScurpos(int row,int col) { wmove(curscr,row % PSrows,col % PScols); } /* Cursor types: 0 - invisible 1 - normal 2 - very visible */ int PScurtype(int type) { int lasttype = curtype; curtype = type; return lasttype; } /* physically set cursor type */ static int setcurtype(int type) { static int lasttype = 1; if (type != lasttype) { char *p = 0; switch (type) { case 0: p = cursor_invisible; break; case 2: case 4: p = cursor_visible; if (p) break; case 1: case 3: p = cursor_normal; break; } if (p) { int last = lasttype; putp(p); lasttype = type; return last; } } return lasttype; } void dropscrn(void) { PSrefresh(); fflush(stdout); reset_shell_mode(); Kend(); } void restscrn(void) { reset_prog_mode(); /* restore current terminal mode */ PSrefresh(); PSsync(); } void mvcur(int oldrow,int oldcol,int row,int col) { register int diff; if (oldrow == row) { if (oldcol == col) return; diff = col - oldcol; if (diff > 0) { if (cursor_right && diff <= 4) { while (diff--) putp(cursor_right); return; } } else if (cursor_left && -diff <= 4) { while (diff++) putp(cursor_left); return; } } if (oldcol == col) { diff = row - oldrow; if (diff > 0) { if (cursor_down && diff <= 4) { while (diff--) putp(cursor_down); return; } } else if (cursor_up && -diff <= 4) { while (diff++) putp(cursor_up); return; } } putp(tparm(cursor_address,row,col)); } #ifdef TRACE static void dumpcurscr() { int i; fprintf(tracef,"PSsync\nsr mod data\n"); for (i = 0; i < PSrows; ++i) { struct rowdata *rp = &curscr->d[i]; fprintf(tracef,"%2d %3d ", rp->srow, rp->modc); dumpline(curscr->data + rp->data,PScols); } } static void dumpline(CHTYPE *cp,int ncols) { int i; for (i = 0; i < ncols; ++i) fprintf(tracef,"%02X %c%c", cp[i] >> 8,cp[i] & 255,(i+1 == ncols) ? '\n' : ' '); } #endif