Metropoli BBS
VIEWER: waitasec.asm MODE: TEXT (ASCII)
;	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

[ RETURN TO DIRECTORY ]