; WAITASEC.ASM -- Holds and Recalls Scrolling Screens ; ============ ; (C) Copyright Charles Petzold, 1985 ; ; COM file format ; CSEG Segment Assume CS:CSEG Org 0080h SWAP Label Byte ; Use this area for line swapping Org 0100h Entry: Jmp Initialize ; Entry: Jump over resident part ; All Data ; -------- Cols equ 80 ; You may want to set this to 40 ScrollLock equ 70 ; Scan code of Scroll Lock key NumberOfScreens dw 8 ; Can be set up to 15 for 80-column db '(C) Copyright Charles Petzold, 1985' DisplaySegment dw ? ; Display address set by VideoCheck OldInterrupt9 dd ? ; Saved address of real Interrupt 9 OldInterrupt10 dd ? ; Ditto for Interrupt 10h TopHoldAddr dw Offset HoldArea ; Address of top of Hold area BotHoldAddr dw ? ; Address of bottom of Hold area SavePointer dw Offset HoldArea ; Points to where text is saved RecallPointer dw Offset HoldArea ; Points to where text is recalled LockStateOn db 0 ; Flag for lock or no lock ScrollLockDown db 0 ; Current state of Scroll Lock key KeyDispatch dw Home,Up,PgUp ; Dispatch addresss of routines dw 5 Dup (Dummy) ; for cursor movement within dw End,Down,PgDn ; recalled displays ; Intercept of Interrupt 10h -- BIOS Video Call ; -------------------------- NewInterrupt10: Cmp AX,0601h ; Check if this is a 1 line scroll up Jne NoNeedToBother ; No, just continue to real interrupt Cmp CX,0 ; Check if this is upper left corner Jne NoNeedToBother ; No, don't want to touch it Cmp DL,Cols - 1 ; Check if DL is right hand side Jne NoNeedToBother ; Another obstacle that shuts us out Call VideoCheck ; Returns Carry if graphics Jc NoNeedToBother ; So we drop out here if Carry is set Sti ; Allow interrupts Cld ; And set direction to forward Push CX ; Save all the registers we'll use Push SI Push DI Push DS Push ES Push CS ; Push this code segment Pop ES ; So we can set ES to it Mov DI,CS:[SavePointer] ; This is the destination Mov DS,CS:[DisplaySegment] ; This is the source segment Sub SI,SI ; Source is top of display Mov CX,Cols ; The number of characters Call VideoOff ; Turn off light show if color Rep Movsw ; Move 'em in! Call VideoOn ; Turn display back on Cmp DI,CS:[BotHoldAddr] ; Check the new destination Jb NotAtBotYet ; Jump if it hasn't wrapped yet Mov DI,CS:[TopHoldAddr] ; Set it to top if it has NotAtBotYet: Mov CS:[SavePointer],DI ; And save it anyway Pop ES ; Pop the registers we saved Pop DS Pop DI Pop SI Pop CX NoNeedToBother: Jmp CS:[OldInterrupt10] ; And do the interrupt for real ; Intercept of Interrupt 9h -- Hardware Keyboard Interrupt ; -------------------------------------------------------- NewInterrupt9: Sti ; Allow other interrupts Push AX ; Save the register for awhile In AL,60h ; Get the pressed key scan code Cmp AL,ScrollLock ; Check if it's the Scroll Lock Jz GottaScrollLock ; If so, do special routine Cmp AL,128 + ScrollLock ; Check if scroll lock release Jz ScrollLockRlse ; Another special routine Jmp AnyOtherKey ; Otherwise, it's some other key ; Scroll Lock key depressed ; ------------------------- GottaScrollLock:Mov CS:[ScrollLockDown],1 ; Flag Scroll Lock as depressed Cmp CS:[LockStateOn],1 ; See if we're locked now Jz AnyOtherKey ; If so, will just reset Mov AH,2 ; Do keyboard interrupt Int 16h ; to get shift states Test AL,04 ; See if Ctrl has been pressed Jnz NormalProcess ; Process normally if Break Call VideoCheck ; See if video is OK for recall Jc NormalProcess ; If not, ignore the key Mov CS:[LockStateOn],1 ; Lock state now ON Push CS:[SavePointer] ; Transfer value of SavePointer Pop CS:[RecallPointer] ; ... to RecallPointer PushF ; Simulate an interrupt Call CS:[OldInterrupt9] ; Let Scroll Lock Register Call VideoOn ; Video might be off - turn on HoldingPattern: Cmp CS:[LockStateOn],1 ; Wait in this loop ... Jz HoldingPattern ; ... until out of lock state Jmp NormalProcess ; Then process last key ; Scroll Lock key released ; ------------------------ ScrollLockRlse: Mov CS:[ScrollLockDown],0 ; Register Scroll Lock release Cmp CS:[LockStateOn],1 ; See if in lock state Jnz NormalProcess ; If not, just process key Mov AX,CS:[SavePointer] ; Check if the screen is in Cmp AX,CS:[RecallPointer] ; normal position Jnz NormalProcess ; If not, just process key Mov CS:[LockStateOn],0 ; If so, turn lock state OFF Pop AX ; Restore register IRet ; Return to holding pattern ; All other keys ; -------------- AnyOtherKey: Cmp CS:[LockStateOn],1 ; For other keys, just do Jnz NormalProcess ; normal processing if not lock Call NowInLockState ; But call routine if locked Pop AX ; Restore register IRet ; Return to holding pattern NormalProcess: Pop AX ; Get back the register Jmp CS:[OldInterrupt9] ; Let the key process ; Routine for other keys during lock state (AL = Scan Code) ; --------------------------------------------------------- NowInLockState: Cld ; String moves generally forward Push CX ; We use all these registers Push DX Push SI Push DI Push DS Push ES Mov DX,CS ; Get value of CS Mov DS,DX ; Set DS and Mov ES,DX ; ES to this segment Assume DS:CSEG, ES:CSEG ; And tell the assembler Mov AH,AL ; AH is actual scan code And AL,7Fh ; AL has release byte off Sub AL,71 ; Subtract the 'Home' key value Jb WrongKey ; No good if below Cmp AL,(81 - 71) ; Above the 'PgDn' key? Ja WrongKey ; You're not right either Cmp AL,(73 - 71) ; Check if under or = 'PgUp' Jbe AllRightKey ; Ok for this key Cmp AL,(79 - 71) ; Check if over or = 'End' Jae AllRightKey ; You pass too WrongKey: Cmp [ScrollLockDown],1 ; See if scroll lock is down Jz ResetKeyboard ; If so, just ignore the key Call End ; If not, restore the display Mov [LockStateOn],0 ; Turn off the lock state Jmp KeyReturn ; And get out quickly AllRightKey: Test AH,80h ; See if cursor key is release Jnz ResetKeyboard ; If so, ignore the key Cbw ; Convert scan code to word Add AX,AX ; Double it because word access Mov SI,AX ; Set SI to it Call [KeyDispatch + SI] ; And do the routine ResetKeyboard: In AL,61h ; These instructions Mov AH,AL ; reset the keyboard Or AL,80h Out 61h,AL Mov AL,AH Out 61h,AL Cli ; Disable interrupts Mov AL,20h ; to reset the interrupt Out 20h,AL ; controller KeyReturn: Pop ES ; Get back all the pushes Pop DS Pop DI Pop SI Pop DX Pop CX Ret ; Return to previous routine ; Routines to scan up through saved display ; ----------------------------------------- Home: Mov CX,0FFFFh ; 'Home' key -- "infinite" lines up Jmp Short GoingUp ; Do it Up: Mov CX,1 ; 'Up' key -- 1 line up Jmp Short GoingUp ; Go to it PgUp: Mov CX,25 ; 'PgUp' key -- 25 lines (1 screen) up GoingUp: Mov AX,[RecallPointer] ; Points to the text source Cmp AX,[TopHoldAddr] ; Check if it's at the top Ja NotScanUpTop ; If not there, no problem Mov AX,[BotHoldAddr] ; Otherwise must wrap around NotScanUpTop: Sub AX,Cols * 2 ; Go back one line for recall Cmp AX,[SavePointer] ; Check if cycled through yet Je EndGoingUp ; If so, abort this routine Mov [RecallPointer],AX ; Save the new pointer value Push CX ; And save our counter Mov SI,2 * 24 * Cols ; Set source to bottom line Call DisplayToSwap ; Transfer it to SWAP area Mov SI,2 * 24 * Cols - 2 ; End of penultimate line Mov DI,2 * 25 * Cols - 2 ; End of last line Std ; Backwards string transfer Call ScrollDisplay ; Do the screen scroll Cld ; Direction back to forward Mov DI,0 ; Destination is top line Call HoldToDisplay ; Move line to display Call SwapToHold ; And SWAP line to hold Pop CX ; Get back the counter Loop GoingUp ; And do CX times Dummy: ; Should never get here EndGoingUp: Ret ; But this is the end ; Routines to scan down through saved display ; ------------------------------------------- End: Mov CX,0FFFFh ; 'End' key -- infinite lines down Jmp Short GoingDown ; Do it Down: Mov CX,1 ; 'Down' key -- 1 line down Jmp Short GoingDown ; All ready PgDn: Mov CX,25 ; 'PgDn' key -- 25 lines down GoingDown: Mov AX,[RecallPointer] ; Check if all through Cmp AX,[SavePointer] ; by this comparison Je EndGoingDown ; If so, exit this thing Push CX ; Save the counter Mov SI,0 ; Set source to top line Call DisplayToSwap ; Transfer it to SWAP area Mov SI,2 * 80 ; Source is second line Mov DI,0 ; Destination is top line Call ScrollDisplay ; So we can scroll up display Mov DI,2 * 24 * Cols ; Destination is bottom line Call HoldToDisplay ; For the saved text Call SwapToHold ; Move original top line in Mov AX,[RecallPointer] ; Adjust the recall pointer Add AX,Cols * 2 ; By increasing by one line Cmp AX,[BotHoldAddr] ; See if wrap around Jb NotScanDownBot ; If not, skip a little Mov AX,[TopHoldAddr] ; Get the top address NotScanDownBot: Mov [RecallPointer],AX ; And set to new recall Pop CX ; Get back the counter Loop GoingDown ; Do this CX times EndGoingDown: Ret ; Go back to caller ; Display to Swap -- Save top or bottom line of display (SI) in SWAP area ; ----------------------------------------------------------------------- DisplayToSwap: Push DS ; Save data segment Mov DS,[DisplaySegment] ; And set it to display Mov DI,Offset Swap ; This is the destination Mov CX,Cols ; Do one line Call VideoOff ; Turn off color video Rep Movsw ; Do the line move Pop DS ; Get back DS Ret ; And end ; Scroll Video Display -- SI is source, DI destination ; ---------------------------------------------------- ScrollDisplay: Push DS ; Save both these segments Push ES Mov ES,[DisplaySegment] ; Set both to display Mov DS,[DisplaySegment] Mov CX,24 * Cols ; 24 lines must be moved Rep Movsw ; This line does it Pop ES ; Get back the segments Pop DS Ret ; And go back ; Hold To Display -- Move one saved line to video display (DI) ; ------------------------------------------------------------ HoldToDisplay: Push ES ; Now save just ES Mov ES,[DisplaySegment] ; And set it to display Mov SI,[RecallPointer] ; This is the source Mov CX,Cols ; One line only Rep Movsw ; Do the transfer Call VideoOn ; Color display back on Pop ES ; Get back ES Ret ; And return ; Swap To Hold -- Completes the cycle ; ----------------------------------- SwapToHold: Mov SI,Offset Swap ; Source is SWAP Mov DI,[RecallPointer] ; Destination in HoldArea Mov CX,Cols ; One line transfer Rep Movsw ; Do it Ret ; And return ; Video Check -- Returns CY flag is graphics or 40 column or not page 0 ; --------------------------------------------------------------------- Assume DS:Nothing, ES:Nothing VideoCheck: Push AX Push BX Mov CS:[DisplaySegment],0B000h ; Set up for monochrome Mov AH,15 ; Get current video mode Int 10h ; through display interrupt Cmp AL,7 ; See if monochrome Jz VideoAOK ; If so, skip out OK Cmp AL,3 ; Check if it's graphics Ja VideoNoGood ; If so, can't do anything Cmp BH,0 ; Check if it's Page 0 Jnz VideoNoGood ; Nope? No bad Cmp AH,Cols ; See if columns is right Jne VideoNoGood ; No -- dishonorable discharge Mov CS:[DisplaySegment],0B800h ; Change it to color VideoAOK: Clc ; Flag for no error Jmp Short VideoReturn ; Get out VideoNoGood: Stc ; Flag for error VideoReturn: Pop BX ; Get back register Pop AX Ret ; Now go back ; Video Off -- Turns off display for Color / Graphics ; --------------------------------------------------- VideoOff: Cmp CS:[DisplaySegment],0B800h ; Check if color Jnz NoColorTurnOff ; If not, punt Push AX ; Just use two registers Push DX Mov DX,3DAh ; Color / Graphics Status Port RetraceWait1: In AL,DX ; Get Color / Graphics Status Test AL,08 ; Check the vertical retrace Jnz RetraceWait1 ; Loop till it's over RetraceWait2: In AL,DX ; Check status again Test AL,08 ; In particular the retrace Jz RetraceWait2 ; Loop till it comes Mov DX,3D8h ; Video Mode Select Mov AL,25h ; Byte to turn off video Out DX,AL ; Turn it off Pop DX ; Retrieve registers from stack Pop AX NoColorTurnOff: Ret ; Video On -- Turns Color Display back on ; --------------------------------------- VideoOn: Cmp CS:[DisplaySegment],0B800h ; See if color Jnz NoColorTurnOn ; If not do nothing Push AX ; Save registers Push DX Mov DX,3D8h ; Video mode register port Mov AL,28h - (Cols EQ 80) ; i.e., 29h for 80 columns Out DX,AL ; Turn video back on Pop DX ; Get back registers Pop AX NoColorTurnOn: Ret ; And leave ; Initialization Procedure ; ------------------------ Assume DS:CSEG, ES:CSEG HoldArea Label Word ; Storage of screens also Initialize: Mov AX,Cols * 25 * 2 ; Characters per screen Mul [NumberOfScreens] ; AX = bytes for HoldArea Add AX,Offset HoldArea ; Calculate bottom of HoldArea Mov [BotHoldAddr],AX ; Save the bottom address Mov DX,AX ; Last address for terminate Sub AX,AX ; Zero out AX Mov DS,AX ; To set DS to Vector Segment Cli ; No Interrupts now Les SI,DS:[9h * 4] ; Get Int 9 address Mov Word Ptr CS:[OldInterrupt9],SI ; Save offset Mov Word Ptr CS:[OldInterrupt9 + 2],ES ; Save segment Mov Word Ptr DS:[9h * 4],Offset NewInterrupt9 ; New offset Mov Word Ptr DS:[9h * 4 + 2],CS ; New segment Les SI,DS:[10h * 4] ; Get Int 10 address Mov Word Ptr CS:[OldInterrupt10],SI ; Save offset Mov Word Ptr CS:[OldInterrupt10 + 2],ES ; Save segment Mov Word Ptr DS:[10h * 4],Offset NewInterrupt10 ; New offset Mov Word Ptr DS:[10h * 4 + 2],CS ; New segment Sti ; Interrupts back on Int 27h ; Terminate and stay resident CSEG EndS End Entry ; End denotes Entry point