////////////////////////////////////////////////////////////////////////// // pfterm environment settings ////////////////////////////////////////////////////////////////////////// #ifdef _PFTERM_TEST_MAIN #define USE_PFTERM #define EXP_PFTERM #define DBCSAWARE #define FT_DBCS_NOINTRESC 1 #define DBG_TEXT_FD #include #include #include #include #include #else #define HAVE_GRAYOUT #include "bbs.h" #ifdef DBCS_NOINTRESC // # ifdef CONVERT // extern int bbs_convert_type; // # define FT_DBCS_NOINTRESC ( // (cuser.uflag & DBCS_NOINTRESC) || // (bbs_convert_type == CONV_UTF8)) // # else # define FT_DBCS_NOINTRESC (cuser.uflag & DBCS_NOINTRESC) // # endif #else # define FT_DBCS_NOINTRESC 0 #endif #endif ////////////////////////////////////////////////////////////////////////// // pfterm debug settings ////////////////////////////////////////////////////////////////////////// // #define DBG_SHOW_DIRTY // #define DBG_SHOW_REPRINT // will not work if you enable SHOW_DIRTY. // #define DBG_DISABLE_OPTMOVE // #define DBG_DISABLE_REPRINT ////////////////////////////////////////////////////////////////////////// // pmore style ansi // #include "ansi.h" ////////////////////////////////////////////////////////////////////////// #ifndef PMORE_STYLE_ANSI #define ESC_CHR '\x1b' #define ESC_STR "\x1b" #define ANSI_COLOR(x) ESC_STR "[" #x "m" #define ANSI_RESET ESC_STR "[m" #endif // PMORE_STYLE_ANSI #ifndef ANSI_IS_PARAM #define ANSI_IS_PARAM(c) (c == ';' || (c >= '0' && c <= '9')) #endif // ANSI_IS_PARAM ////////////////////////////////////////////////////////////////////////// // grayout advanced control // #include "grayout.h" ////////////////////////////////////////////////////////////////////////// #ifndef GRAYOUT_DARK #define GRAYOUT_COLORBOLD (-2) #define GRAYOUT_BOLD (-1) #define GRAYOUT_DARK (0) #define GRAYOUT_NORM (1) #define GRAYOUT_COLORNORM (+2) #endif // GRAYOUT_DARK ////////////////////////////////////////////////////////////////////////// // Typeahead ////////////////////////////////////////////////////////////////////////// #ifndef TYPEAHEAD_NONE #define TYPEAHEAD_NONE (-1) #define TYPEAHEAD_STDIN (0) #endif // TYPEAHEAD ////////////////////////////////////////////////////////////////////////// // pfterm: piaip's flat terminal system, a new replacement for 'screen'. // pfterm can also be read as "Perfect Term" // // piaip's new implementation of terminal system (term/screen) with dirty // map, designed and optimized for ANSI based output. // // "pfterm" is "piaip's flat terminal" or "perfect term", not "PTT's term"!!! // pfterm is designed for general maple-family BBS systems, not // specific to any branch. // // Author: Hung-Te Lin (piaip), Dec 2007. // // Copyright (c) 2007-2008 Hung-Te Lin // All rights reserved. // // Distributed under a Non-Commercial 4clause-BSD alike license. // // 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. All advertising materials mentioning features or use of this software // must display appropriate acknowledgement, like: // This product includes software developed by Hung-Te Lin (piaip). // The acknowledgement can be localized with the name unchanged. // 4. You may not exercise any of the rights granted to you above in any // manner that is primarily intended for or directed toward commercial // advantage or private monetary compensation. For avoidance of doubt, // using in a program providing commercial network service is also // prohibited. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER // 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. // // MAJOR IMPROVEMENTS: // - Interpret ANSI code and maintain a virtual terminal // - Dual buffer for dirty map and optimized output // // TODO AND DONE: // - DBCS aware and prevent interrupting DBCS [done] // - optimization with relative movement [done] // - optimization with ENTER/clrtoeol [done] // - ncurses-like API [done] // - inansistr to output escaped string [done] // - handle incomplete DBCS characters [done] // - optimization with reprint ability [done] // - add auto wrap control // // DEPRECATED: // - standout() [rework getdata() and namecomplete()] // - talk.c (big_picture) [rework talk.c] // ////////////////////////////////////////////////////////////////////////// // Reference: // http://en.wikipedia.org/wiki/ANSI_escape_code // http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf ////////////////////////////////////////////////////////////////////////// // The supported escapes are based on 'cons25' termcap and // Windows 2000/XP/Vista built-in telnet. // // Underline is only supported by vt100 (and monochrome) so we drop it. // Blink is supported by vt100/cons25 and we keep it. ////////////////////////////////////////////////////////////////////////// // Experimental now #if defined(EXP_PFTERM) || defined(USE_PFTERM) ////////////////////////////////////////////////////////////////////////// // pfterm Configurations ////////////////////////////////////////////////////////////////////////// // Prevent invalid DBCS characters #define FTCONF_PREVENT_INVALID_DBCS // Some terminals use current attribute to erase background #define FTCONF_CLEAR_SETATTR // Some terminals (NetTerm, PacketSite) have bug in bolding attribute. #define FTCONF_WORKAROUND_BOLD // Some terminals prefer VT100 style scrolling, including Win/DOS telnet #undef FTCONF_USE_ANSI_SCROLL #undef FTCONF_USE_VT100_SCROLL // Few poor terminals do not have relative move (ABCD). #undef FTCONF_USE_ANSI_RELMOVE // Handling ANSI commands with 2 parameters (ex, ESC[m;nH) // 2: Good terminals can accept any omit format (ESC[;nH) // 1: Poor terminals (eg, Win/DOS telnet) can only omit 2nd (ESC[mH) // 0: Very few poor terminals (eg, CrazyTerm/BBMan) cannot omit any parameters #define FTCONF_ANSICMD2_OMIT (0) ////////////////////////////////////////////////////////////////////////// // Flat Terminal Definition ////////////////////////////////////////////////////////////////////////// #define FTSZ_DEFAULT_ROW (24) #define FTSZ_DEFAULT_COL (80) #define FTSZ_MIN_ROW (24) #define FTSZ_MAX_ROW (100) #define FTSZ_MIN_COL (80) #define FTSZ_MAX_COL (320) #define FTCHAR_ERASE (' ') #define FTATTR_ERASE (0x07) #define FTCHAR_BLANK (' ') #define FTATTR_DEFAULT (FTATTR_ERASE) #define FTCHAR_INVALID_DBCS ('?') // #define FTATTR_TRANSPARENT (0x80) #define FTDIRTY_CHAR (0x01) #define FTDIRTY_ATTR (0x02) #define FTDIRTY_DBCS (0x04) #define FTDIRTY_INVALID_DBCS (0x08) #define FTDIRTY_RAWMOVE (0x10) #define FTDBCS_SAFE (0) // standard DBCS #define FTDBCS_UNSAFE (1) // not on all systems (better do rawmove) #define FTDBCS_INVALID (2) // invalid #define FTCMD_MAXLEN (63) // max escape command sequence length #define FTATTR_MINCMD (16) #ifndef FTCONF_USE_ANSI_RELMOVE # define FTMV_COST (8) // always ESC[m;nH will costs avg 8 bytes #else # define FTMV_COST (5) // ESC[ABCD with ESC[m;nH costs avg 4+ bytes #endif ////////////////////////////////////////////////////////////////////////// // Flat Terminal Data Type ////////////////////////////////////////////////////////////////////////// typedef unsigned char ftchar; // primitive character type typedef unsigned char ftattr; // primitive attribute type ////////////////////////////////////////////////////////////////////////// // Flat Terminal Structure ////////////////////////////////////////////////////////////////////////// typedef struct { ftchar **cmap[2]; // character map ftattr **amap[2]; // attribute map ftchar *dmap; // dirty map ftchar *dcmap; // processed display map ftattr attr; int rows, cols; int y, x; int sy,sx; // stored cursor int mi; // map index, mi = current map and (1-mi) = old map int dirty; int scroll; // memory allocation int mrows, mcols; // raw terminal status int ry, rx; ftattr rattr; // typeahead char typeahead; // escape command ftchar cmd[FTCMD_MAXLEN+1]; int szcmd; } FlatTerm; static FlatTerm ft; #ifdef _WIN32 // sorry, we support only 80x24 on Windows now. HANDLE hStdout; COORD coordBufSize = {80, 24}, coordBufCoord = {0, 0}; SMALL_RECT winrect = {0, 0, 79, 23}; CHAR_INFO winbuf[80*24]; #endif ////////////////////////////////////////////////////////////////////////// // Flat Terminal Utility Macro ////////////////////////////////////////////////////////////////////////// // ftattr: 0| FG(3) | BOLD(1) | BG(3) | BLINK(1) |8 #define FTATTR_FGSHIFT (0) #define FTATTR_BGSHIFT (4) #define FTATTR_GETFG(x) ((x >> FTATTR_FGSHIFT) & 0x7) #define FTATTR_GETBG(x) ((x >> FTATTR_BGSHIFT) & 0x7) #define FTATTR_FGMASK ((ftattr)(0x7 << FTATTR_FGSHIFT)) #define FTATTR_BGMASK ((ftattr)(0x7 << FTATTR_BGSHIFT)) #define FTATTR_BOLD (0x08) #define FTATTR_BLINK (0x80) #define FTATTR_DEFAULT_FG (FTATTR_GETFG(FTATTR_DEFAULT)) #define FTATTR_DEFAULT_BG (FTATTR_GETBG(FTATTR_DEFAULT)) #define FTATTR_MAKE(f,b) (((f)<=(0x80)) #define FTDBCS_ISTAIL(x) (((unsigned char)(x))>=(0x40)) // - 0xFF is used as telnet IAC, don't use it! // - 0x80 is invalid for Big5. #define FTDBCS_ISBADLEAD(x) ((((unsigned char)(x)) == 0x80) || (((unsigned char)(x)) == 0xFF)) // even faster: // #define FTDBCS_ISLEAD(x) (((unsigned char)(x)) & 0x80) // #define FTDBCS_ISTAIL(x) (((unsigned char)(x)) & ((unsigned char)~0x3F)) #define FTDBCS_ISSBCSPRINT(x) \ (((unsigned char)(x))>=' ' && ((unsigned char)(x))<0x80) #ifndef min #define min(x,y) (((x)<(y))?(x):(y)) #endif #ifndef max #define max(x,y) (((x)>(y))?(x):(y)) #endif #ifndef abs #define abs(x) (((x)>0)?(x):-(x)) #endif #define ranged(x,vmin,vmax) (max(min(x,vmax),vmin)) ////////////////////////////////////////////////////////////////////////// // Flat Terminal API ////////////////////////////////////////////////////////////////////////// //// common ncurse-like library interface // initialization void initscr (void); int resizeterm (int rows, int cols); int endwin (void); // attributes ftattr attrget (void); void attrset (ftattr attr); void attrsetfg (ftattr attr); void attrsetbg (ftattr attr); // cursor void getyx (int *y, int *x); void getmaxyx (int *y, int *x); void move (int y, int x); // clear void clear (void); // clrscr + move(0,0) void clrtoeol (void); // end of line void clrtobot (void); // clear (non-ncurses) void clrtoln (int ln); // clear down to ln ( excluding ln, as [y,ln) ) void clrcurln (void); // whole line void clrtobeg (void); // begin of line void clrtohome (void); void clrscr (void); // clear and keep cursor untouched void clrregion (int r1, int r2); // clear [r1,r2], bi-directional. // window control void newwin (int nlines, int ncols, int y, int x); // flushing void refresh (void); // optimized refresh void doupdate (void); // optimized refresh, ignore input queue void redrawwin (void); // invalidate whole screen int typeahead (int fd);// prevent refresh if input queue is not empty // scrolling void scroll (void); // scroll up void rscroll (void); // scroll down void scrl (int rows); // output (ncurses flavor) void addch (unsigned char c); // equivalent to outc() void addstr (const char *s); // equivalent to outs() void addnstr (const char *s, int n); // output (non-ncurses) void outc (unsigned char c); void outs (const char *s); void outns (const char *s, int n); void outstr (const char *str); // prepare and print a complete string. void addstring (const char *str); // ncurses-like of outstr(). // readback int instr (char *str); int innstr (char *str, int n); // n includes \0 int inansistr (char *str, int n); // n includes \0 // deprecated void standout (void); void standend (void); // grayout advanced control void grayout (int y, int end, int level); //// flat-term internal processor int fterm_inbuf (void); // raw input adapter void fterm_rawc (int c); // raw output adapter void fterm_rawnewline(void); // raw output adapter void fterm_rawflush (void); // raw output adapter void fterm_raws (const char *s); void fterm_rawnc (int c, int n); void fterm_rawnum (int arg); void fterm_rawcmd (int arg, int defval, char c); void fterm_rawcmd2 (int arg1, int arg2, int defval, char c); void fterm_rawattr (ftattr attr); // optimized changing attribute void fterm_rawclear (void); void fterm_rawclreol (void); void fterm_rawhome (void); void fterm_rawscroll (int dy); void fterm_rawcursor (void); void fterm_rawmove (int y, int x); void fterm_rawmove_opt(int y, int x); void fterm_rawmove_rel(int dy, int dx); int fterm_chattr (char *s, ftattr oa, ftattr na); // size(s) > FTATTR_MINCMD void fterm_exec (void); // execute ft.cmd void fterm_flippage (void); void fterm_dupe2bk (void); void fterm_markdirty (void); // mark as dirty int fterm_strdlen (const char *s); // length of string for display int fterm_prepare_str(int len); // DBCS supporting int fterm_DBCS_Big5(unsigned char c1, unsigned char c2); ////////////////////////////////////////////////////////////////////////// // Flat Terminal Implementation ////////////////////////////////////////////////////////////////////////// #define fterm_markdirty() { ft.dirty = 1; } // initialization void initscr(void) { #ifdef _WIN32 hStdout = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleScreenBufferSize(hStdout, coordBufSize); SetConsoleCursorPosition(hStdout, coordBufCoord); #endif memset(&ft, sizeof(ft), 0); ft.attr = ft.rattr = FTATTR_DEFAULT; resizeterm(FTSZ_DEFAULT_ROW, FTSZ_DEFAULT_COL); // clear both pages ft.mi = 0; clrscr(); ft.mi = 1; clrscr(); ft.mi = 0; // typeahead ft.typeahead = 1; fterm_rawclear(); move(0, 0); } int endwin(void) { int r, mi = 0; // fterm_rawclear(); for (mi = 0; mi < 2; mi++) { for (r = 0; r < ft.mrows; r++) { free(ft.cmap[mi][r]); free(ft.amap[mi][r]); } } free(ft.dmap); free(ft.dcmap); memset(&ft, sizeof(ft), 0); return 0; } int resizeterm(int rows, int cols) { int dirty = 0, mi = 0, i = 0; rows = ranged(rows, FTSZ_MIN_ROW, FTSZ_MAX_ROW); cols = ranged(cols, FTSZ_MIN_COL, FTSZ_MAX_COL); // adjust memory only for increasing buffer if (rows > ft.mrows || cols > ft.mcols) { for (mi = 0; mi < 2; mi++) { // allocate rows if (rows > ft.mrows) { ft.cmap[mi] = (ftchar**)realloc(ft.cmap[mi], sizeof(ftchar*) * rows); ft.amap[mi] = (ftattr**)realloc(ft.amap[mi], sizeof(ftattr*) * rows); // allocate new columns for (i = ft.mrows; i < rows; i++) { ft.cmap[mi][i] = (ftchar*)malloc((cols+1) * sizeof(ftchar)); ft.amap[mi][i] = (ftattr*)malloc((cols+1) * sizeof(ftattr)); // zero at end to prevent over-run ft.cmap[mi][i][cols] = 0; ft.amap[mi][i][cols] = 0; } } // resize cols if (cols > ft.mcols) { for (i = 0; i < ft.mrows; i++) { ft.cmap[mi][i] = (ftchar*)realloc(ft.cmap[mi][i], (cols+1) * sizeof(ftchar)); ft.amap[mi][i] = (ftattr*)realloc(ft.amap[mi][i], (cols+1) * sizeof(ftattr)); // zero at end to prevent over-run ft.cmap[mi][i][cols] = 0; ft.amap[mi][i][cols] = 0; } } else { // we have to deal one case: // expand x, shrink x, expand y -> // now new allocated lines will have small x(col) instead of mcol. // the solution is to modify mcols here, or change the malloc above // to max(ft.mcols, cols). ft.mcols = cols; } } // adjusts dirty and display map. // no need to initialize anyway. if (cols > ft.mcols) { ft.dmap = (ftchar*) realloc(ft.dmap, (cols+1) * sizeof(ftchar)); ft.dcmap = (ftchar*) realloc(ft.dcmap, (cols+1) * sizeof(ftchar)); } // do mrows/mcols assignment here, because we had 2 maps running loop above. if (cols > ft.mcols) ft.mcols = cols; if (rows > ft.mrows) ft.mrows = rows; dirty = 1; } // clear new exposed buffer after resized // because we will redawwin(), so need to change front buffer only. for (i = ft.rows; i < rows; i++) { memset(FTCMAP[i], FTCHAR_ERASE, (cols) * sizeof(ftchar)); memset(FTAMAP[i], FTATTR_ERASE, (cols) * sizeof(ftattr)); } if (cols > ft.cols) { for (i = 0; i < ft.rows; i++) { memset(FTCMAP[i]+ft.cols, FTCHAR_ERASE, (cols-ft.cols) * sizeof(ftchar)); memset(FTAMAP[i]+ft.cols, FTATTR_ERASE, (cols-ft.cols) * sizeof(ftattr)); } } if (ft.rows != rows || ft.cols != cols) { ft.rows = rows; ft.cols = cols; redrawwin(); } ft.x = ranged(ft.x, 0, ft.cols-1); ft.y = ranged(ft.y, 0, ft.rows-1); return dirty; } // attributes ftattr attrget(void) { return ft.attr; } void attrset(ftattr attr) { ft.attr = attr; } void attrsetfg(ftattr attr) { ft.attr &= (~FTATTR_FGMASK); ft.attr |= ((attr & FTATTR_FGMASK) << FTATTR_FGSHIFT); } void attrsetbg(ftattr attr) { ft.attr &= (~FTATTR_BGMASK); ft.attr |= ((attr & FTATTR_FGMASK) << FTATTR_BGSHIFT); } // clear void clrscr(void) { int r; for (r = 0; r < ft.rows; r++) memset(FTCMAP[r], FTCHAR_ERASE, ft.cols * sizeof(ftchar)); for (r = 0; r < ft.rows; r++) memset(FTAMAP[r], FTATTR_ERASE, ft.cols * sizeof(ftattr)); fterm_markdirty(); } void clear(void) { clrscr(); move(0,0); } void clrtoeol(void) { ft.x = ranged(ft.x, 0, ft.cols-1); ft.y = ranged(ft.y, 0, ft.rows-1); memset(FTPC, FTCHAR_ERASE, ft.cols - ft.x); memset(FTPA, FTATTR_ERASE, ft.cols - ft.x); fterm_markdirty(); } void clrtobeg(void) { ft.x = ranged(ft.x, 0, ft.cols-1); ft.y = ranged(ft.y, 0, ft.rows-1); memset(FTCROW, FTCHAR_ERASE, ft.x+1); memset(FTAROW, FTATTR_ERASE, ft.x+1); fterm_markdirty(); } void clrcurrline(void) { ft.y = ranged(ft.y, 0, ft.rows-1); memset(FTCROW, FTCHAR_ERASE, ft.cols); memset(FTAROW, FTATTR_ERASE, ft.cols); fterm_markdirty(); } void clrtoln(int line) { if (line <= ft.y) return; clrregion(ft.y, line-1); } void clrregion(int r1, int r2) { // bi-direction if (r1 > r2) { int r = r1; r1 = r2; r2 = r; } // check r1, r2 range r1 = ranged(r1, 0, ft.rows-1); r2 = ranged(r2, 0, ft.rows-1); for (; r1 <= r2; r1++) { memset(FTCMAP[r1], FTCHAR_ERASE, ft.cols); memset(FTAMAP[r1], FTATTR_ERASE, ft.cols); } fterm_markdirty(); } void clrtobot(void) { clrtoeol(); clrregion(ft.y+1, ft.rows-1); } void clrtohome(void) { clrtobeg(); clrregion(ft.y-1, 0); } void newwin (int nlines, int ncols, int y, int x) { int oy, ox; // check range x = ranged(x, 0, ft.cols-1); y = ranged(y, 0, ft.rows-1); ncols = ranged(x+ncols-1, x, ft.cols-1); nlines = ranged(y+nlines-1, y, ft.rows-1); ncols = ncols - x + 1; nlines= nlines- y + 1; if (nlines <= 0 || ncols <= 0) return; getyx(&oy, &ox); while (nlines-- > 0) { move(y++, x); // use prepare_str to erase character fterm_prepare_str(ncols); // memset(FTAMAP[y]+x, ft.attr, ncols); // memset(FTCMAP[y]+x, FTCHAR_ERASE, ncols); } move(oy, ox); } // dirty and flushing void redrawwin(void) { // flip page fterm_flippage(); clrscr(); // clear raw terminal fterm_rawclear(); // flip page again fterm_flippage(); // mark for refresh. fterm_markdirty(); } int typeahead(int fd) { switch(fd) { case TYPEAHEAD_NONE: ft.typeahead = 0; break; case TYPEAHEAD_STDIN: ft.typeahead = 1; break; default: // shall never reach here assert(NULL); break; } return 0; } void refresh(void) { // prevent passive update if(fterm_inbuf() && ft.typeahead) return; doupdate(); } void doupdate(void) { int y, x; char touched = 0; if (!ft.dirty) { fterm_rawcursor(); return; } #ifdef _WIN32 assert(ft.rows == coordBufSize.Y); assert(ft.cols == coordBufSize.X); for (y = 0; y < ft.rows; y++) { for (x = 0; x < ft.cols; x++) { WORD xAttr = FTAMAP[y][x], xxAttr; // w32 attribute: bit swap (0,2) and (4, 6) xxAttr = xAttr & 0xAA; if (xAttr & 0x01) xxAttr |= 0x04; if (xAttr & 0x04) xxAttr |= 0x01; if (xAttr & 0x10) xxAttr |= 0x40; if (xAttr & 0x40) xxAttr |= 0x10; winbuf[y*ft.cols + x].Attributes= xxAttr; winbuf[y*ft.cols + x].Char.AsciiChar = FTCMAP[y][x]; } } WriteConsoleOutputA(hStdout, winbuf, coordBufSize, coordBufCoord, &winrect); #else // !_WIN32 // if scroll, do it first if (ft.scroll) fterm_rawscroll(ft.scroll); // calculate and optimize dirty for (y = 0; y < ft.rows; y++) { int len = ft.cols, ds = 0, derase = 0; char dbcs = 0, odbcs = 0; // 0: none, 1: lead, 2: tail // reset dirty and display map memset(FTD, 0, ft.cols * sizeof(ftchar)); memcpy(FTDC,FTCMAP[y], ft.cols * sizeof(ftchar)); // first run: character diff for (x = 0; x < len; x++) { // build base dirty information if (FTCMAP[y][x] != FTOCMAP[y][x]) FTD[x] |= FTDIRTY_CHAR, ds++; if (FTAMAP[y][x] != FTOAMAP[y][x]) FTD[x] |= FTDIRTY_ATTR, ds++; // determine DBCS status if (dbcs == 1) { #ifdef FTCONF_PREVENT_INVALID_DBCS switch(fterm_DBCS_Big5(FTCMAP[y][x-1], FTCMAP[y][x])) { case FTDBCS_SAFE: // safe to print FTD[x-1] &= ~FTDIRTY_INVALID_DBCS; FTDC[x-1] = FTCMAP[y][x-1]; break; case FTDBCS_UNSAFE: // ok to print, but need to rawmove. FTD[x-1] &= ~FTDIRTY_INVALID_DBCS; FTDC[x-1] = FTCMAP[y][x-1]; FTD[x-1] |= FTDIRTY_CHAR; FTD[x] |= FTDIRTY_RAWMOVE; break; case FTDBCS_INVALID: // only SBCS safe characters can be print. if (!FTDBCS_ISSBCSPRINT(FTCMAP[y][x])) { FTD[x] |= FTDIRTY_INVALID_DBCS; FTDC[x] = FTCHAR_INVALID_DBCS; } break; } #endif // FTCONF_PREVENT_INVALID_DBCS dbcs = 2; // TAIL: dirty prev and me if any is dirty. if (FTD[x] || FTD[x-1]) { FTD[x] |= FTDIRTY_DBCS; FTD[x-1]|= FTDIRTY_CHAR; } } else if (FTDBCS_ISLEAD(FTCMAP[y][x])) { // LEAD: clear dirty when tail was found. dbcs = 1; #ifdef FTCONF_PREVENT_INVALID_DBCS FTD[x] |= FTDIRTY_INVALID_DBCS; FTDC[x] = FTCHAR_INVALID_DBCS; #endif // FTCONF_PREVENT_INVALID_DBCS } else { // NON-DBCS dbcs = 0; } if (odbcs == 1) { // TAIL: dirty prev and me if any is dirty. odbcs = 2; if (FTD[x] || FTD[x-1]) { FTD[x] |= FTDIRTY_CHAR; FTD[x-1]|= FTDIRTY_CHAR; } } else if (FTDBCS_ISLEAD(FTOCMAP[y][x])) { // LEAD: dirty next? odbcs = 1; } else { odbcs = 0; } } #ifndef DBG_SHOW_DIRTY if (!ds) continue; #endif // DBG_SHOW_DIRTY // Optimization: calculate ERASE section // TODO ERASE takes 3 bytes (ESC [ K), so enable only if derase >= 3? // TODO ERASE then print can avoid lots of space, optimize in future. for (x = ft.cols - 1; x >= 0; x--) if (FTCMAP[y][x] != FTCHAR_ERASE || FTAMAP[y][x] != FTATTR_ERASE) break; else if (FTD[x]) derase++; len = x+1; for (x = 0; x < len; x++) { #ifndef DBG_SHOW_DIRTY if (!FTD[x]) continue; #endif // !DBG_SHOW_DIRTY // Optimization: re-print or move? #ifndef DBG_DISABLE_REPRINT while (ft.ry == y && x > ft.rx && abs(x-ft.rx) < FTMV_COST) { int i; // Special case: we may be in position of DBCS tail... // Inside a refresh() loop, this will never happen. // However it may occur for the first print entering refresh. // So enable only space if this is the first run (!touched). // if we don't need to change attributes, // just print remaining characters for (i = ft.rx; i < x; i++) { // if same attribute, simply accept. if (FTAMAP[y][i] == ft.rattr && touched) continue; // XXX spaces may accept (BG=rBG), // but that will also change cached attribute. if (!FTCHAR_ISBLANK(FTCMAP[y][i])) break; if (FTATTR_GETBG(FTAMAP[y][i]) != FTATTR_GETBG(ft.rattr)) break; } if (i != x) break; // safe to print all! // printf("[re-print %d chars]", i-ft.rx); #ifdef DBG_SHOW_REPRINT // reverse to hint this is a re-print fterm_rawattr(FTATTR_MAKE(0, 7) | FTATTR_BOLD); #endif // DBG_SHOW_REPRINT for (i = ft.rx; i < x; i++) { fterm_rawc(FTDC[i]); FTAMAP[y][i] = FTOAMAP[y][i]; // spaces may change attr... ft.rx++; } break; } #endif // !DBG_DISABLE_REPRINT if (y != ft.ry || x != ft.rx) fterm_rawmove_opt(y, x); #ifdef DBCSAWARE if ((FTD[x] & FTDIRTY_DBCS) && (FT_DBCS_NOINTRESC)) { // prevent changing attributes inside DBCS } else #endif // DBCSAWARE #ifdef DBG_SHOW_DIRTY fterm_rawattr(FTD[x] ? (FTAMAP[y][x] | FTATTR_BOLD) : (FTAMAP[y][x] & ~FTATTR_BOLD)); #else // !DBG_SHOW_DIRTY fterm_rawattr(FTAMAP[y][x]); #endif // !DBG_SHOW_DIRTY fterm_rawc(FTDC[x]); ft.rx++; touched = 1; if (FTD[x] & FTDIRTY_RAWMOVE) { fterm_rawcmd2(ft.ry+1, ft.rx+1, 1, 'H'); } } if (derase) { fterm_rawmove_opt(y, len); fterm_rawclreol(); } } #endif // !_WIN32 // doing fterm_rawcursor() earlier to enable max display time fterm_rawcursor(); fterm_dupe2bk(); ft.dirty = 0; } // cursor management void getyx(int *y, int *x) { if (y) *y = ft.y; if (x) *x = ft.x; } void getmaxyx(int *y, int *x) { if (y) *y = ft.rows; if (x) *x = ft.cols; } void move(int y, int x) { ft.y = ranged(y, 0, ft.rows-1); ft.x = ranged(x, 0, ft.cols-1); } // scrolling void scrl(int rows) { if (!rows) return; if (rows > 0) { for (; rows > 0; rows--) scroll(); } else { for (; rows < 0; rows++) rscroll(); } } void scroll() { // scroll up int y; ftchar *c0 = FTCMAP[0], *oc0 = FTOCMAP[0]; ftattr *a0 = FTAMAP[0], *oa0 = FTOAMAP[0]; // prevent mixing buffered scroll up+down if (ft.scroll < 0) fterm_rawscroll(ft.scroll); // smart scroll: move pointers for (y = 0; y < ft.rows-1; y++) { FTCMAP[y] = FTCMAP[y+1]; FTAMAP[y] = FTAMAP[y+1]; FTOCMAP[y]= FTOCMAP[y+1]; FTOAMAP[y]= FTOAMAP[y+1]; } FTCMAP[y] = c0; FTAMAP[y] = a0; FTOCMAP[y]= oc0; FTOAMAP[y]= oa0; // XXX also clear backup buffer // must carefully consider if up then down scrolling. fterm_flippage(); clrregion(ft.rows-1, ft.rows-1); fterm_flippage(); clrregion(ft.rows-1, ft.rows-1); ft.scroll ++; // fterm_markdirty(); // should be already dirty } void rscroll() { // scroll down int y; ftchar *c0 = FTCMAP[ft.rows -1], *oc0 = FTOCMAP[ft.rows -1]; ftattr *a0 = FTAMAP[ft.rows -1], *oa0 = FTOAMAP[ft.rows -1]; // prevent mixing buffered scroll up+down if (ft.scroll > 0) fterm_rawscroll(ft.scroll); // smart scroll: move pointers for (y = ft.rows -1; y > 0; y--) { FTCMAP[y] = FTCMAP[y-1]; FTAMAP[y] = FTAMAP[y-1]; FTOCMAP[y]= FTOCMAP[y-1]; FTOAMAP[y]= FTOAMAP[y-1]; } FTCMAP[y] = c0; FTAMAP[y] = a0; FTOCMAP[y]= oc0; FTOAMAP[y]= oa0; // XXX also clear backup buffer // must carefully consider if up then down scrolling. fterm_flippage(); clrregion(0, 0); fterm_flippage(); clrregion(0, 0); ft.scroll --; // fterm_markdirty(); // should be already dirty } // output void addch (unsigned char c) { outc(c); } void addstr (const char *s) { outs(s); } void addnstr(const char *s, int n) { outns(s, n); } void addstring(const char *s) { outstr(s); } void outs(const char *s) { if (!s) return; while (*s) outc(*s++); } void outns(const char *s, int n) { if (!s) return; while (*s && n-- > 0) outc(*s++); } void outstr(const char *str) { if (!str) { fterm_prepare_str(0); return; } // calculate real length of str (stripping escapes) // TODO only print by the returned size fterm_prepare_str(fterm_strdlen(str)); outs(str); // maybe the source string itself is broken... // basically this check should be done by clients, not term library. #if 0 { int isdbcs = 0; while (*str) { if (isdbcs == 1) isdbcs = 2; else if (FTDBCS_ISLEAD(*str)) isdbcs = 1; else isdbcs = 0; str++; } if (isdbcs == 1) // incomplete string! outs("\b?"); } #endif } void outc(unsigned char c) { // 0xFF is invalid for most cases (even DBCS), if (c == 0xFF || c == 0x00) return; fterm_markdirty(); if (ft.szcmd) { // collecting commands ft.cmd[ft.szcmd++] = c; if ((ft.szcmd == 2 && c == '[') || (ANSI_IS_PARAM(c) && ft.szcmd < FTCMD_MAXLEN)) return; // process as command fterm_exec(); ft.szcmd = 0; } else if (c == ESC_CHR) { // start of escaped commands ft.cmd[ft.szcmd++] = c; } else if (c == '\t') { // tab: move by 8, and erase the moved range int x = ft.x; if (x % 8 == 0) x += 8; else x += (8-(x%8)); x = ranged(x, 0, ft.cols-1); // erase the characters between if (x > ft.x) { memset(FTCROW+ft.x, FTCHAR_ERASE, x - ft.x); memset(FTAROW+ft.x, ft.attr, x-ft.x); } ft.x = x; } else if (c == '\b') { ft.x = ranged(ft.x-1, 0, ft.cols-1); } else if (c == '\r' || c == '\n') { // new line: cursor movement, and do not print anything // XXX old screen.c also calls clrtoeol() for newlins. clrtoeol(); ft.x = 0; ft.y ++; while (ft.y >= ft.rows) { // XXX scroll at next dirty? // screen.c ignored such scroll. // scroll(); ft.y --; } } else if (iscntrl((unsigned char)c)) { // unknown control characters: ignore } else // normal characters { assert (ft.x >= 0 && ft.x < ft.cols); // normal characters FTC = c; #ifdef FTATTR_TRANSPARENT if (ft.attr != FTATTR_TRANSPARENT) #endif // FTATTR_TRANSPARENT FTA = ft.attr; ft.x++; // XXX allow x == ft.cols? if (ft.x >= ft.cols) { ft.x = 0; ft.y ++; while (ft.y >= ft.rows) { // XXX scroll at next dirty? // screen.c ignored such scroll. // scroll(); ft.y --; } } } } // readback int instr (char *str) { int x = ft.cols -1; *str = 0; if (ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) return 0; // determine stopping location while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE) x--; if (x < ft.x) return 0; x = x - ft.x + 1; memcpy(str, FTCROW+ft.x, x); str[x] = 0; return x; } int innstr (char *str, int n) { int on = n; int x = ranged(ft.x + n-1, 0, ft.cols-1); *str = 0; n = x - ft.x+1; if (on < 1 || ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) return 0; // determine stopping location while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE) x--; if (x < ft.x) return 0; n = x - ft.x + 1; if (n >= on) n = on-1; memcpy(str, FTCROW+ft.x, n); str[n] = 0; return n; } int inansistr (char *str, int n) { int x = ft.cols -1, i = 0, szTrail = 0; char *ostr = str; char cmd[FTATTR_MINCMD*2] = ""; ftattr a = FTATTR_DEFAULT; *str = 0; if (ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) return 0; if (n < 1) return 0; n--; // preserve last zero // determine stopping location while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE && FTAROW[x] == FTATTR_ERASE) x--; // retrieve [rt.x, x] if (x < ft.x) return 0; // preserve some bytes if last attribute is not FTATTR_DEFAULT for (i = ft.x; n > szTrail && i <= x; i++) { *str = 0; if (a != FTAROW[i]) { fterm_chattr(cmd, a, FTAROW[i]); a = FTAROW[i]; if (a != FTATTR_DEFAULT) szTrail = 3; // ESC [ m else szTrail = 0; if (strlen(cmd) >= n-szTrail) break; strcpy(str, cmd); n -= strlen(cmd); str += strlen(cmd); } // n should > szTrail *str ++ = FTCROW[i]; n--; } if (szTrail && n >= szTrail) { *str++ = ESC_CHR; *str++ = '['; *str++ = 'm'; } *str = 0; return (str - ostr); } // internal core of piaip's flat-term void fterm_flippage (void) { // we have only 2 pages now. ft.mi = 1 - ft.mi; } #ifndef fterm_markdirty void fterm_markdirty (void) { ft.dirty = 1; } #endif void fterm_dupe2bk(void) { int r = 0; for (r = 0; r < ft.rows; r++) { memcpy(FTOCMAP[r], FTCMAP[r], ft.cols * sizeof(ftchar)); memcpy(FTOAMAP[r], FTAMAP[r], ft.cols * sizeof(ftattr)); } } int fterm_DBCS_Big5(unsigned char c1, unsigned char c2) { // ref: http://www.cns11643.gov.tw/web/word/big5/index.html // High byte: 0xA1-0xFE, 0x8E-0xA0, 0x81-0x8D // Low byte: 0x40-0x7E, 0xA1-0xFE // C1: 0x80-0x9F if (FTDBCS_ISBADLEAD(c1)) return FTDBCS_INVALID; if (!FTDBCS_ISTAIL(c2)) return FTDBCS_INVALID; if (c1 >= 0x80 && c1 <= 0x9F) return FTDBCS_UNSAFE; return FTDBCS_SAFE; } int fterm_prepare_str(int len) { // clear and make (cursor, +len) as DBCS-ready. int x = ranged(ft.x, 0, ft.cols-1); int y = ranged(ft.y, 0, ft.rows-1); int dbcs = 0, sdbcs = 0; // TODO what if x+len is outside range? // check if (x,y) is in valid range if (x != ft.x || y != ft.y) return -1; len = ranged(x+len, x, ft.cols); for (x = 0; x < len; x++) { // determine DBCS status if (dbcs == 1) dbcs = 2; // TAIL else if (FTDBCS_ISLEAD(FTCROW[x])) dbcs = 1; // LEAD else dbcs = 0; if (x == ft.x) sdbcs = dbcs; } x = ft.x; // fix start and end if(sdbcs == 2 && x > 0) // TAIL, remove word x--; if (dbcs == 1 && len < ft.cols) // LEAD, remove word len ++; len = ranged(len, 0, ft.cols); len -= x; if (len < 0) len = 0; memset(FTCROW + x, FTCHAR_ERASE, len); memset(FTAROW + x, ft.attr, len); return len; } void fterm_exec(void) { ftchar cmd = ft.cmd[ft.szcmd-1]; char *p = (char*)ft.cmd + 2; // ESC [ int n = -1, x = -1, y; ft.cmd[ft.szcmd] = 0; if (isdigit(*p)) { n = atoi(p); while (*p && isdigit(*p)) p++; if (*p == ';') p++; // p points to next param now } switch(cmd) { // Cursor Movement case 'A': // CUU: CSI n A case 'B': // CUD: CSI n B case 'C': // CUF: CSI n C case 'D': // CUB: CSI n D // Moves the cursor n (default 1) cells in the given direction. // If the cursor is already at the edge of the screen, this has no effect. if (n < 1) n = 1; getyx(&y, &x); if (cmd == 'A') { y -= n; } else if (cmd == 'B') { y += n; } else if (cmd == 'C') { x += n; } else if (cmd == 'D') { x -= n; } move(y, x); break; case 'E': // CNL: CSI n E case 'F': // CPL: CSI n F // Moves cursor to beginning of the line // n (default 1) lines up/down (next/previous line). if (n < 1) n = 1; getyx(&y, &x); if (cmd == 'E') { y -= n; } else if (cmd == 'F') { y += n; } move(y, 0); break; case 'G': // CHA: CSI n G // Moves the cursor to column n. if (n < 1) n = 1; getyx(&y, &x); move(y, n-1); break; case 'H': // CUP: CSI n ; m H case 'f': // HVP: CSI n ; m f // Moves the cursor to row n, column m. // The values are 1-based, and default to 1 (top left corner) if omitted. // A sequence such as CSI ;5H is a synonym for CSI 1;5H as well as // CSI 17;H is the same as CSI 17H and CSI 17;1H y = n; if (y >= 0 && isdigit(*p)) x = atoi((char*)p); if (y < 0) y = 1; if (x < 0) x = 1; move(y-1, x-1); break; // Clear case 'J': // ED: CSI n J // Clears part of the screen. // If n is zero (or missing), clear from cursor to end of screen. // If n is one, clear from cursor to beginning of the screen. // If n is two, clear entire screen if (n == 0 || n < 0) clrtobot(); else if (n == 1) clrtohome(); else if (n == 2) { clrregion(0, ft.rows-1); } break; case 'K': // EL: CSI n K // Erases part of the line. // If n is zero (or missing), clear from cursor to the end of the line. // If n is one, clear from cursor to beginning of the line. // If n is two, clear entire line. Cursor position does not change. if (n == 0 || n < 0) clrtoeol(); else if (n == 1) clrtobeg(); else if (n == 2) clrcurrline(); break; case 's': // SCP: CSI s // Saves the cursor position. getyx(&ft.sy, &ft.sx); break; case 'u': // RCP: CSI u // Restores the cursor position. move(ft.sy, ft.sx); break; case 'S': // SU: CSI n S // Scroll whole page up by n (default 1) lines. // New lines are added at the bottom. if (n < 1) n = 1; scrl(n); break; case 'T': // SD: CSI n T // Scroll whole page down by n (default 1) lines. // New lines are added at the top. if (n < 1) n = 1; scrl(-n); break; case 'm': // SGR: CSI n [;k] m // Sets SGR (Select Graphic Rendition) parameters. // After CSI can be zero or more parameters separated with ;. // With no parameters, CSI m is treated as CSI 0 m (reset / normal), // which is typical of most of the ANSI codes. // --------------------------------------------------------- // SGR implementation: // SGR 0 (reset/normal) is supported. // SGR 1 (intensity: bold) is supported. // SGR 2 (intensity: faint) is not supported. // SGR 3 (italic: on) is not supported. (converted to inverse?) // SGR 4 (underline: single) is not supported. // SGR 5 (blink: slow) is supported. // SGR 6 (blink: rapid) is converted to (blink: slow) // SGR 7 (image: negative) is partially supported (not a really attribute). // SGR 8 (conceal) is not supported. // SGR 21(underline: double) is not supported. // SGR 22(intensity: normal) is supported. // SGR 24(underline: none) is not supported. // SGR 25(blink: off) is supported. // SGR 27(image: positive) is not supported. // SGR 28(reveal) is not supported. // SGR 30-37 (FG) is supported. // SGR 39 (FG-reset) is supported. // SGR 40-47 (BG) is supported. // SGR 49 (BG-reset) is supported. if (n == -1) // first param n = 0; while (n > -1) { if (n >= 30 && n <= 37) { // set foreground attrsetfg(n - 30); } else if (n >= 40 && n <= 47) { // set background attrsetbg(n - 40); } else switch(n) { case 0: attrset(FTATTR_DEFAULT); break; case 1: attrset(attrget() | FTATTR_BOLD); break; case 22: attrset(attrget() & ~FTATTR_BOLD); break; case 5: case 6: attrset(attrget() | FTATTR_BLINK); break; case 25: attrset(attrget() & ~FTATTR_BLINK); break; case 3: case 7: { ftattr a = attrget(); attrsetfg(FTATTR_GETBG(a)); attrsetbg(FTATTR_GETFG(a)); } break; case 39: attrsetfg(FTATTR_DEFAULT_FG); break; case 49: attrsetbg(FTATTR_DEFAULT_BG); break; } // parse next command n = -1; if (*p == ';') { n = 0; p++; } else if (isdigit(*p)) { n = atoi(p); while (isdigit(*p)) p++; if (*p == ';') p++; } } break; default: // unknown command. break; } } int fterm_chattr(char *s, ftattr oattr, ftattr nattr) { ftattr fg, bg, bold, blink, ofg, obg, obold, oblink; char lead = 1; if (oattr == nattr) return 0; *s++ = ESC_CHR; *s++ = '['; // optimization: reset as default if (nattr == FTATTR_DEFAULT) { *s++ = 'm'; *s++ = 0; return 1; } fg = FTATTR_GETFG(nattr); bg = FTATTR_GETBG(nattr); bold = (nattr & FTATTR_BOLD) ? 1 : 0; blink = (nattr & FTATTR_BLINK)? 1 : 0; ofg = FTATTR_GETFG(oattr); obg = FTATTR_GETBG(oattr); obold = (oattr & FTATTR_BOLD) ? 1 : 0; oblink = (oattr & FTATTR_BLINK)? 1 : 0; // we dont use "disable blink/bold" commands, // so if these settings are changed then we must reset. // another case is changing background to default background - // better use "RESET" to override it. // Same for foreground. // Possible optimization: when blink/bold on, don't RESET // for background change? if ((oblink != blink && !blink) || (obold != bold && !bold) || (bg == FTATTR_DEFAULT_BG && obg != bg) || (fg == FTATTR_DEFAULT_FG && ofg != fg) ) { if (lead) lead = 0; else *s++ = ';'; *s++ = '0'; ofg = FTATTR_DEFAULT_FG; obg = FTATTR_DEFAULT_BG; obold = 0; oblink = 0; } if (bold && !obold) { if (lead) lead = 0; else *s++ = ';'; *s++ = '1'; #ifdef FTCONF_WORKAROUND_BOLD // Issue here: // PacketSite does not understand ESC[1m. It needs ESC[1;37m // NetTerm defaults bold color to yellow. // As a result, we use ESC[1;37m for the case. if (fg == FTATTR_DEFAULT_FG) ofg = ~ofg; #endif // FTCONF_WORKAROUND_BOLD } if (blink && !oblink) { if (lead) lead = 0; else *s++ = ';'; *s++ = '5'; // XXX 5(slow) or 6(fast)? } if (ofg != fg) { if (lead) lead = 0; else *s++ = ';'; *s++ = '3'; *s++ = '0' + fg; } if (obg != bg) { if (lead) lead = 0; else *s++ = ';'; *s++ = '4'; *s++ = '0' + bg; } *s++ = 'm'; *s++ = 0; return 1; } int fterm_strdlen(const char *s) { char ansi = 0; int sz = 0; // the logic should match outc(). while (s && *s) { if (!ansi) // ansi == 0 { switch(*s) { case ESC_CHR: ansi++; break; case '\r': case '\n': break; case '\b': if (sz) sz--; break; case '\t': // XXX how to deal with this? sz ++; break; default: if (!iscntrl((unsigned char)*s)) sz++; break; } } else if (ansi == 1) { if (*s == '[') ansi++; else ansi = 0; } else if (!ANSI_IS_PARAM(*s)) // ansi == 2 { // TODO outc() take max to FTCMD_MAXLEN now... ansi = 0; } s++; } return sz; } void fterm_rawattr(ftattr rattr) { static char cmd[FTATTR_MINCMD*2]; if (!fterm_chattr(cmd, ft.rattr, rattr)) return; fterm_raws(cmd); ft.rattr = rattr; } void fterm_rawnum(int arg) { if (arg < 0 || arg > 99) { // complex. use printf. char sarg[16]; // max int sprintf(sarg, "%d", arg); fterm_raws(sarg); } else if (arg < 10) { // 0 .. 10 fterm_rawc('0' + arg); } else { fterm_rawc('0' + arg/10); fterm_rawc('0' + arg%10); } } void fterm_rawcmd(int arg, int defval, char c) { fterm_rawc(ESC_CHR); fterm_rawc('['); if (arg != defval) fterm_rawnum(arg); fterm_rawc(c); } void fterm_rawcmd2(int arg1, int arg2, int defval, char c) { fterm_rawc(ESC_CHR); fterm_rawc('['); // See FTCONF_ANSICMD2_OMIT // XXX Win/DOS telnet does now accept omitting first value // ESC[nX and ESC[n;X works, but ESC[;mX does not work. if (arg1 != defval || arg2 != defval) { #if (FTCONF_ANSICMD2_OMIT >= 2) if (arg1 != defval) #endif fterm_rawnum(arg1); #if (FTCONF_ANSICMD2_OMIT >= 1) if (arg2 != defval) #endif { fterm_rawc(';'); fterm_rawnum(arg2); } } fterm_rawc(c); } void fterm_rawclear(void) { fterm_rawhome(); // ED: CSI n J, 0 = cursor to bottom, 2 = whole fterm_raws(ESC_STR "[2J"); } void fterm_rawclreol(void) { #ifdef FTCONF_CLEAR_SETATTR // ftattr oattr = ft.rattr; // XXX If we skip with "backround only" here, future updating // may get wrong attributes. Or not? (consider DBCS...) // if (FTATTR_GETBG(oattr) != FTATTR_GETBG(FTATTR_ERASE)) fterm_rawattr(FTATTR_ERASE); #endif // EL: CSI n K, n = 0 fterm_raws(ESC_STR "[K"); #ifdef FTCONF_CLEAR_SETATTR // No need to do so? because we will always reset attribute... // fterm_rawattr(oattr); #endif } void fterm_rawhome(void) { // CUP: CSI n ; m H fterm_raws(ESC_STR "[H"); ft.rx = ft.ry = 0; } void fterm_rawmove_rel(int dy, int dx) { #ifndef FTCONF_USE_ANSI_RELMOVE // Old BBS system does not output relative moves (ESC[ABCD) . // Poor terminals (ex, pcman-1.0.5-FF20.xpi) // do not support relmoves fterm_rawmove(ft.ry + dy, ft.rx + dx); #else if (!dx) { int y = ranged(dy + ft.ry, 0, ft.rows-1); dy = y - ft.ry; if (!dy) return; fterm_rawcmd(abs(dy), 1, dy < 0 ? 'A' : 'B'); ft.ry = y; } else if (!dy) { int x = ranged(dx + ft.rx, 0, ft.cols-1); dx = x - ft.rx; if (!dx) return; fterm_rawcmd(abs(dx), 1, dx < 0 ? 'D' : 'C'); ft.rx = x; } else { // (dy, dx) are given - use fterm_move. fterm_rawmove(ft.ry + dy, ft.rx + dx); } #endif } void fterm_rawmove(int y, int x) { y = ranged(y, 0, ft.rows-1); x = ranged(x, 0, ft.cols-1); if (y == ft.ry && x == ft.rx) return; // CUP: CSI n ; m H fterm_rawcmd2(y+1, x+1, 1, 'H'); ft.ry = y; ft.rx = x; } void fterm_rawmove_opt(int y, int x) { // optimized move int ady = abs(y-ft.ry), adx=abs(x-ft.rx); if (!adx && !ady) return; #ifdef DBG_DISABLE_OPTMOVE return fterm_rawmove(y, x); #endif // known hacks: \r = (x=0), \b=(x--), \n = (y++) // // Warning: any optimization here should not change displayed content, // because we don't have information about content variation information // (eg, invalid DBCS bytes will become special marks) here. // Any hacks which will try to display data from FTCMAP should be done // inside dirty-map calculation, for ex, using spaces to move right, // or re-print content. #ifndef DBG_TEXT_FD // x=0: the cheapest output. However not work for text mode fd output. // a special case is "if we have to move y to up". // and FTCONF_ANSICMD2_OMIT < 1 (cannot omit x). #if FTCONF_ANSICMD2_OMIT < 1 if (y >= ft.ry) #endif if (adx && x == 0) { fterm_rawc('\r'); ft.rx = x = adx = 0; } #endif // !DBG_TEXT_FD // x--: compare with FTMV_COST: ESC[m;nH costs 5-8 bytes // in order to prevent wrap, don't use bs when rx exceed boundary (ft.cols) if (x < ft.rx && y >= ft.ry && (adx+ady) < FTMV_COST && ft.rx < ft.cols) { while (adx > 0) fterm_rawc('\b'), adx--; ft.rx = x; } // finishing movement if (y > ft.ry && ady < FTMV_COST && adx == 0) { while (ft.ry++ < y) fterm_rawc('\n'); ft.ry = y; } else if (ady && adx) { fterm_rawmove(y, x); } else if (ady) { fterm_rawmove_rel(y-ft.ry, 0); } else if (adx) { fterm_rawmove_rel(0, x-ft.rx); } } void fterm_rawcursor(void) { #ifdef _WIN32 COORD cursor; cursor.X = ft.x; cursor.Y = ft.y; SetConsoleCursorPosition(hStdout, cursor); #else // fterm_rawattr(FTATTR_DEFAULT); fterm_rawattr(ft.attr); fterm_rawmove_opt(ft.y, ft.x); fterm_rawflush(); #endif // !_WIN32 } void fterm_rawscroll (int dy) { #ifdef FTCONF_USE_ANSI_SCROLL // SU: CSI n S (up) // SD: CSI n T (down) char cmd = (dy > 0) ? 'S' : 'T'; int ady = abs(dy); if (ady == 0) return; if (ady >= ft.rows) ady = ft.rows; fterm_rawcmd(ady, 1, cmd); ft.scroll -= dy; #else // VT100 flavor: // * ESC D: scroll down // * ESC M: scroll up // // Elder BBS systems works in a mixed way: // \n at (rows-1) as scroll() // and ESC-M at(0) as rscoll(). // // SCP: CSI s / RCP: CSI u // Warning: cannot use SCP/RCP here, because on Win/DOS telnet // the position will change after scrolling (ex, (25,0)->(24,0). // // Since scroll does not happen very often, let's relax and not // optimize these commands here... int ady = abs(dy); if (ady == 0) return; if (ady >= ft.rows) ady = ft.rows; // we are not going to preserve (rx,ry) // so don't use fterm_move*. if (dy > 0) fterm_rawcmd2(ft.rows, 1, 1, 'H'); else fterm_rawcmd2(1, 1, 1, 'H'); for (; ady > 0; ady--) { if (dy >0) { // Win/DOS telnet may have extra text in new line, // because of the IME line. #ifdef FTCONF_USE_VT100_SCROLL fterm_raws(ESC_STR "D" ESC_STR "[K"); // ESC_STR "[K"); #else fterm_raws("\n" ESC_STR "[K"); #endif } else { fterm_raws(ESC_STR "M"); // ESC_STR "[K"); } } // Do not use optimized move here, because in poor terminals // the coordinates are already out of sync. fterm_rawcmd2(ft.ry+1, ft.rx+1, 1, 'H'); ft.scroll -= dy; #endif } void fterm_raws(const char *s) { while (*s) fterm_rawc(*s++); } void fterm_rawnc(int c, int n) { while (n-- > 0) fterm_rawc(c); } ////////////////////////////////////////////////////////////////////////// // grayout advanced control ////////////////////////////////////////////////////////////////////////// void grayout(int y, int end, int level) { char grattr = FTATTR_DEFAULT; y = ranged(y, 0, ft.rows-1); end = ranged(end, 0, ft.rows-1); if (level == GRAYOUT_COLORBOLD) { int x = 0; for (; y < end; y++) { for (x = 0; x < ft.cols-1; x++) FTAMAP[y][x] |= FTATTR_BOLD; } return; } if (level == GRAYOUT_COLORNORM) { int x = 0; for (; y < end; y++) { for (x = 0; x < ft.cols-1; x++) FTAMAP[y][x] &= ~(FTATTR_BLINK | FTATTR_BOLD); } return; } if (level == GRAYOUT_BOLD) { grattr |= FTATTR_BOLD; } else if (level == GRAYOUT_DARK) { grattr = FTATTR_MAKE(0,0); grattr |= FTATTR_BOLD; } else if (level == GRAYOUT_NORM) { // normal text } else { // not supported yet } for (; y <= end; y++) { memset(FTAMAP[y], grattr, ft.cols); } } ////////////////////////////////////////////////////////////////////////// // environment specific ////////////////////////////////////////////////////////////////////////// #ifndef _PFTERM_TEST_MAIN void scr_dump(screen_backup_t *psb) { int y = 0; char *p = NULL; psb->row= ft.rows; psb->col= ft.cols; psb->y = ft.y; psb->x = ft.x; p = psb->raw_memory = malloc (ft.rows * ft.cols * (sizeof(ftchar) + sizeof(ftattr))); for (y = 0; y < ft.rows; y++) { memcpy(p, FTCMAP[y], ft.cols * sizeof(ftchar)); p += ft.cols * sizeof(ftchar); memcpy(p, FTAMAP[y], ft.cols * sizeof(ftattr)); p += ft.cols * sizeof(ftattr); } } void scr_restore(const screen_backup_t *psb) { int y = 0; char *p = NULL; int c = ft.cols, r = ft.rows; if (!psb || !psb->raw_memory) return; p = psb->raw_memory; c = ranged(c, 0, psb->col); r = ranged(r, 0, psb->row); ft.y = ranged(psb->y, 0, ft.rows-1); ft.x = ranged(psb->x, 0, ft.cols-1); clrscr(); for (y = 0; y < r; y++) { memcpy(FTCMAP[y], p, c * sizeof(ftchar)); p += psb->col * sizeof(ftchar); memcpy(FTAMAP[y], p, c * sizeof(ftattr)); p += psb->col * sizeof(ftattr); } free(psb->raw_memory); ft.dirty = 1; refresh(); } void move_ansi(int y, int x) { move(y, x); } void getyx_ansi(int *y, int *x) { getyx(y, x); } void region_scroll_up(int top, int bottom) { int i; ftchar *c0; ftattr *a0; // logic same with old screen.c if (top > bottom) { i = top; top = bottom; bottom = i; } if (top < 0 || bottom >= ft.rows) return; c0 = FTCMAP[top]; a0 = FTAMAP[top]; for (i = top; i < bottom; i++) { FTCMAP[i] = FTCMAP[i+1]; FTAMAP[i] = FTAMAP[i+1]; } FTCMAP[bottom] = c0; FTAMAP[bottom] = a0; clrregion(bottom, bottom); fterm_markdirty(); } #endif ////////////////////////////////////////////////////////////////////////// // adapter ////////////////////////////////////////////////////////////////////////// int fterm_inbuf(void) { #ifdef _PFTERM_TEST_MAIN return 0; #else return num_in_buf(); #endif } void fterm_rawc(int c) { #ifdef _PFTERM_TEST_MAIN // if (c == ESC_CHR) putchar('*'); else putchar(c); #else ochar(c); #endif } void fterm_rawnewline(void) { #ifdef _PFTERM_TEST_MAIN putchar('\n'); #else ochar('\r'); ochar('\n'); #endif } void fterm_rawflush(void) { #ifdef _PFTERM_TEST_MAIN fflush(stdout); #else oflush(); #endif } ////////////////////////////////////////////////////////////////////////// // test ////////////////////////////////////////////////////////////////////////// #ifdef _PFTERM_TEST_MAIN int main(int argc, char* argv[]) { char buf[512]; initscr(); if (argc < 2) { #if 0 // DBCS test char *a1 = ANSI_COLOR(1;33) "測試" ANSI_COLOR(34) "中文" ANSI_REVERSE "測試" ANSI_RESET "測試" "測試a" ANSI_RESET "\n"; outstr(a1); move(0, 2); outstr("中文1"); outstr(ANSI_COLOR(1;33)"中文2"); outstr(" 中\x85"); outstr("okok herer\x8a"); move(0, 8); inansistr(buf, sizeof(buf)-1); move(3,5); outs(ANSI_RESET "(From inansistr:) "); outs(buf); move(7, 3); sprintf(buf, "strlen()=%d\n", fterm_strdlen(a1)); outstr(buf); refresh(); getchar(); outs(ANSI_COLOR(1;33) "test " ANSI_COLOR(34) "x" ANSI_RESET "te" ANSI_COLOR(43;0;1;35) " st" ANSI_COLOR(0) "testx\n"); refresh(); getchar(); clear(); outs("中文中文中文中文中文中文中文中文中文中文中文中文"); move(0, 0); outs(" this\xFF (ff)is te.(80 tail)->\x80 (80)"); refresh(); getchar(); #endif #if 1 // test resize clear(); move(1, 0); outs("test resize\n"); doupdate(); getchar(); // expand X resizeterm(25,200); clear(); move(20, 0); clrtoeol(); outs("200x25"); doupdate(); getchar(); // resize back resizeterm(25,80); clear(); move(20, 0); clrtoeol(); outs("80x25"); doupdate(); getchar(); // expand Y resizeterm(60,80); clear(); move(20, 0); clrtoeol(); outs("80x60"); doupdate(); getchar(); // see if you crash here. resizeterm(60,200); clear(); move(20, 0); clrtoeol(); outs("200x60"); doupdate(); getchar(); #endif #if 0 // test optimization clear(); move(1, 0); outs("x++ optimization test\n"); outs("1 2 3 4 5 6 7 8 9 0\n"); outs("1122233334444455555566666667777777788888888899999999990\n"); refresh(); getchar(); move(2, 0); outs("1122233334444455555566666667777777788888888899999999990\n"); outs("1 2 3 4 5 6 7 8 9 0\n"); refresh(); getchar(); rscroll(); refresh(); getchar(); #endif } else { FILE *fp = fopen(argv[1], "r"); int c = 0; while (fp && (c=getc(fp)) > 0) { outc(c); } fclose(fp); refresh(); } endwin(); printf("\ncomplete. enter to exit.\n"); getchar(); return 0; } #endif // _PFTERM_TEST_MAIN #endif // defined(EXP_PFTERM) || defined(USE_PFTERM) // vim:ts=4:sw=4:expandtab