page 56,132 title WBIOS Window BIOS extension .sall ; ; Written 1984 By J. Eric Roskos; public domain. ; No support is provided by the author; you must make any desired ; revisions yourself. ; ; This program provides an extension to the BIOS function calls, ; functions 40H-43H. For a description of the calls, see the Turbo ; Pascal program WDEMO.PAS, which also shows the preferred way of ; using the functions. Note, however, that this BIOS extension is ; independent of any particular language, and will work with any ; program that does its I/O through the ROM BIOS's "WRITE_TTY" ; function (the plain MS-DOS display driver does this, for instance). ; ; There are a few enhancements to the IBM BIOS call: TABs are handled ; properly, BELs produce a more bell-like sound, and there is a "Raw" ; mode (although you'll have to add the function call to set it your- ; self) in which all characters are displayed as-is, without translation ; for CR, LF, BEL, etc. There is also an option to turn end-of-line ; wraps on or off, although again you must add the function call. ; To turn these modes on, set the variables "wrap" and "raw" as ; explained below in the comments. (These features were included in ; the program for future expansion, but have never been tested, and ; aren't guaranteed to work). ; ; ; Macros ; ; ; wto: write message to display. Append cr/lf unless crsup=nocr ; wto macro msg,crsup local msgstr,around jmp around msgstr db msg ifb db 0DH,0AH endif db '$' around: push ax push bx push si push di push bp mov si,offset msgstr call putc pop bp pop di pop si pop bx pop ax endm prtreg macro rg,msg ; put register rg with message msg on display push ax mov ax,rg call prtax wto msg,nocr pop ax endm ; ; Code Segment ; cseg segment para public 'code' assume cs:cseg,ds:cseg,ss:cseg,es:nothing org 100H ; ; COM program startup ; cpstart proc far jmp near ptr start cpstart endp ; ; Local Procedures ; lcpos dw 0E60H putc proc near ; write a string to display W/O DOS intervention push es push ax mov ax,crt_seg mov es,ax mov di,cs:lcpos putc1: mov al,cs:[si] cmp al,'$' je putc2 cmp al,0dH jne putc3 mov cs:lcpos,0E60H mov di,0E60H jmp putc4 putc3: mov es:[di],al inc di inc di putc4: inc si jmp putc1 putc2: mov cs:lcpos,di pop ax pop es ret putc endp prtnum proc near ; print half a byte on screen push ds ; this procedure is used by prtax below push es push cs pop ds push bx mov bx,crt_seg mov es,bx mov bx,offset xltab xlatb mov bx,cs:lcpos mov es:[bx],al inc cs:lcpos inc cs:lcpos pop bx pop es pop ds ret xltab db '0123456789ABCDEF' prtnum endp prtax proc near ; print contents of ax register on screen push cx ; all registers are preserved push ax mov al,ah mov cl,4 shr al,cl call prtnum pop ax push ax mov al,ah and al,0Fh call prtnum pop ax push ax mov cl,4 shr al,cl call prtnum pop ax push ax and al,0Fh call prtnum pop ax pop cx ret prtax endp ; ; BIOS call dispatcher ; ; ; data area ; ; ; vector to ROM BIOS ; romoff dw ? romseg dw ? ; ; display variables ; active_page db ? ; parameters for wtty (window I/O) proc. crt_mode db ? color db 7 rt_edge db 79 bot_edge db 24 lf_edge db 0 top_edge db 0 wd_width db 79 ; window width and height (really the coords of wd_height db 24 ; bottom corner relative to strt of window) wrap db 0 ; 0=don't CRLF at right margin, 1=do raw db 0 ; 0=xlate cr, lf, etc, 1=display w/o xlation crt_seg dw ? ; segment for CRT display RAM ; ; clock ; tick dw 0 ; 1/18th second counter tock dw 0 ; every-5-seconds counter (18.2 Hz adjust) clk_oset dw 0 ; save area for displaced clock int handler clk_seg dw 0 ; ; window stack: ; 0[wsp] = left edge of window ; 1[wsp] = top edge of window ; 2[wsp] = right edge of window ; 3[wsp] = bottom edge of window ; 4[wsp] = cursor address (2 bytes) ; 6[wsp] = offset of window save area (2 bytes) ; 8[wsp] = segment of window save area (2 bytes) ; 10[wsp] = number of frames (1 byte) ; wssize equ 13 ; number of bytes in window stack frame wsframes equ 15 ; number of windows that can be stacked wsp dw offset wsend ; display window stack pointer wstack db wssize*wsframes dup (?) ; display window save stack wsend equ this word ; bottom of stack db wssize dup (?) ; extra frame in case of user error ; ; window queue: save area for windows ; wqp dw offset qbase ; next avail byte in window queue wqsize equ 8192 ; size of window queue - ok to tune qlim dw ? ; max address in window queue ; ; temp dw ? ; used when fixing up stack for return ; ; jump table for our BIOS functions ; jmptab label word dw offset setwindow dw offset pushwindow dw offset rstwindow dw offset frame tablen equ $-jmptab ; ; the dispatcher ; disp proc far ; ; see if it's one of our functions ; cmp ah,14 ; write TTY function jne tryrcp jmp ourfn tryrcp: cmp ah,3 ; read cursor position jne tryscp jmp readcsr tryscp: cmp ah,2 ; set cursor position je adjxy ; have to adjust coordinates cmp ah,6 ; scroll screen up jne trysd ; have to adjust corners jmp adjsc trysd: cmp ah,7 ; scroll screen down jne tryus ; adjust corners jmp adjsc tryus: cmp ah,40H ; one of our new functions? jl callbios ; no, call the BIOS jmp ourfn callbios: push cs:romseg ; not one of ours; call BIOS push cs:romoff ret ; ; adjust x and y coordinates for set-cursor-position ; adjxy: push dx ; save caller's dx add dh,cs:top_edge ; adjust the coordinates cmp dh,cs:bot_edge jle adjxy1 ; if below the bottom edge, scroll up a line push ax ; save caller's registers push bx push cx push dx push si push di mov ax,0601H ; scroll up one line mov cx,0 ; in the current window mov dh,cs:wd_height mov dl,cs:wd_width mov bh,cs:color int 10H pop di ; restore caller's registers pop si pop dx pop cx pop bx pop ax mov dh,cs:bot_edge ; now set cursor to bottom adjxy1: add dl,cs:lf_edge cmp dl,cs:rt_edge jle adjxy2 mov dl,cs:rt_edge ; fix up stack so BIOS's IRET will return to ; our adjxy3 label adjxy2: mov cs:temp,ax ; save ax (can't push it!) push ax ; a dummy flag register save push cs ; return cs mov ax,offset adjxy3 ; return ip push ax mov ax,cs:temp ; restore saved ax jmp callbios ; and call the BIOS adjxy3: pop dx ; back from BIOS now: restore caller's iret ; dx, and return. ; ; adjust corners for scroll ; adjsc: push bx add ch,cs:top_edge ; adjust row posn of upper left cmp ch,cs:bot_edge jle adjsc1 mov ch,cs:bot_edge adjsc1: add cl,cs:lf_edge ; adjust column posn of upper left cmp cl,cs:rt_edge jle adjsc2 mov cl,cs:rt_edge adjsc2: add dh,cs:top_edge ; adjust row posn of lower right cmp dh,cs:bot_edge jle adjsc3 mov dh,cs:bot_edge adjsc3: add dl,cs:lf_edge ; adjust column posn of lower right cmp dl,cs:rt_edge jle adjsc4 mov dl,cs:rt_edge adjsc4: mov bl,cs:bot_edge ; adjust number of lines to scroll sub bl,cs:top_edge cmp al,bl jle adjsc5 mov al,bl adjsc5: pop bx jmp callbios readcsr: ; read cursor position push ds ; save registers that must be kept push bx mov ax,40H ; switch to ROM BIOS data segment mov ds,ax mov bl,bh ; get param page number into bx xor bh,bh sal bx,1 ; convert to cursor table offset mov dx,[bx+50H] ; read csr posn from cursor table mov cx,word ptr 60H ; read csr mode from mode word push cs ; switch to our data segment pop ds sub dh,top_edge ; adjust position for window corner sub dl,lf_edge ; make sure cursor address is in range -- adjust if not cmp dh,0 ; check not above first row jge lrowok mov dh,0 lrowok: cmp dh,wd_height ; check not below last row jle hrowok mov dh,wd_height hrowok: cmp dl,0 ; check not left of first column jge lcolok mov dl,0 lcolok: cmp dl,wd_width ; check not right of last column jle hcolok mov dl,wd_width hcolok: pop bx ; restore bx register pop ds ; switch to caller's data segment iret ; and return ourfn: ; ; Process one of our functions. ; cld ; save all registers except ax push es push ds push dx push cx push bx push si push di push cs ; switch to our data/code segment push cs pop ds pop es ; find what routine they are invoking and call it cmp ah,14 jne newfns call wtty jmp ourret newfns: mov al,ah xor ah,ah sub al,40H sal ax,1 mov si,ax cmp ax,tablen jb validjmp jmp ourret validjmp: call word ptr [si+offset jmptab] ourret: pop di pop si pop bx pop cx pop dx pop ds pop es clc cmp ax,0 ; did we return an error code? je ourretok ; no, leave carry cleared stc ; set carry ourretok: ret 2 ; discard flags from interrupt disp endp ; ; setwindow - set corners of the current display window. ; (ch,cl) = (row,column) of upper left corner ; (dh,dl) = (row,column) of lower right corner ; setwindow proc near mov lf_edge,cl mov top_edge,ch mov rt_edge,dl mov bot_edge,dh mov al,dh ; compute relative bottom corner of window sub al,ch mov wd_height,al mov al,dl sub al,cl mov wd_width,al mov ax,0 ; always return successful ret setwindow endp ; ; setcolor - set attribute byte for display to al ; setcolor proc near mov color,al ret setcolor endp ; ; cls - clear the current window ; cls proc near mov cx,0 mov dh,bot_edge sub dh,top_edge mov dl,rt_edge sub dl,lf_edge mov ax,0600H mov bh,color int 10H ret cls endp ; ; savqcalc - compute parameters for the save/restore window operation ; No input parameters. ; results: cx = number of words to xfer ; bl = number of words to transfer per line ; bh = zero, used in counting words xferred on this line ; dx = amount to add to screen pointer to wrap to next line ; ax = same as cx ; savqcalc proc near mov al,bot_edge ; get number of words to xfer sub al,top_edge ; compute number of lines in al inc al mov bl,rt_edge ; compute number of words per line in bl sub bl,lf_edge inc bl mov dl,80 ; get wraparound increment in dx = 80-bl xor dh,dh sub dl,bl ; compute word increment shl dx,1 ; convert to byte increment mul bl ; multiply ax = al*bl mov cx,ax ; gives word count to xfer, store in cx ; bl still contains # words to xfer per line xor bh,bh ; zero out bh to count up to bl before wrapping ret savqcalc endp ; ; pushwindow - push the current window state onto the window stack ; pushwindow proc near ; first, save the new corners on stack push cx push dx ; now see if there's room in the window queue: ; compute ax = (endrow-strtrow+1)*(endcol-strtcol+1)*2 ; add ax to queue pointer and see if it overflows qlim mov al,dh ; get ending row sub al,ch ; subtract starting row inc al ; add 1 mov bl,dl ; get ending column sub bl,cl ; subtract starting column inc bl ; add 1 mul bl ; multiply the dimensions shl ax,1 ; and mult by 2 to convert to byte count add ax,wqp ; add the byte count to the queue pointer cmp ax,qlim ; compare against end of queue jl pushok ; if less, go ahead pop dx ; no room. get back dx and cx pop cx mov ax,1 ; set error code in ax jmp nosav ; and don't do the save ; save window corners and cursor position pushok: sub wsp,wssize ; adjust the stack pointer down a frame mov bx,wsp mov al,lf_edge mov [bx],al mov al,top_edge mov 1[bx],al mov al,rt_edge mov 2[bx],al mov al,bot_edge mov 3[bx],al mov ah,3 mov bh,0 int 10H ; get current cursor position mov bx,wsp ; get window stack pointer back mov 4[bx],dx ; save current cursor position ; set up the new window pop dx ; get back the new window corners pop cx call setwindow ; set the window corners ; now, save the area occupied by new window ; compute area to be saved mov al,top_edge ; get starting address in CRT segment mov bl,160 mul bl add al,lf_edge ; ax = top_edge*160 * lf_edge*2 adc ah,0 add al,lf_edge adc ah,0 mov si,ax ; store it in the si call savqcalc ; compute size of save area, etc. mov di,wqp ; set di to next byte in window queue push ds ; save ds for switch to screen segment mov ax,crt_seg ; switch ds to screen segment mov ds,ax ;;; assume ds:0B000H cld ; save address of screen save area on stack push di ; so we can get it back later pshmov: movsw ; move a word inc bh ; inc count of words moved cmp bh,bl ; done with this line? jge pshnxt ; yes, go move to next line loop pshmov jmp pshdon pshnxt: add si,dx ; increment si by wraparound increment xor bh,bh ; zero out the words-per-line counter loop pshmov ; and go move next word pshdon: ; here when finished saving mov ax,di ; remember next free mem address pop di ; get back the starting address of save area pop ds ; get back our data segment ;;; assume ds:dseg mov wqp,ax ; now set wqp to next free mem address mov bx,wsp ; get window stack pointer back again mov 6[bx],di ; save screen save area address on stack mov 8[bx],es ; (this word is currently always our ds) mov ax,0 ; set no-error code nosav: ret pushwindow endp ; ; rstwindow - restore most recent window state from stack ; rstwindow proc near ; first make sure there's a window on the stack cmp wsp,offset wsend jl rstok mov ax,1 ; nothing on stack. set error code in ax jmp norst ; and don't restore rstok: ; restore screen contents mov bx,wsp ; get window stack pointer mov al,10[bx] ; delete the frames around the window sub lf_edge,al sub top_edge,al add rt_edge,al add bot_edge,al shl al,1 add wd_width,al add wd_height,al mov byte ptr 10[bx],0 ; now there are no frames around the window push es mov di,crt_seg ; get address of screen segment into es mov es,di ;;; assume es:0B000H ; compute area to be restored mov al,top_edge ; get starting address in CRT segment mov bl,160 mul bl add al,lf_edge ; ax = top_edge*160 + lf_edge*2 adc ah,0 add al,lf_edge adc ah,0 mov di,ax ; store it in the di mov bx,wsp ; get back our window stack pointer mov si,6[bx] ; get address of window save area mov wqp,si ; delete save area while we've got the address call savqcalc cld rstmov: movsw ; move a word inc bh ; inc count of words moved cmp bh,bl ; done with this line? jge rstnxt ; yes, go move to next line loop rstmov jmp rstdon rstnxt: add di,dx ; increment si by wraparound increment xor bh,bh ; zero out the words-per-line counter loop rstmov ; and go move next word rstdon: ; here when finished saving pop es ; get our es back ;;; assume es:dseg ; restore the screen corners and cursor position mov bx,wsp ; get stack frame mov al,[bx] ; restore corners mov lf_edge,al mov al,1[bx] mov top_edge,al mov al,2[bx] mov rt_edge,al mov al,3[bx] mov bot_edge,al mov al,bot_edge ; compute relative window edges sub al,top_edge mov wd_height,al mov al,rt_edge sub al,lf_edge mov wd_width,al mov dx,4[bx] ; set cursor position mov ah,2 mov bh,0 int 10H add wsp,wssize ; delete this stack frame mov ax,0 ; return no-error code norst: ret ; and return rstwindow endp ; ; frame - draw a frame around the current window ; frame proc near push es ; switch es to crt segment mov ax,crt_seg mov es,ax mov al,top_edge ; find starting address of window mov bl,160 ; ax = top_edge*160 + lf_edge*2 mul bl mov bl,lf_edge shl bl,1 add al,bl adc ah,0 mov si,ax ; store it in the si for horizontals mov di,ax ; and in di for drawing verticals mov al,wd_height ; find distance to bottom line of window mov bl,160 ; ax = wd_height*160 mul bl mov bx,ax ; store it in the bx mov byte ptr es:[si],0daH ; put on the top and bottom corners mov byte ptr es:[si+bx],0c0H inc si mov ah,color ; fill in attributes for corners mov byte ptr es:[si],ah mov byte ptr es:[si+bx],ah inc si mov cl,wd_width ; get width of window, minus 2 xor ch,ch dec cx mov al,0c4H ; get horizontal line into ax mov ah,color horiz: ; draw the 2 horizontals mov word ptr es:[si],ax mov word ptr es:[si+bx],ax add si,2 loop horiz mov al,0bfH ; top right corner mov word ptr es:[si],ax mov al,0d9H ; bottom right corner mov word ptr es:[si+bx],ax mov si,di ; get back starting address of window mov bl,wd_width ; get width of window minus 1 into bx xor bh,bh shl bx,1 add si,160 ; move down a line (don't overwrite corners) mov cl,wd_height ; get height of window, minus 2, into cx xor ch,ch dec cx mov al,0B3H ; get vertical line into al mov ah,color ; get attribute into ah vert: ; draw the two verticals mov word ptr es:[si],ax mov word ptr es:[si+bx],ax add si,160 loop vert inc lf_edge ; shrink the window inc top_edge dec rt_edge dec bot_edge sub wd_width,2 sub wd_height,2 mov bx,wsp inc byte ptr 10[bx] ; increment count of frames pop es ret frame endp ; ; wtty - same as function 14 in ROM BIOS, but uses windows. ; wtty proc near ; get CRT mode into ah and crt_mode ; get active page into active_page push es push bx mov bx,40H mov es,bx mov bx,62H ; 62H = offset active page mov ah,es:[bx] mov active_page,ah mov bx,49H ; 49H = offset CRT mode mov ah,es:[bx] mov crt_mode,ah pop bx pop es push ax ; save registers push ax ; save char to write ; read cursor position (inline for speed) push ds ; save registers that must be kept push bx mov ax,40H ; switch to ROM BIOS data segment mov ds,ax mov bl,bh ; get param page number into bx xor bh,bh sal bx,1 ; convert to cursor table offset mov dx,[bx+50H] ; read csr posn from cursor table mov cx,word ptr 60H ; read csr mode from mode word push cs ; switch to our data segment pop ds sub dh,top_edge ; adjust position for window corner sub dl,lf_edge pop bx ; restore bx register pop ds ; switch to caller's data segment ; end of read-cursor-position pop ax ; get back character cmp raw,0 ; raw I/O mode? jne rawio ; yes, no special char xlation cmp al,8 ; BS? je dobsp cmp al,0dH ; CR? je docr cmp al,0aH ; LF? je dolf cmp al,07H ; BEL? je dobel cmp al,09H ; TAB? je dotab rawio: mov bh,active_page ; set active page into bh which we got above mov bl,color ; get color also (attribute byte) mov ah,9 ; write character/attribute BIOS call mov cx,1 ; write only once int 10H inc dl ; advance cursor one position cmp wrap,0 ; wrap-around on? je scrpos ; no, don't wrap cmp dl,wd_width ; at the right edge of the screen? jnz scrpos ; no, don't wrap mov dl,0 ; yes, move it to left edge cmp dh,wd_height ; at bottom of screen? jnz down1 ; no, just move it down a line sclreq: ; have to scroll mov ah,2 ; jmp here to set cursor position also mov bh,0 int 10H mov al,crt_mode ; get the crt mode cmp al,4 jc gcolor cmp al,7 mov bh,color ; fill with proper fg/bg jne scrl1 gcolor: mov bh,color ; get filler fg/bg scrl1: mov ax,601H ; scroll one line mov cx,0 ; get current screen corners mov dh,wd_height mov dl,wd_width xint10: int 10h ; do scroll - here also for gen'l int 10 call wtret1: ; jmp here to return from wtty pop ax ; restore the character we wrote ret down1: ; move cursor down a line inc dh scrpos: ; jmp here to do set-cursor call mov ah,2 jmp xint10 dobsp: ; backspace cmp dl,0 ; do nothing if already at left edge je scrpos dec dl ; move back one position jmp scrpos docr: ; carriage return mov dl,0 jmp scrpos dolf: ; linefeed cmp dh,wd_height jl down1 ; just move down jmp sclreq ; scroll (*** fix- makes redundant set csr call!) dobel: ; bell mov bl,2 call beep jmp wtret1 dotab: ; tab mov ah,3 mov bh,0 ; get cursor position int 10H add dl,8 ; increment it by 8 and dl,0F8H ; mask it down mov ah,2 ; set cursor position int 10H jmp wtret1 ; and return from call wtty endp ; ; beep the speaker for desired interval. This is how IBM does it -- kind ; of a mystery why they didn't put a counter in the clk int routine to turn ; the beeper off to avoid busy-waiting... ; This beeper is set up to sound like the Z19 beeper, which I especially like. ; beep proc near mov al,10110110B ; command to timer - generate square wave out 43H,al mov ax,0700H ; count to produce beep frequency out 42H,al mov al,ah out 42H,al in al,61H ; get orig. value in PPI with spkr enable bit mov ah,al or al,03 ; turn on speaker enable, starting beep out 61H,al mov cx,2000H ; this determines beep duration bpwt: loop bpwt mov al,ah ; turn speaker enable off, stopping beep out 61H,al ret beep endp ; ; everything below this point will be deleted from memory once the initial ; command exits ; qbase equ this word start proc near ; determine what kind of CRT is in use int 11H and ax,0030H cmp ax,0030H jne iscolor mov crt_seg,0B000H jmp setvec iscolor: mov crt_seg,0B800H setvec: ; set up the BIOS vectors mov ax,0 mov es,ax mov ax,word ptr es:40H mov romoff,ax mov ax,word ptr es:42H mov romseg,ax mov ax,offset disp mov word ptr es:40H,ax mov ax,cs mov word ptr es:42H,ax mov dx,offset qbase ; get starting address for window queue add dx,wqsize ; allocate space for window queue mov qlim,dx ; save end of queue to test later int 27H start endp cseg ends end cpstart