/* * * * * * * * * * * * * * * * * * * * * CHK-ANSI.C (c) 1994, Jim Groeneveld * * * * * * * * * * * * * * * * * * * * * Routine ChkAnsiCPR Version 1.0 Date 30 April 1994 ------------------------------------------------------------------------------ Y. (Jim) Groeneveld, Schoolweg 14, 8071 BC Nunspeet, Nederland, 03412 60413. Email (work): groeneveld@tno.nl, groeneveld@cmi.tno.nl, groeneveld@nipg.tno.nl ------------------------------------------------------------------------------ * ChkAnsiCPR detects the presence of ANSI.SYS by sending a DSR escape sequence * to stdout and expecting a CPR escape sequence on stdin, both not redirected. * Instead of stdout stderr or CON may be used allowing stdout to be redirected, * though applying ANSI.SYS escape sequences to redirected stdout does not * always makes sense. If stdin is being redirected, receive CPR from file CON. * From another operating system input and output might be handled differently. * * DSR = Device Status Report ANSI escape sequence "Esc[6n". * DSR sent to stderr with ANSI.SYS loaded returns CPR on stdin. * CPR = Cursor Position Report ANSI escape sequence. * CPR = Esc[line#;column#R (line and column both counting from 1). * * Return values: 0 = ANSI.SYS not detected (NULL ptr) * ptr to CPR string = ANSI.SYS detected * Exit codes: ( 2 = unknown, unable to open SCREEN for write ) * ( 3 = unknown, unable to open KEYBOARD for read ) * ( 4 = unknown, unable to read KEYBOARD ) * Check these with ERRORLEVEL from DOS. Disclaimer ---------- The author is not liable for any negative consequences of the use or misuse of these routines. ----------------------------------------------------------------------- */ /*-----------------------------------DEFINE-----------------------------------*/ /*----------------------------------INCLUDE-----------------------------------*/ #include #include #include #include "chk-ansi.h" #if KEYBOARD > 1 #include "tokeybuf.h" #endif /* KEYBOARD > 1 */ #include "openread.h" /*--------------------------------ChkAnsiCPR----------------------------------*/ /* ChkAnsiCPR expects a first parameter of 0 or 1 indicating ANSI.SYS known to * be present (detected earlier already) (=1) or unknown (=0). If ANSI.SYS is * known to be present it is not necessary to use the keyboard buffer for any * scratch input and only the (expected) CPR string is read in order to obtain * the current cursor position. Care should be taken with ANSI.COM vs. 1.3 as * the ANSI driver, which should explicitely read from KEYBOARD values 0, 1 or * 2 in order not to wait for input via CON (which isn't there if ANSI.SYS is * indicated to be present) if KEYBOARD=3. If KEYBOARD=4 the indicator is set * to 0 anyway in order to cause an explicit check on ANSI.SYS using the * keyboard buffer as if ANSI.SYS is yet unknown (safest setting). * * ChkAnsiCPR also returns the cursor position via pointer arguments * if ANSI.SYS detected, call : char_ptr=ChkAnsiCPR(&Line,&Pos); */ char *ChkAnsiCPR(int AnsiSys, int *Vert, int *Horz) { FILE *Screen, *Keyboard; int Row, Column; int CPRfound; char Dummy[2]; /* Dummy to swallow forced 'empty' line */ static char CPRstr[CPRlen]; /* static CPR string storage */ /* static: to use its contents outside this function */ CPRfound=0; /* presence of ANSI.SYS, 0=no, !0=yes */ #if KEYBOARD == 4 AnsiSys=0; /* set knowledge of ANSI.SYS to 'unknown' for safety */ #endif /* KEYBOARD == 4 */ /* Print DSR to either stdout, stderr or CON. It gets 'lost' if stdout is * being redirected (test with 'isatty()', for drawbacks of isatty see doc.). * Use stderr or (better) CON instead. Only then it is captured and processed * by an eventual ANSI.SYS. Before printing DSR clear the keyboard buffer to * cause an eventual CPR string to be read as the first input. */ #if KEYBOARD > 1 /* Also with KEYBOARD=4, with kbhit() & getch() */ if (!AnsiSys) ClKeyBuf(); /* only if ANSI.SYS not yet known to be present */ #elif KEYBOARD == 1 /* Clear keyboard buffer via not-redirected stdin */ while (kbhit()) /* read stdin until empty to clear keyboard buffer (stdin) */ getch(); /* Reads and skips a complete eventually redirected input file! */ /* If stdin is redirected CPR can not be determined anymore from stdin! */ #endif /* KEYBOARD */ /* No clear of stdin with KEYBOARD = 0 */ #if SCREEN == 3 /* Open SCREEN ("CON") for write */ /* DOS, how about Unix? */ Screen=OpenFile(CONSOLE,"w",2); fprintf (Screen,DSR); /* print DSR to SCREEN */ #elif SCREEN == 2 Screen = stderr; fprintf (Screen,DSR); /* print DSR to SCREEN */ #else /* SCREEN = 1 */ Screen = stdout; printf (DSR); /* print DSR to SCREEN */ /* This must be so and thus different from the other SCREENs, because * - fprintf (any_file_pointer,......) does not close the file of course, * - fprintf (stderr,.......) closes stderr apparently implicitely after write, * - fprintf (stdout,.......) does not close stdout implicitely, while * - printf (.........) apparently closes stdout implicitely after write * and kbhit (below with KEYBOARD<=1) needs a closed (output) file before it * indicates a hit on stdin. Yet the other KEYBOARD input types using only * the statements fgets() or getch() already work if the statement here is: * fprintf (Screen,DSR); while the SCREEN file does not need to be closed! * This at least has been experienced with MSDOS 5.0 ANSI.SYS. */ #endif /* SCREEN */ /* After printing DSR to the screen an eventual CPR string has to be received. * Using kbhit and getch only works with a non-redirected stdin, because these * always read from stdin. (Alternative routines may be written to always read * the keyboard. But ANSI.SYS does not send CPR to the keyboard buffer, but to * the file "CON" instead.) However, it has appeared that the CPR string always * contains a trailing newline, so the string may be read using fgets. To * prevent waiting for user input if ANSI.SYS is not loaded and there is no * CPR string a CR (read as newline) will be written to the keyboard buffer * in advance, read via CON (or not-redirected stdin). So if only an 'empty' * line is received this indicates no CPR string and thus no present ANSI.SYS. * If, however, ANSI.SYS is loaded and the CPR string has been received an * additional fgets has to be performed to force swallowing the always present * 'empty' line. After that any routine may read stdin the regular way. * Instead of a CR a ^Z will be fed into the buffer if using getch() to read, * in order to be able to wipe out the visible DSR sequence without ANSI.SYS. * * Furthermore it has appeared that reading stdin using kbhit and getch also * reads the initial ESC character in the CPR string, while nothing is visible * on the screen. If using fgets the ESC apparently works via DOS and causes * a cancel of the 'current line', resulting in only the remaining characters, * starting with a '['. */ #if KEYBOARD > 1 /* Not with KEYBOARD<=1, with kbhit() & getch() */ /* Feed a string into the keyboard buffer (might even be done before DSR). * The string consists of a ^Z with INPUT=1 (getch) and a CR in all other cases. * This enables to wipe out the DSR from the screen, if ANSI.SYS is not present. * Other INPUTs would need an additional CR yet to process the ^Z, so that does * not yield the possibility to wipe out DSR while still on the same line. * * This is suppressed if ANSI.SYS is known to be present (AnsiSys=1). */ if (!AnsiSys) /* only if ANSI.SYS not yet known to be present */ #if INPUT == 1 { ToKeyBuf("\x1A",1); /* feed ^Z, ascii-26 or EOF character */ #else /* INPUT != 1 */ { ToKeyBuf("\r",1); /* feed CR, ENTER, ascii-13 */ #endif /* INPUT */ } #endif /* KEYBOARD > 1 */ /* Try to read the CPR string */ #if KEYBOARD >= 3 /* Open KEYBOARD for read to read eventual CPR, but in any case the explicit newline should be read. */ Keyboard=OpenFile(KEYBD,"rb",3); /* "rb" for binary */ #elif KEYBOARD <= 2 Keyboard = stdin; #endif /* KEYBOARD */ #if KEYBOARD >= 2 && INPUT == 0 /* stdin or "CON" using fgets() */ ReadLine(CPRstr,CPRlen,Keyboard,4); if (CPRstr[0]==27 || CPRstr[0]=='[') /* check 1st char,if ESC or [ then CPR */ { CPRfound=1; /* CPRstring starts with '[' and may be interpreted */ if (!AnsiSys) ReadLine(Dummy,2,Keyboard,4); /* Read additional newline */ } else CPRfound=0; /* check 1st char, probably NL, no CPR */ #elif KEYBOARD >= 2 && INPUT >= 1 /* stdin or "CON" using INPUTs */ /* (Eventual keys already present in the keyboard buffer would be passed via * stdin only after the processing of the CPR sequence, which might possibly * be generated and returned via the not-redirected stdin later.) * * Does not work with redirected stdin (unless redirected from CON: = 2 */ if (CPRfound) /* If ANSI.SYS probably detected: check and */ { if (CheckCPR(CPRstr,&Row,&Column)) /*get reported row and column numbers*/ { *Vert = Row; *Horz = Column; } else CPRfound=0; /* No valid CPR string found afterwards */ } #if KEYBOARD <= 1 || KEYBOARD == 4 /* Read stdin using kbhit() and getch() only. any INPUT The newline in the CPR string from ANSI.SYS actually is a CR to be recognized as '\r', not '\n' by getch(). (If getc() would be used it would have to be viewed as '\n'.) */ /* This presents an improvement by the addition of the use of the function * kbhit(), which, like getch(), applies to stdin (whether redirected or not). * It would then not be necessary to feed an explicit CR into the keyboard * buffer, because kbhit() would indicate whether there is something (the CPR * sequence) without causing a wait-for-user-input. Thus the side effect of * the routine, its clearing of the keyboard buffer and using it for itself, * would not be necessary. This routine initially only searches for a leading * ESC character input via stdin; if it is found the whole CPR string gets * read subsequently; if not then the character, if any, is 'unget'. * However, this works with not-redirected stdin only (unless redirected from * CON: */ CPRx++; /* increment CPR string index */ } if (CPRfound) CPRstr[++CPRx]='\0'; /* add trailing \0 */ if (CPRfound) /* If ANSI.SYS probably detected: check and */ { if (CheckCPR(CPRstr,&Row,&Column)) /*get reported row and column numbers*/ { *Vert = Row; *Horz = Column; } else CPRfound=0; /* No valid CPR string found afterwards */ } #if SCREEN == 3 /* Reopen SCREEN ("CON") for write of backspaces later */ Screen=OpenFile(CONSOLE,"w",2); #endif /* SCREEN == 3 */ } #endif /* KEYBOARD <= 1 || KEYBOARD == 4 */ #if KEYBOARD >= 3 fclose (Keyboard); #endif /* KEYBOARD >= 3 */ /* If ANSI.SYS is not present then erase visible DSR Esc sequence: * Esc"[6n" is written to the screen as 4 characters, not eaten by ANSI.SYS. */ #if KEYBOARD <= 1 || INPUT == 1 /* KEYBOARD <= 1: Nothing forced, INPUT=1: only ^Z forced (if AnsiSys=0), * so still on same line as DSR (4 visible characters, 3 if AnsiSys=1). */ if (!CPRfound) { if (!AnsiSys) fprintf (Screen,"\b\b\b\b \b\b\b\b"); /* extra BS's to position */ else /* if ANSI.SYS claimed present and yet unfortunately not found! */ fprintf (Screen,"\b\b\b \b\b\b"); /* extra BS's to position, no ^Z */ } /* else if KEYBOARD > 1 && INPUT != 1 : * CR/NL forced, not able to erase DSR if no CPR found, so nothing to do */ #endif /* KEYBOARD and INPUT */ /* Could be solved by not forcing a CR, but some other character, like a ^Z, * that should initially be read as a single character instead of an empty line. * (Then 5 instead of 4 characters would have to be erased!) * But that would not work because explicit fgets() and the rest of the used * INPUT functions need a CR before processing. So that is no solution. */ #if KEYBOARD >= 2 && INPUT != 1 /* anything but getch() used */ /* Erase visible CPR escape sequence using ANSI escape sequences and * return cursor to original position if possible (if screen not scrolled). */ if (CPRfound==1)/* not -1 with KEYBOARD=4 thus only using one of the INPUTs */ { if (!AnsiSys) fprintf (Screen,CUU); /* go to line with CR from CPR */ fprintf (Screen,CUU); /* go to line with '[' and rest of CPR */ #if INPUT == 4 fprintf (Screen,CUU); /* with fscanf() this is one line more up */ #endif /* INPUT == 4 */ fprintf (Screen,"%s%s", /* CPR string can be anywhere on the line */ CUL, /* thus move to the leftmost position */ EL); /* and erase whole line from cursor */ /* CUP can be used by determining scrolling from reported Row if that is * a value lower than 22 with INPUT=4 and lower than 23 in all other cases. * A 25 line screen is assumed! */ #if INPUT == 4 if (Row<22+AnsiSys) /* AnsiSys may only have value 0 or 1 */ #else /* INPUT != 4 */ if (Row<23+AnsiSys) /* AnsiSys may only have value 0 or 1 */ #endif /* INPUT */ /* screen lines certainly not scrolled */ fprintf (Screen,"%c%c%d%c%d%c%s", 27,'[',Row,';',Column,'H', /* go to original cursor position with CUP */ " \b"); /* and erase '\' by overwriting with space */ else /* screen lines scrolled */ { fprintf (Screen, CUU); /* go to line with '\', may be scrolled up */ if (Column>1) fprintf (Screen,"%s%d%c", /* value 0 causes at least 1 */ "\x1B[",Column-1,'C');/* go to column of '\', from CPR */ fprintf (Screen, " \b"); /* overwrite space and backspace '\' */ } } else if (CPRfound==-1 && !AnsiSys) fprintf (Screen,CUU); /* undo initial explicit CR from keyboard buffer */ /* If the function ChkAnsiCPR is being used to obtain the current cursor * position within a (screen oriented) program its side effect of disturbing * the screen contents (and changing the cursor position anyway) by returning * the CPR string visibly via the screen is a drawback of this approach. * Reading the file "CON" can only be done (independent of eventual redirection * of stdin) using either the fgets(), getc(), fread() or fscanf functions that * cause their input from "CON" to be visible on the screen (buffered input). * (Only getch() from a not-redirected stdin would not show its unbuffered * input characters.) * While the CPR sequence gets wiped out using the statements above this * disrupts the screen, possibly severely. If the function ChkAnsiCPR only is * being used to detect the presence of ANSI.SYS within a ((partially) line * oriented) program this is no disadvantage. * * The other definitions (with getch() from stdin) do not show the above * little problem, but have the drawback that stdin may not be redirected. * However, if one is sure that the function is only to be used without * redirected stdin then the KEYBOARD = 1 or 0 definition is the best choice. * This does not mean that one could test for redirection using 'isatty()', * because that would already be too late if stdin appears to be redirected. * One should know for sure in advance that stdin never gets redirected, * e.g. by calling this program only from within a known BATch file. * * Of course ChkAnsiCPR might test for a redirected stdin using 'isatty()' and * based upon the result dynamically select to obtain its input from stdin or * from CON, but that would leave the screen disrupting problem * described above with reading from CON if stdin appears to be redirected. * Besides 'isatty()' has severe drawbacks as described in the documentation. */ #endif /* KEYBOARD >= 2 && INPUT != 1 */ #if SCREEN == 3 fclose (Screen); #endif /* SCREEN == 3 */ /*The CPR string has been interpreted using CheckCPR to deduce cursor position*/ return CPRfound ? CPRstr : NULL; } /* end of */ #if KEYBOARD > 1 && INPUT >= 1 /*---------------------------------InputChar----------------------------------*/ char InputChar (FILE *Keyboard) { int c; #if INPUT == 1 /* getch() (from stdin only) */ c = getch(); /* No EOF test on redirected stdin */ if (c == '\r') c = '\n'; /* getch() reads CR, convert into NL */ #elif INPUT == 2 /* getc() */ if ( (c=getc(Keyboard)) == EOF ) /* ascii-255 returns EOF too if c is char */ #elif INPUT == 3 /* fread() */ if ( (fread(&c,sizeof(char),1,Keyboard)) != 1) #elif INPUT == 4 /* fscanf() */ if ( (fscanf(Keyboard,"%c",&c)) != 1) #endif /* INPUT */ #if INPUT >= 2 { fprintf (stderr,"Unable to read character from input device.\n"); exit (4); } #if INPUT == 3 /* fread() */ c = (char) c; /* because testing below with ((char)c) does not work %%% */ if (c == '\r') c = InputChar(Keyboard); /* fread() reads both CR and LF */ #endif /* INPUT == 3 */ /* getc() and fscanf() read NL from CR(LF) only */ #endif /* INPUT >= 2 */ return c; } /* end of */ #endif /* KEYBOARD > 1 && INPUT >= 1 */ /*----------------------------------CheckCPR----------------------------------*/ int CheckCPR (char *CPRstr, int *Row, int *Column) { char *c; if (c=strchr(CPRstr,'[')) /* find first number after '[' */ { *Row = atoi(++c); /* and interprete it */ if (c=strchr(c,';')) /* find first number after ';' */ { *Column = atoi(++c); /* and interprete it */ if (!strchr(c,'R')) return 0; /* find character 'R', not found */ } else return 0; /* no ';' found (2nd if......) */ } else return 0; /* no '[' found (1st if......) */ return 1; /* valid CPR string interpreted */ } /* end of */ /*---------------------------- That's all folks! ----------------------------- ---------------------------------History----------------------------------- Vs. 1.0 Initial working release with side effect of clearing keyboard buffer 30/04-94 and possible drawback of disrupting the screen with a screen oriented program (if included as a function) ----------------------------------Future----------------------------------- - maybe a solution for preventing CPR string on screen, who has a suggestion? ---------------------------------Remarks----------------------------------- - works correctly with ANSI.SYS of MSDOS 5.0 and HP's MSDOS 3.1 and 3.3, and with QWIKANSI.SYS ((C) 1986, Michael J. Acker); - does not work quite correctly with ANSI.COM vs. 1.3 ((C) 1988 Ziff Communications Co. PC Magazine by Michael J. Mefford). It does not work while reading from CON (KEYBOARD=3). It is yet unknown why the CPR can not be read using CON. Apparently it is not going via CON, maybe only via stdin, but if that actually is being redirected how will the CPR fit in? Is the stdin file handle lost if redirected? Or is ANSI.COM not quite as compatible with a standard ANSI.SYS as might be expected? */