'=========================================================================== ' Subject: ANSI EMULATOR Date: 07/24/92 (00:00) ' Author: Rich Geldreich Code: QB, PDS ' Keys: ANSI,EMULATOR Packet: ANSI.ABC '=========================================================================== 'PANSI.BAS v1.50 'ANSI emulator for QuickBASIC 4.5 and PDS 'By Richard Geldreich July 24, 1992 'Don't forget that "CALL INTERRUPT" is used- load QB with "QB/l" 'I have fixed up & improved the ANSI escape sequence state machine. It 'now works faster. I still don't know why I'm releasing this driver, 'because I'm going to release my all-assembly version very soon... '(the assembly version of this driver is light years ahead of this program!) 'See the PrintANSI procedure for a list of bug fixes. '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! '! Don't forget to modify the "SendStatus" procedure for your ! '! comm package! ! '! You also should modify PrintString for QB4.5 or PDS ! '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'I welcome any suggestions or ideas about this program... It _should_ 'emulate DOS's ANSI.SYS device driver... This program is in the public 'domain; do what you want with it! Have a ball!! Just try and give 'me some credit. Thanks. I have tested this driver out with many BBS's 'and door programs and it works fine. Please test this driver out before 'you release it in a program!!! 'NOTE: This program assumes that the current segment is always 'pointing twards the video buffer!! If you change the current 'segment don't forget to change it back or sparks will fly when you 'write to the screen! (see GetVSeg) 'Info: 'ClearScreen- used internally by the PrintAnsi procedure- you may 'use it to clear the current window(the current background color 'is used in the clear). The cursor is set to the upper left hand corner 'of the window after the window is cleared. 'CursorControl A- if A is non-zero then the SetCursor routine(which 'is called by PrintAnsi) will update the cursor whenever it is moved. 'If it is zero then SetCursor won't touch the cursor's position. 'GetVSeg- Returns the current video segment. 'Init- Initializes the driver. This should be called before any other 'procedure. Completly resets the entire driver, sets the window to the 'current screen page & size and moves the cursor to the upper left hand 'corner of the screen. 'Music A- if A is not zero, then ANSI music is enabled. 'PrintAnsi Char- where Char is an ASCII code from 0-255. Recognizes 'ANSI escape sequences(of course!). Processes the character and 'updates the display, if needed 'PrintString A$- prints a string to the display. Calls PrintAnsi for 'each character. Don't forget to modify this for PDS/QuickBASIC. 'ScrollUpScreen- scrolls up the current window. Uses a BIOS call. 'Normally used internally by PrintAnsi. 'SendStatus- sends a CPR sequence to the receiver. 'In other words, SendStatus will output the current X and Y coordinates 'of the cursor to the remote terminal. Used by some BBS's and doors 'to see if the user's terminal has ANSI capibilities. You must modify 'this procedure to output the status string to your comm package! '(this is used internally by PrintAnsi) 'SetCursor- moves the cursor to its correct position(it doesn't turn 'it on however- use the LOCATE , , 1 command to do that). This procedure 'should work on all adapters, but I haven't tested it out on many 'cards yet... Use this to restore the cursor to where it should be 'after you move it. If you want, change this procedure to use QB's 'LOCATE command instead of the OUT's. 'SetWindow WorkPage, Lx,Ly,Hx,Hy- defines a window where all text 'is printed. if WorkPage is -1, then the BIOS data area is examined for 'the current screen page, otherwise WorkPage must indicate which page to 'write to. If Lx is -1, the the window will take up the entire screen 'otherwise Lx and Ly are the upper-left lines of the window(where '1,1 is the upper corner of the screen) and Hx and Hy are the lower-right 'coordinates of the window. ' The current cursor position is moved to the upper left corner of the 'new window. If the coordinates passed are invalid, the window is not 'modified. ' That's all! You can add more functions if you need them; I've 'documented the PrintAnsi procedure enough for you to get 'a good idea of how it works. DEFINT A-Z DECLARE SUB ClearScreen () DECLARE SUB CursorControl (A%) DECLARE FUNCTION GetVSeg% () DECLARE SUB Init () DECLARE SUB Music (A%) DECLARE SUB PrintANSI (Char%) DECLARE SUB PrintString (B$) DECLARE SUB ScrollUpScreen () DECLARE SUB SendStatus (X%, Y%) DECLARE SUB SetCursor () DECLARE SUB SetWindow (WorkPage%, Lx%, Ly%, Hx%, Hy%) DECLARE SUB playme (A$) TYPE RegType Ax AS INTEGER Bx AS INTEGER Cx AS INTEGER Dx AS INTEGER bp AS INTEGER si AS INTEGER di AS INTEGER flags AS INTEGER END TYPE DIM SHARED Xpos, Ypos 'cursor's position DIM SHARED MinX, MinY, MaxX, MaxY 'current window DIM SHARED SaveX, SaveY 'used by SCR and RCP DIM SHARED Colors(7), Attribute DIM SHARED CursorOn, VideoSegment, VideoOffset, CursorAddress, BytesPerLine DIM SHARED Monochrome, CRT 'monochrome adapter flag DIM SHARED ANSIMusic, MusicLevel DIM SHARED Level CONST True = -1, False = NOT True 'usefull stuff 'The color translation table is used to translate an ANSI color 'to a screen color. ColorTable: DATA 0,4,2,6,1,5,3,7 '******START OF TEST PROGRAM 'The following code is not needed... It's only for testing! SCREEN 0 WIDTH 80, 25 CLS LOCATE , , 1 'turn cursor on Init ClearScreen 'clear the window SetWindow -1, 1, 1, 80, 25 'set window at (1,2)-(80,25) 'DO ' A$ = INKEY$: IF A$ <> "" THEN PrintString A$ 'LOOP 'test ANSI music PrintString CHR$(27) + "[MFO1CDEFGABC" + CHR$(14) 'A! = TIMER 'PrintString STRING$(5000, 65) 'B! = TIMER 'PRINT 5000 / (B! - A!) 'END 'a lame test Esc$ = CHR$(27) Up$ = CHR$(27) + "[A" Down$ = CHR$(27) + "[B" Lft$ = CHR$(27) + "[D" Rgt$ = CHR$(27) + "[C" Foreground = 31: Background = 40: Bold = 0 X = 1: Y = 1 DO A$ = CHR$(27) + "[" IF NOT Bold THEN A$ = A$ + "0;" ELSE A$ = A$ + "1;" PrintString A$ + MID$(STR$(Foreground), 2) + ";" + MID$(STR$(Background), 2) + "m" Bold = NOT Bold Foreground = Foreground + 1 IF Foreground > 37 THEN Foreground = 31 Background = Background + 1 IF Background > 47 THEN Background = 40 END IF PrintString CHR$(27) + "[s" + CHR$(219) + CHR$(27) + "[u" IF Xdirect THEN X = X - 1 PrintString Lft$ IF X = 1 THEN Xdirect = 0 ELSE X = X + 1 PrintString Rgt$ IF X = 80 THEN Xdirect = 1 END IF IF Ydirect THEN Y = Y - 1 PrintString Up$ IF Y = 1 THEN Ydirect = 0 ELSE Y = Y + 1 PrintString Down$ IF Y = 24 THEN Ydirect = 1 END IF LOOP UNTIL INKEY$ <> "" END '******END OF TEST PROGRAM 'Clears the current window. The cursor is also set to the upper-left hand 'corner of the window. SUB ClearScreen DIM Regs AS RegType Regs.Ax = &H600 A& = Attribute * 256& IF A& > 32767 THEN A = A& - 65536 ELSE A = A& Regs.Bx = A Regs.Cx = (MinY * 256&) + MinX - 257 Regs.Dx = (MaxY * 256&) + MaxX - 257 CALL interrupt(&H10, Regs, Regs) Xpos = MinX: Ypos = MinY SetCursor END SUB 'Enables or disables cursor updating. SUB CursorControl (A) IF A THEN CursorOn = True ELSE CursorOn = False END IF END SUB 'Returns the current video segment. FUNCTION GetVSeg GetVSeg = VideoSegment END FUNCTION 'Initializes everything. SUB Init DIM Regs AS RegType 'default color, white on black (or black on white??) Attribute = 7 Level = 0: MusicLevel = 0 'reset levels ANSIMusic = True 'ANSI music enabled CursorOn = True 'cursor movement enabled 'read in color translation table RESTORE ColorTable FOR A = 0 TO 7: READ Colors(A): NEXT Regs.Ax = 15 * 256 CALL interrupt(&H10, Regs, Regs) 'if AL=7 then card is monochrome. IF (Regs.Ax AND 255) = 7 THEN VideoSegment = &HB000 Monochrome = True ELSE VideoSegment = &HB800 Monochrome = False END IF DEF SEG = &H40 CRT = PEEK(&H63) + PEEK(&H64) * 256& 'Set segment to the screen. DEF SEG = VideoSegment 'window defaults to screen's page & size 'Xpos, Ypos, SaveX, SaveY, MinX, MinY, MaxX, MaxY, VideoOffset and the 'cursor are set up within this procedure SetWindow -1, -1, 0, 0, 0 END SUB 'Enables/Disables ANSI music... SUB Music (A) ANSIMusic = A END SUB 'Prints an ASCII character on the screen; filters out 'ANSI escape sequences and parses them. 'Fixups from last version(howcome nobody told me about these errors?!): ' A chr$(27) would not be processed correctly if received from within ' another escape sequence. This has been fixed. ' SetCursor now uses a BIOS variable to get the correct OUT address... It ' should now work on monochrome and color monitors. ' The cursor set, up & down commands are now not ignored if the cursor is ' set to a position that is invalid. ' The entire parameter table is set to 1 so special case tests do not ' have to be performed. Parameters will now be interpeted as 1 if they ' are zero in the cursor set commands(these two aren't bugs, just ' improvements!) ' ESC[m now resets the attribute to 7. The new page command, CHR$(12), now ' resets the screen to attribute 7 before clearing(not really a bug, but...) ' OOPS!! The cursor position command, ESC[H, was processed as an absolute ' coordinate relative to the upper-left hand of the screen... It should of ' been processed relative to the upper-left hand corner of the window! DUMB! ' So if the window was set to (1,2)-(80,25), and an ESC[H was received, the ' cursor would not move anywhere.... This of course has been fixed. ' ' I discovered almost all of these little bugs while coding the assembly ' version of the driver... SUB PrintANSI (Char) STATIC DIM Parameters(10) SELECT CASE Level CASE 0 'normal mode GOSUB ProcessChar CASE 1 'Level=1 after a chr$(27) is received. 'valid escape sequence? IF Char <> 91 THEN Level = 0 GOSUB ProcessChar ELSE 'a valid escape sequence has been received Level = 2 CurrentParameter = 0 NumParameters = 0 ValidParameter = False FOR A = 0 TO 10: Parameters(A) = 1: NEXT END IF CASE 2 'inside an escape sequence GOSUB ProcessCode END SELECT EXIT SUB ProcessChar: 'processes a non-ANSI code SELECT CASE Char 'process new page code CASE 12 Attribute = 7 ClearScreen 'process escape character CASE 27 Level = 1 'process enter CASE 13 Xpos = MinX SetCursor 'process line feed CASE 10 Ypos = Ypos + 1 IF Ypos > MaxY THEN Ypos = MaxY: ScrollUpScreen SetCursor 'process backspace(non-destructive) CASE 8 IF Xpos > MinX THEN Xpos = Xpos - 1 SetCursor END IF 'process tab key(tab stops=8) CASE 9 Xpos = ((Xpos \ 8) + 1) * 8 IF Xpos > MaxX THEN Xpos = MaxX SetCursor 'process bell CASE 7 'don't substitute a "BEEP" statement here! 'BEEP resets the cursor to where QB thinks it is! SOUND 3140, 1.25 'any other character is sent to the screen CASE ELSE 'prints a character to the screen POKE CursorAddress, Char: POKE CursorAddress + 1, Attribute CursorAddress = CursorAddress + 2 Xpos = Xpos + 1 IF Xpos > MaxX THEN Xpos = MinX Ypos = Ypos + 1 IF Ypos > MaxY THEN Ypos = MaxY ScrollUpScreen END IF SetCursor ELSE IF CursorOn THEN Address = CursorAddress \ 2 OUT CRT, &HE OUT CRT + 1, Address \ 256 OUT CRT, &HF OUT CRT + 1, Address AND 255 END IF END IF END SELECT RETURN 'processes a character within an ansi escape sequence 'non-valid characters are sent to the screen ProcessCode: 'handles ANSI music... IF MusicLevel > 0 THEN SELECT CASE MusicLevel 'see if the "F" in "ESC[MF" is received... CASE 1 IF Char <> 70 THEN '"F" MusicLevel = 0 Level = 0 GOSUB ProcessChar ELSE MusicLevel = 2 MusicString$ = "" END IF 'Either add a char to the music string or play it... CASE 2 IF Char <> 14 THEN 'fall out if an escape character is received... IF Char = 27 THEN MusicString$ = "" MusicLevel = 0 Level = 0 GOSUB ProcessChar 'assume the character received to be part of the 'PLAY string ELSE MusicString$ = MusicString$ + CHR$(Char) END IF ELSE IF ANSIMusic THEN 'play the string- the PLAY command is in a seperate 'module to keep error checking out of this module playme MusicString$ END IF MusicString$ = "" MusicLevel = 0 Level = 0 END IF END SELECT ELSE SELECT CASE Char CASE 77 '"M" MusicLevel = 1 CASE 48 TO 57 '0-9 'all parameters should be lower than 199... IF CurrentParameter < 199 THEN CurrentParameter = CurrentParameter * 10 + (Char - 48) ValidParameter = True ELSE Level = 0 GOSUB ProcessChar END IF CASE 59 GOSUB MakeParameter '";" 'CUP-set cursor's position CASE 72, 102 'H or f GOSUB MakeParameter Ypos = MinY + A - 1 A = Parameters(1): IF A = 0 THEN A = 1 Xpos = MinX + A - 1 IF Xpos > MaxX THEN Xpos = MaxX IF Ypos > MaxY THEN Ypos = MaxY SetCursor Level = 0 'CUU- cursor up CASE 65 'A GOSUB MakeParameter Ypos = Ypos - A IF Ypos < MinY THEN Ypos = MinY SetCursor Level = 0 'CUD-cursor down CASE 66 'B GOSUB MakeParameter Ypos = Ypos + A IF Ypos > MaxY THEN Ypos = MaxY SetCursor Level = 0 'CUF-cursor forward CASE 67 'C GOSUB MakeParameter Xpos = Xpos + A IF Xpos > MaxX THEN Xpos = MaxX SetCursor Level = 0 'CUB-cursor backward CASE 68 'D GOSUB MakeParameter Xpos = Xpos - A IF Xpos < MinX THEN Xpos = MinX SetCursor Level = 0 'SCR-save cursor position CASE 115 's SaveX = Xpos: SaveY = Ypos Level = 0 'RCP-restore cursor position CASE 117 'u Xpos = SaveX: Ypos = SaveY Level = 0 SetCursor 'ED-erase display(ESC[2J and ESC[J work 'both work) CASE 74 'J ClearScreen Level = 0 'EL-erase in line CASE 75 'K A = CursorAddress FOR X = Xpos TO MaxX POKE A, 32: POKE A + 1, Attribute: A = A + 2 NEXT Level = 0 'SGR-sets new color CASE 109 'm GOSUB MakeParameter 'if no color codes then stuff 0 into the table IF NumParameters = 0 THEN Parameters(0) = 0: NumParameters = 1 FOR A = 0 TO NumParameters - 1 P = Parameters(A) SELECT CASE P CASE IS <= 8 SELECT CASE P 'all attributes off CASE 0 Attribute = 7 'high-intensity CASE 1 Attribute = Attribute OR 8 'blinking CASE 5 Attribute = Attribute OR 128 'inverse CASE 7 Attribute = (Attribute AND 136) OR (Attribute AND 7) * 16 OR (Attribute AND 112) \ 16 END SELECT 'set foreground CASE 30 TO 37 IF NOT Monochrome THEN Attribute = (Attribute AND 248) OR Colors(P - 30) END IF 'set background CASE 40 TO 47 IF NOT Monochrome THEN Attribute = (Attribute AND 143) Attribute = Attribute OR Colors(P - 40) * 16 END IF END SELECT NEXT Level = 0 'DSR-outputs a CPR sequence 'This function outputs the string "ESC[#;#R" where '#;# is the current Y and current X coordinate 'to the receiver. 'Calls SendStatus to do its dirty work... CASE 110 SendStatus Xpos, Ypos Level = 0 'any other code is assumed to be invalid;it's just sent to the 'screen CASE ELSE Level = 0 GOSUB ProcessChar END SELECT END IF RETURN 'stores a numeric parameter into the parameter table MakeParameter: 'check to see if a least one digit has been received 'for this parameter and there's room left in the table IF ValidParameter AND NumParameters < 10 THEN 'add parameter to table Parameters(NumParameters) = CurrentParameter NumParameters = NumParameters + 1 CurrentParameter = 0 ValidParameter = False END IF 'Set A equal to the first parameter and make it 1 if it's 0 A = Parameters(0) IF A = 0 THEN A = 1 RETURN END SUB 'Prints a string to the display. SUB PrintString (B$) A& = SADD(B$) IF A& < 0 THEN A& = A& + 65536 STOP' You must change the next line if you're using QB4.5! 'It is currently coded for PDS. 'Segment = VARSEG(B$) + A& \ 16 Segment = SSEG(B$) + A& \ 16 'change to VARSEG(B$) for QB4.5 & QBASIC Address = A& MOD 16 FOR B = Address TO Address + LEN(B$) - 1 DEF SEG = Segment A1 = PEEK(B) DEF SEG = VideoSegment PrintANSI A1 NEXT END SUB SUB ScrollUpScreen DIM Regs AS RegType Regs.Ax = &H601 A& = Attribute * 256& IF A& > 32767 THEN A = A& - 65536 ELSE A = A& Regs.Bx = A Regs.Cx = (MinY * 256&) + MinX - 257 Regs.Dx = (MaxY * 256&) + MaxX - 257 CALL interrupt(&H10, Regs, Regs) END SUB 'Sends the screen's status to the receiver. You must modify the '"PRINT #1, A$;" command to print to your comm package. 'Sends "ESC[##;##R" where ##;## is Y;X. SUB SendStatus (X, Y) A$ = CHR$(27) + "[" + RIGHT$("0" + MID$(STR$(Y), 2), 2) A$ = A$ + ";" + RIGHT$("0" + MID$(STR$(X), 2), 2) + "R" '*****Change the next line to print this string out to your comm package!!**** PRINT A$; 'DON'T insert a line feed!! END SUB 'Sets the cursor- uses OUT's for speed SUB SetCursor 'Must do this... CursorAddress = (Xpos - 1) * 2 + (Ypos - 1) * BytesPerLine + VideoOffset IF CursorOn THEN Address = CursorAddress \ 2 OUT CRT, &HE OUT CRT + 1, Address \ 256 OUT CRT, &HF OUT CRT + 1, Address AND 255 END IF END SUB 'Sets a new printing window. SUB SetWindow (WorkPage, Lx, Ly, Hx, Hy) DEF SEG = &H40 IF WorkPage = -1 THEN VideoOffset = PEEK(&H4E) + PEEK(&H4F) * 256& ELSE VideoOffset = (PEEK(&H4C) + PEEK(&H4D) * 256&) * WorkPage END IF ScreenX = PEEK(&H4A) ScreenY = PEEK(&H84) + 1 IF Lx = -1 THEN MinX = 1: MinY = 1 MaxX = ScreenX: MaxY = ScreenY BytesPerLine = MaxX * 2 ELSE 'change window size if coordinates are valid IF Lx <= Hx AND Ly <= Hy AND Hx <= ScreenX AND Hy <= ScreenY THEN MinX = Lx: MaxX = Hx: MinY = Ly: MaxY = Hy END IF END IF DEF SEG = VideoSegment Xpos = MinX: Ypos = MinY SaveX = MinX: SaveY = MinY SetCursor END SUB '-------------8<----[ Begin PLAYME.BAS ]---->8---------------- 'A lone play command is present in this module so error trapping is 'seperate from the main module. This decreases the size of the driver. DEFINT A-Z ErrorHandler: RESUME NEXT SUB playme (A$) ON ERROR GOTO ErrorHandler PLAY A$ ON ERROR GOTO 0 END SUB '-------------8<----[ End PLAYME.BAS ]---->8----------------