;======================================================================
; POPWATCH 1.00 Copyright (c) 1992, Robert L. Hummel
; PC Magazine Assembly Language Lab Notes
;
; POPWATCH is a memory resident clock, calendar, and stopwatch. It may
; be used interactively by popping up the POPWATCH window using the
; ALT-P keystroke combination. Alternately, it can be invoked from the
; command line or from a batch file to produce date and time stamps
; that can be directed to the active display or to the printer.
;----------------------------------------------------------------------
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
ORG 100H ;COM file format
ENTPT: JMP MAIN
;======================================================================
; General program equates.
;----------------------------------------------------------------------
CR EQU 0DH ;ASCII carriage return
LF EQU 0AH ;ASCII line feed
SPACE EQU 20H ;ASCII blank
ESCAPE EQU 1BH ;ASCII ESC
ENDKEY EQU 4F00H ;END key scan code
JMPFAR EQU 0EAH ;Opcode for jump far
SHIFT_MASK EQU 08H ;Mask to pick out ALT
HOTKEY EQU 19H ;SCAN code for P key
;======================================================================
; Resident data area - this data remains with the resident portion.
;----------------------------------------------------------------------
; Messages.
;----------------------------------------------------------------------
RES_MARKER DB CR,LF,"PopWatch 1.00 Copyright (c) 1992 ",254
DB " Robert L. Hummel",26
MARKER_LEN EQU $-RES_MARKER
;----------------------------------------------------------------------
; Program variables.
;----------------------------------------------------------------------
DOS_BUSY DD 0 ;Address of DOS busy flag
REQUEST DB 0 ;Non-zero if request pending
ACTIVE DB 0 ;Nonzero if POPWATCH is active
;----------------------------------------------------------------------
; Video data.
;----------------------------------------------------------------------
NROW EQU 12 ;Number of rows in the window
NCOL EQU 22 ;Number of cols in the window
BOX_ROW EQU 02 ;Top row of window on screen
BOX_COL EQU 05 ;Left col of window on screen
CUR_POS DW 0 ;Original cursor position
CUR_SIZ DW 0 ;Original cursor shape
VPAGE DB 0 ;Current video page
VATTR DB 0 ;Window color
BW_ATTR EQU 70H ;Monochrome window
CO_ATTR EQU 17H ;Color window
;======================================================================
; INT_9 (ISR)
;
; This routine gets control on each keypress and tests to see if the
; hotkey combination has been typed. If so, it sets a flag indicating
; that POPWATCH should be activated.
;----------------------------------------------------------------------
INT_9 PROC FAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
PUSH AX ;Save used register
;----------------------------------------------------------------------
; Read the key scan code from the controller and compare to the scan
; code for our hotkey.
;----------------------------------------------------------------------
IN AL,60H ;Get key scan code
CMP AL,HOTKEY ;Check if hot-key
JNE I9_1
;----------------------------------------------------------------------
; Determine if the ALT key is also pressed.
;----------------------------------------------------------------------
STI ;Enable interrupts
MOV AH,2 ;Get shift status fn
INT 16H ; thru BIOS
AND AL,0FH ;Test shift keys
CMP AL,SHIFT_MASK ;Must match exactly
JE I9_2
;----------------------------------------------------------------------
; Restore the altered register and jump to the original Int 9 handler.
;----------------------------------------------------------------------
I9_1:
POP AX ;Restore register
CLI ;Interrupts off
DB JMPFAR ;Jump to old interrupt
OLDINT9 DD -1 ; located here
;----------------------------------------------------------------------
; Reset the keyboard and interrupt controllers. This tells the system
; to simply forget the key stroke.
;----------------------------------------------------------------------
I9_2:
IN AL,61H ;These instructions
MOV AH,AL ; reset the keyboard
OR AL,80H ; controller
OUT 61H,AL
MOV AL,AH
JMP SHORT $+2 ;Flush the prefetch
JMP SHORT $+2 ; queue for I/O delay
OUT 61H,AL
CLI ;Disable interrupts
MOV AL,20H ;Reset the controller
OUT 20H,AL
STI ;Enable interrupts
;----------------------------------------------------------------------
; If POPWATCH is already active, ignore the request.
;----------------------------------------------------------------------
CMP BYTE PTR CS:[ACTIVE],0 ;If active, ignore
JNE I9_3
MOV BYTE PTR CS:[REQUEST],-1 ;Set pop up request
I9_3:
POP AX ;Restore register
IRET ;Return from INT
INT_9 ENDP
;======================================================================
; INT_8 (ISR)
;
; This routine gets control each timer tick. If a pop up request is
; pending and DOS is not busy, it pops up the window. Interrupts are
; not enabled until popping up is certain.
;----------------------------------------------------------------------
INT_8 PROC FAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; If no request is pending, simply jump to the original interrupt.
;----------------------------------------------------------------------
CMP BYTE PTR CS:[REQUEST],0 ;0 = no request
JNE I8_2
I8_1:
DB JMPFAR ;Jump to old interrupt
OLDINT8 DD -1 ; located here
;----------------------------------------------------------------------
; If already popped up, ignore the request.
;----------------------------------------------------------------------
I8_2:
CMP BYTE PTR CS:[ACTIVE],0 ;Nonzero is active
JNE I8_1
;----------------------------------------------------------------------
; Determine if DOS is in a busy state by examining the DOS BUSY flag.
;----------------------------------------------------------------------
PUSH BX ;Save used registers
PUSH DS
LDS BX,CS:[DOS_BUSY] ;DS:BX -> DOS BUSY flag
ASSUME DS:NOTHING
CMP BYTE PTR DS:[BX],0 ;Zero is not busy
POP DS ;Restore registers
ASSUME DS:NOTHING
POP BX
JNE I8_1
;----------------------------------------------------------------------
; It's okay for us to pop up. Cancel (acknowledge) the request. Set the
; active flag, then service the old Int 8.
;----------------------------------------------------------------------
MOV BYTE PTR CS:[REQUEST],0 ;Cancel request
MOV BYTE PTR CS:[ACTIVE],-1 ;Nonzero means busy
STI ;Enable interrupts
PUSHF ;Simulate interrupt
CALL DWORD PTR CS:[OLDINT8] ;Perform old Int 8
JMP POPUP ;Go pop up
INT_8 ENDP
;======================================================================
; INT_28 (ISR)
;
; Gets control each time DOS issues its idle interrupt. This proc
; checks to see if a pop up request is pending. Interrupts are not
; enabled until popping up is certain.
;----------------------------------------------------------------------
INT_28 PROC FAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; If no request is pending, simply jump to the original interrupt.
;----------------------------------------------------------------------
CMP BYTE PTR CS:[REQUEST],0 ;0 = no request
JNE I28_2
I28_1:
DB JMPFAR ;Jmp to old interrupt
OLDINT28 DD -1 ; located here
;----------------------------------------------------------------------
; Cancel the request. If POPWATCH is already busy, we'll ignore it.
;----------------------------------------------------------------------
I28_2:
MOV BYTE PTR CS:[REQUEST],0 ;Cancel request
CMP BYTE PTR CS:[ACTIVE],0 ;Non-zero = active
JNE I28_1
;----------------------------------------------------------------------
; Set the active flag to indicate that we're popping up. Before we do,
; however, service the old Int 8.
;----------------------------------------------------------------------
MOV BYTE PTR CS:[ACTIVE],-1 ;Nonzero means busy
STI ;Enable interrupts
PUSHF ;Simulate interrupt
CALL DWORD PTR CS:[OLDINT28] ;Perform old Int 28
JMP SHORT POPUP ;Go pop up
INT_28 ENDP
;======================================================================
; POPUP (Near)
;
; Control is tranferred to this routine to pop up the timer window on
; the screen. Other routines have determined that it is safe to do so.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: None (ISR protocols)
;----------------------------------------------------------------------
L1 DB BOX_COL+2 ,BOX_ROW+1,"DATE",0
L2 DB BOX_COL+2 ,BOX_ROW+2,"TIME",0
L4 DB BOX_COL+2 ,BOX_ROW+4,"ELAPSED",0
L6 DB BOX_COL+2 ,BOX_ROW+6,"PRINTER IS O"
PRN_STAT DB "FF",0
L7 DB BOX_COL+2 ,BOX_ROW+7,"CONSOLE IS O"
CON_STAT DB "N ",0
L9 DB BOX_COL+3 ,BOX_ROW+9 ,"GO STOP RESET",0
L10 DB BOX_COL+3 ,BOX_ROW+10,"PRINTER CONSOLE",0
MSG_TBL DW L1, L2, L4
PC_TBL DW L6, L7, L9, L10
MSG_QTY EQU ($-MSG_TBL)/2
;----------------------------------------------------------------------
POPUP PROC NEAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
PUSH AX ;Save registers
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
PUSH BP
PUSH DS
PUSH ES
MOV AX,CS ;Point DS and ES
MOV DS,AX ; to this segment
ASSUME DS:CSEG
MOV ES,AX
ASSUME ES:CSEG
CLD ;String moves forward
;----------------------------------------------------------------------
; Check that we're in a text video mode. Determine our colors.
;----------------------------------------------------------------------
MOV CL,CO_ATTR ;Assume color window
MOV AH,0FH ;Get current video mode fn
INT 10H ; thru BIOS
CMP AL,3 ;All CGA text modes
JBE P_1
MOV CL,BW_ATTR ;Set mono colors
CMP AL,7 ;Mono text mode?
JE P_1
;----------------------------------------------------------------------
; Return to whoever issued the original Int 8 or Int 28h.
;----------------------------------------------------------------------
P_EXIT:
POP ES ;Restore all registers
ASSUME ES:NOTHING
POP DS
ASSUME DS:NOTHING
POP BP
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
MOV BYTE PTR CS:[ACTIVE],0 ;Turn off active flag
IRET
;----------------------------------------------------------------------
; Save the current video details for later restoration.
;----------------------------------------------------------------------
ASSUME DS:CSEG, ES:CSEG
P_1:
MOV [VATTR],CL ;Save color scheme
MOV [VPAGE],BH ;Save current page
MOV AH,3 ;Get cursor position fn
INT 10H ; thru BIOS
MOV [CUR_SIZ],CX ;Save cursor size
MOV [CUR_POS],DX ;Save cursor position
;----------------------------------------------------------------------
; This BIOS call makes the cursor invisible on most video adapters.
; Some EGAs, however, have trouble with it.
;----------------------------------------------------------------------
MOV AH,1 ;Set cursor shape
MOV CX,2000H ;Invisible on most PCs
INT 10H ; thru BIOS
;----------------------------------------------------------------------
; Save the section of the screen we will be writing over.
;----------------------------------------------------------------------
MOV SI,-1 ;Tell proc to save
MOV DI,OFFSET SCREEN_BUF ;Destination for save
CALL SCREEN ;Save user's screen
;----------------------------------------------------------------------
; Clear the screen and draw the popup window and shadow.
;----------------------------------------------------------------------
CALL DRAW_BOX ;Create frame
;----------------------------------------------------------------------
; Fill in the text labels.
;----------------------------------------------------------------------
MOV BX,OFFSET MSG_TBL ;Message located here
MOV CX,MSG_QTY ;Display this many
CALL WR_STRINGS
;----------------------------------------------------------------------
; Display timer info and act on commands.
;----------------------------------------------------------------------
CALL CLOCK ;Activate clocks
;----------------------------------------------------------------------
; Restore the screen to its original state.
;----------------------------------------------------------------------
MOV SI,OFFSET SCREEN_BUF ;Point to data
CALL SCREEN ;Restore screen
MOV AH,1 ;Set cursor shape
MOV CX,[CUR_SIZ] ;Restore original shape
INT 10H ; thru BIOS
MOV AH,2 ;Set Cursor position
MOV DX,[CUR_POS] ;Restore old cursor
INT 10H ; thru BIOS
JMP P_EXIT
POPUP ENDP
;======================================================================
; SCREEN (Near)
;
; Saves or restores the screen data in our window area based on the
; values passed to it. Uses BIOS functions to read and write screen.
;----------------------------------------------------------------------
; Entry:
; SI = -1, copy from screen to buffer
; ES:DI = destination buffer address
;
; SI != -1, copy from buffer at DS:SI to screen
; Exit:
; None
;----------------------------------------------------------------------
; Changes: AX BX CX DX SI DI
;----------------------------------------------------------------------
SCREEN PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:NOTHING
MOV BH,[VPAGE] ;Active video page
MOV CX,NROW+1 ;Row loop counter
MOV DH,BOX_ROW ;Init row pointer
;----------------------------------------------------------------------
; Loop here for each row.
;----------------------------------------------------------------------
S_1:
PUSH CX ;Save row counter
MOV CX,NCOL+2 ;Set up column loop
MOV DL,BOX_COL ;Column Pointer
;----------------------------------------------------------------------
; Loop here for each column.
;----------------------------------------------------------------------
S_2:
PUSH CX ;Save column counter
MOV AH,2 ;Position cursor fn
INT 10H ; thru BIOS
CMP SI,-1 ;SI =FFFF if SAVE
JE S_3
;----------------------------------------------------------------------
; Restore the screen from the buffer at DS:SI.
;----------------------------------------------------------------------
LODSW ;Get char and attribute
MOV BL,AH ;Put attribute where needed
MOV AH,9 ;Write char fn
MOV CX,1 ;Write one copy of char
INT 10H ; thru BIOS
JMP SHORT S_4
;----------------------------------------------------------------------
; Copy from the screen to the buffer at ES:DI.
;----------------------------------------------------------------------
S_3:
MOV AH,8 ;Get char & attribute fn
INT 10H ; thru BIOS
STOSW ;Write to buffer
;----------------------------------------------------------------------
; Loop for each column and row.
;----------------------------------------------------------------------
S_4:
INC DL ;Next column
POP CX ;Restore column counter
LOOP S_2 ;Close Inner loop
INC DH ;Next row
POP CX ;Restore row counter
LOOP S_1 ;Close Outer loop
RET
SCREEN ENDP
;======================================================================
; DRAW_BOX (Near)
;
; Clears a window (box) on the screen and builds the popup window.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX DX SI
;----------------------------------------------------------------------
BOX_MSG DB BOX_COL+3,BOX_ROW,181,"PopWatch 1.00",198,0
BOX_CHARS DB 201,205,187 ;Top row
DB 186,196,186 ;Middle rows
DB 200,205,188 ;Bottom row
;----------------------------------------------------------------------
DRAW_BOX PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:NOTHING
;----------------------------------------------------------------------
; Clear two overlapping rectangles on the screen to create a shadow
; box effect.
;----------------------------------------------------------------------
MOV AX,0600H ;Scroll window fn
MOV CX,(BOX_ROW+1)*100H+BOX_COL+1
MOV DX,(BOX_ROW+NROW)*100H+BOX_COL+NCOL+1
MOV BH,[VATTR] ;Window color
INT 10H ; thru BIOS
MOV AX,0600H ;Scroll window fn
MOV CX,BOX_ROW*100H+BOX_COL
MOV DX,(BOX_ROW+NROW-1)*100H+BOX_COL+NCOL-1
MOV BH,[VATTR] ;Window color
INT 10H ; thru BIOS
;----------------------------------------------------------------------
; Draw box as one top line, repeated middle lines, and one end line.
;----------------------------------------------------------------------
MOV DH,CH ;Cur row from last call
MOV BH,[VPAGE] ;Active video page
MOV SI,OFFSET BOX_CHARS ;Chars to draw
MOV CX,NROW ;Number of rows to draw
CB_1:
PUSH CX ;Save counter
MOV AH,2 ;Position cursor
MOV DL,BOX_COL ; to this row,column
INT 10H ; thru BIOS
LODSB ;Get leftmost char
MOV AH,0EH ;Write char TTY
INT 10H ; thru BIOS
LODSB ;Get middle char
MOV AH,0AH ;Write repeated char
MOV CX,NCOL-2 ;This many copies
INT 10H ; thru BIOS
MOV AH,2 ;Position cursor
MOV DL,BOX_COL+NCOL-1 ;Col = righthand edge
INT 10H ; thru BIOS
LODSB ;Get rightmost char
MOV AH,0EH ;Write char TTY
INT 10H ; thru BIOS
INC DH ;Next row
POP CX ;Restore counter
;----------------------------------------------------------------------
; The first and last rows are drawn once. For the middle rows, SI is
; backed up and the chars are used again.
;----------------------------------------------------------------------
CMP CL,NROW ;Jump if 1st row
JE CB_2
CMP CL,2 ;Jump if last row
JE CB_2
SUB SI,3 ;Repeat row chars
CB_2:
LOOP CB_1
;----------------------------------------------------------------------
; Display the program's name in the box border.
;----------------------------------------------------------------------
MOV SI,OFFSET BOX_MSG ;Message to write
CALL WR_MESSAGE
RET
DRAW_BOX ENDP
;======================================================================
; WR_MESSAGE (Near)
;
; Write a string to the screen using the BIOS TTY function.
;----------------------------------------------------------------------
; Entry:
; DS:SI = string to write
; String format is:
; DB BIOS screen column
; DB BIOS screen row
; DB text,0
; Exit:
; None
;----------------------------------------------------------------------
; Changes: AX BX DX SI
;----------------------------------------------------------------------
WR_MESSAGE PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
MOV BH,[VPAGE] ;Get active page
LODSW ;Get cursor position
MOV DX,AX ; in DX
MOV AH,2 ;Position cursor fn
WM_1:
INT 10H ; thru BIOS
MOV AH,0EH ;Write TTY
LODSB ;Get character
OR AL,AL ;0 if end of string
JNZ WM_1
RET
WR_MESSAGE ENDP
;======================================================================
; WR_STRINGS (Near)
;
; Copy a list of strings to the console.
;----------------------------------------------------------------------
; Entry:
; DS:BX = offset of table of near string pointers.
; Exit:
; None
;----------------------------------------------------------------------
; Changes: BX CX SI
;----------------------------------------------------------------------
WR_STRINGS PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
WS_1:
PUSH CX ;Save string count
MOV SI,[BX] ;Get pointer
INC BX ;Point to next
INC BX ; pointer
PUSH BX ;Save across call
CALL WR_MESSAGE ;Write to screen
POP BX ;Restore pointer
POP CX ;Restore counter
LOOP WS_1
RET
WR_STRINGS ENDP
;======================================================================
; CLOCK (Near)
;
; Display the stopwatch interface on the screen.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX SI DI
;----------------------------------------------------------------------
CON_FLAG DB -1 ;Console print, on
PRN_FLAG DB 0 ;Printer flag, off
REFRESH DB 0 ;Nonzero when refreshing window
REF_TICKS DD 0 ;32-bit elapsed tick reference
DATE_B DB BOX_COL+12,BOX_ROW+1,"MM/DD/YY",0 ;Date
TIME_B DB BOX_COL+12,BOX_ROW+2,"HH:MM:SS",0 ;Time
ET_B DB BOX_COL+12,BOX_ROW+4,"HH:MM:SS",0 ;Elapsed time
UPD_TBL DW TIME_B, ET_B
UPD_QTY EQU ($-UPD_TBL)/2
;----------------------------------------------------------------------
CLOCK PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:NOTHING
;----------------------------------------------------------------------
; Display the date once. This never changes.
;----------------------------------------------------------------------
MOV SI,OFFSET DATE_B ;Pointer to message
CALL WR_MESSAGE ;Write to CRT
;----------------------------------------------------------------------
; Begin the refresh cycle.
;----------------------------------------------------------------------
CL_1A:
NOT BYTE PTR [REFRESH] ;Non-zero = refresh
CL_1B:
MOV AH,1 ;Check if key ready
INT 16H ; Thru BIOS
JNZ CL_1C
;----------------------------------------------------------------------
; No key is waiting. Call the DO_CMD proc to write the new time and
; elapsed time values to the strings. The strings are then copied to
; the screen.
;----------------------------------------------------------------------
MOV DI,OFFSET TIME_B+2 ;String destination
MOV AL,"t" ;Update time
CALL DO_CMD
MOV DI,OFFSET ET_B+2 ;String destination
MOV AL,"e" ;Update elapsed time
CALL DO_CMD
MOV CX,UPD_QTY ;Number of strings
MOV BX,OFFSET UPD_TBL ;Pointer to pointers
CALL WR_STRINGS ;Write to screen
JMP CL_1B
;----------------------------------------------------------------------
; The BIOS reports a key is ready. End refresh mode, fetch the key, and
; process it.
;----------------------------------------------------------------------
CL_1C:
NOT BYTE PTR [REFRESH] ;0 = Refresh complete
SUB AH,AH ;Get the key
INT 16H ; thru BIOS
;----------------------------------------------------------------------
; Check the keystroke and act on it.
;----------------------------------------------------------------------
CMP AL,ESCAPE ;ESC means exit
JE CL_2A
CMP AX,ENDKEY ;END means exit
JNE CL_2B
CL_2A:
RET
;----------------------------------------------------------------------
; Let DO_CMD determine if this is a valid command.
;----------------------------------------------------------------------
CL_2B:
OR AL,20H ;Make lower case
SUB AH,AH ;Indicate key input
CALL DO_CMD ;Act on the command
JMP CL_1A
CLOCK ENDP
;======================================================================
; DO_CMD (Near)
;
; Interprets commands that are passed to it to turn on and off options
; and display information. This procedure acts somewhat differently
; when running in REFRESH mode.
;----------------------------------------------------------------------
; Entry:
; ES:DI = string destination if refreshing
; Exit:
; None
;
; Note:
; When called from the resident portion, CS=DS=ES=resident segment.
; When called from the currently executing copy CS=DS=seg of current
; copy and ES=seg of resident copy.
;----------------------------------------------------------------------
; Changes: BX CX DX DI
;----------------------------------------------------------------------
STDOUT EQU 1 ;System file handles
STDPRN EQU 4
ET_FLAG DB 0 ;Elapsed timer, defaults to off
TIME_MSG DB "PopWatch -> "
TIME_SLOT DB "XX:XX:XX",CR,LF
TIME_LEN EQU $-TIME_MSG
;----------------------------------------------------------------------
DO_CMD PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
PUSH AX ;Save registers
PUSH SI
;----------------------------------------------------------------------
; The P and C functions can be processed during both popup and command
; line execution.
;----------------------------------------------------------------------
CMP AL,"p" ;Printer status
JE DC_1A
CMP AL,"c" ;Console status
JNE DC_2
;----------------------------------------------------------------------
; C = change the console output status.
;----------------------------------------------------------------------
MOV BX,OFFSET CON_FLAG ;Point BX to flag
MOV DI,OFFSET CON_STAT ;Point DI to string
JMP SHORT DC_1B
;----------------------------------------------------------------------
; P = change the printer output status.
;----------------------------------------------------------------------
DC_1A:
MOV BX,OFFSET PRN_FLAG ;Point BX to flag
MOV DI,OFFSET PRN_STAT ;Point DI to string
;----------------------------------------------------------------------
; If a + or - was present, set the status to ON or OFF, respectively.
; If not, just toggle the current state.
;----------------------------------------------------------------------
DC_1B:
MOV AL,BYTE PTR ES:[BX] ;Get current flag
NOT AL ;Toggle it
OR AH,AH ;0 = toggle request
JZ DC_1C
SUB AL,AL ;Turn flag off
CMP AH,"-" ;Was that the request?
JE DC_1C
NOT AL ;Turn flag on
CMP AH,"-" ;Was that the request?
JNE DC_EXIT
DC_1C:
MOV BYTE PTR ES:[BX],AL ;Save new status
OR AL,AL ;Check final status
MOV AX,"FF" ;Change string to OFF
JE DC_1D
MOV AX," N" ; or to ON
DC_1D:
STOSW ;Write word to ES:DI
CMP BYTE PTR ES:[ACTIVE],0 ;If command line mode
JE DC_EXIT ; return
;----------------------------------------------------------------------
; Update the window in popup mode.
;----------------------------------------------------------------------
MOV BX,OFFSET PC_TBL ;Table of pointers
MOV CX,2 ;P and C strings only
CALL WR_STRINGS ;Write to screen
JMP SHORT DC_EXIT
;----------------------------------------------------------------------
; Process the options related to the elapsed timer.
;----------------------------------------------------------------------
DC_2:
MOV BX,OFFSET REF_TICKS ;Point to reference
;----------------------------------------------------------------------
; R = reset the elapsed timer to 0 and start it.
;----------------------------------------------------------------------
CMP AL,"r" ;Reset timer?
JNE DC_3
MOV AH,-1 ;Turn timer on
CALL GET_TICKS ;Get ticks in CX:DX
JMP SHORT DC_4C
;----------------------------------------------------------------------
; G = start the elapsed timer, retaining cumulative value.
;----------------------------------------------------------------------
DC_3:
MOV AH,ES:[ET_FLAG] ;Get timer status in AH
CMP AL,"g" ;G = turn timer on
JNE DC_4A
OR AH,AH ;Ignore if already on
JNZ DC_EXIT
NOT AH ;AH = FF
JMP SHORT DC_4B
;----------------------------------------------------------------------
; S = stop the timer at its current value.
;----------------------------------------------------------------------
DC_4A:
CMP AL,"s" ;Turn timer off?
JNE DC_5A
OR AH,AH ;Ignore if already off
JZ DC_EXIT
SUB AH,AH ;Turn timer off
;----------------------------------------------------------------------
; Get the current ticks. Calculate the difference between the reference
; and the current and store the result back into the reference.
;----------------------------------------------------------------------
DC_4B:
CALL GET_TICKS ;Get ticks in CX:DX
SUB DX,WORD PTR ES:[BX][0] ;Low word
SBB CX,WORD PTR ES:[BX][2] ;High word
DC_4C:
MOV WORD PTR ES:[BX][0],DX ;Save low word
MOV WORD PTR ES:[BX][2],CX ; and high word
DC_4D:
MOV ES:[ET_FLAG],AH ;Update timer status
;----------------------------------------------------------------------
; Exit the routine.
;----------------------------------------------------------------------
DC_EXIT:
POP SI ;Restore registers
POP AX
RET ;Return from proc
;----------------------------------------------------------------------
; The D command can only be performed in command line mode.
; It is ignored when popped up and never called during refresh.
;----------------------------------------------------------------------
DC_5A:
CMP AL,"d" ;Report date
JNE DC_6A
CMP BYTE PTR ES:[ACTIVE],0 ;Must be inactive
JNE DC_EXIT
;----------------------------------------------------------------------
; D = display the current date.
;----------------------------------------------------------------------
MOV SI,OFFSET DATE_B+2 ;ES:SI = source
MOV DI,OFFSET TIME_SLOT ;DS:DI = destination
MOV CX,8 ;String length
DC_5B:
MOV AL,ES:[SI] ;Get date digit
MOV DS:[DI],AL ;Put in local string
INC DI ;Advance pointers
INC SI
LOOP DC_5B
JMP SHORT DC_8B
;----------------------------------------------------------------------
; The T and E commands can be performed in command line mode or during
; a resident refresh, but not from the keyboard while popped up.
;----------------------------------------------------------------------
DC_6A:
CMP BYTE PTR ES:[ACTIVE],0 ;Must be command line
JE DC_6B
CMP BYTE PTR ES:[REFRESH],0 ; or refreshing
JE DC_EXIT
;----------------------------------------------------------------------
; E = report or refresh elapsed time.
;----------------------------------------------------------------------
DC_6B:
CMP AL,"e" ;Report elapsed time
JNE DC_7A
;----------------------------------------------------------------------
; If the elapsed timer is off, simply convert the stored number.
;----------------------------------------------------------------------
CMP BYTE PTR ES:[ET_FLAG],0 ;Check timer status
JNE DC_6C
MOV DX,WORD PTR ES:[REF_TICKS][0] ;Low word
MOV CX,WORD PTR ES:[REF_TICKS][2] ;High word
JMP SHORT DC_7B
;----------------------------------------------------------------------
; If the timer is on, get the difference between the current time and
; the reference time.
;----------------------------------------------------------------------
DC_6C:
CALL GET_TICKS ;Get ticks in CX:DX
SUB DX,WORD PTR ES:[REF_TICKS][0] ;Low word
SBB CX,WORD PTR ES:[REF_TICKS][2] ;High word
JMP SHORT DC_7B
;----------------------------------------------------------------------
; T = report/refresh current time.
;----------------------------------------------------------------------
DC_7A:
CMP AL,"t" ;Report current time
JNE DC_EXIT
CALL GET_TICKS ;Get ticks in CX:DX
DC_7B:
CALL TICK2HMS ;Convert CX:DX to time
;----------------------------------------------------------------------
; If we're in refresh mode, put the string in the desired location.
;----------------------------------------------------------------------
CMP ES:[REFRESH],0 ;Check refresh status
JE DC_8A
MOV CX,2 ;Want HH:MM:SS
CALL ASC_TIME ;Perform the conversion
JMP DC_EXIT
;----------------------------------------------------------------------
; For command line mode, format the info into a displayable message.
;----------------------------------------------------------------------
DC_8A:
PUSH ES ;Save resident segment
MOV DI,OFFSET TIME_SLOT ;Point ES:DI to string
PUSH DS ;Assume nothing so
POP ES ; always consistent
ASSUME ES:NOTHING
MOV CX,2 ;Want HH:MM:SS
CALL ASC_TIME ;Perform the conversion
POP ES ;Restore segment
ASSUME ES:NOTHING
;----------------------------------------------------------------------
; In command line mode, the display may be going to the console...
;----------------------------------------------------------------------
DC_8B:
CMP BYTE PTR ES:[CON_FLAG],0 ;0 = don't write
JE DC_8C
MOV AH,40H ;Write to handle
MOV BX,STDOUT ;Console
MOV CX,TIME_LEN ;This many bytes
MOV DX,OFFSET TIME_MSG ;From this location
INT 21H ; thru DOS
;----------------------------------------------------------------------
; ...and it may go to the printer.
;----------------------------------------------------------------------
DC_8C:
CMP BYTE PTR ES:[PRN_FLAG],0 ;0 = don't write
JE DC_8D
MOV AH,40H ;Write to handle
MOV BX,STDPRN ;Printer
MOV CX,TIME_LEN ;This many bytes
MOV DX,OFFSET TIME_MSG ;From this location
INT 21H ; thru DOS
DC_8D:
JMP DC_EXIT
DO_CMD ENDP
;======================================================================
; GET_TICKS (Near)
;
; This routine performs the same function as the BIOS Get Tick Count
; (Int 1Ah, AH=0), but takes about half the time.
;----------------------------------------------------------------------
; Entry:
; None
; Exit:
; CX:DX = 32-bit timer tick clock count
;----------------------------------------------------------------------
; Changes: CX DX
;----------------------------------------------------------------------
TIMER_ADR DD 0000046CH ;Address of clock count
;----------------------------------------------------------------------
GET_TICKS PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
PUSH BX ;Preserve registers
PUSH DS
LDS BX,[TIMER_ADR] ;Point to tick count
ASSUME DS:NOTHING
LDS DX,[BX] ;Get count in DS:DX
ASSUME DS:NOTHING
MOV CX,DS ;Put high count in CX
POP DS ;Restore registers
ASSUME DS:CSEG
POP BX
RET
GET_TICKS ENDP
;======================================================================
; TICK2HMS (Near)
;
; Convert BIOS timer ticks to HH:MM:SS.ss. The math in this routine
; was "borrowed" from the internal DOS routines that convert timer
; ticks to time representation.
;----------------------------------------------------------------------
; Entry:
; CX:DX = 32-bit timer tick count
; Exit:
; AH = hours
; AL = minutes
; BH = seconds
; BL = hundredths of seconds
;----------------------------------------------------------------------
; Changes: AX BX
;----------------------------------------------------------------------
TICK2HMS PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
PUSH CX ;Save used registers
PUSH DX
MOV AX,CX
MOV BX,DX
SHL DX,1
RCL CX,1
SHL DX,1
RCL CX,1
ADD DX,BX
ADC AX,CX
XCHG AX,DX
MOV CX,0E90BH
DIV CX
MOV BX,AX
XOR AX,AX
DIV CX
MOV DX,BX
MOV CX,00C8H
DIV CX
CMP DL,64H
JB TICK1
SUB DL,64H
TICK1:
CMC
MOV BL,DL
RCL AX,1
MOV DL,00
RCL DX,1
MOV CX,003CH
DIV CX
MOV BH,DL
DIV CL
XCHG AL,AH
ADD BL,5 ;Correct for round off
POP DX ;Restore registers
POP CX
RET
TICK2HMS ENDP
;======================================================================
; ASC_TIME (Near)
;
; Converts HH:MM:SS.ss time in AX:BX registers to an ASCII string in
; HH:MM:SS.ss format. The precision of the conversion can be specified.
;----------------------------------------------------------------------
; Entry:
; AH = hours
; AL = minutes
; BH = seconds
; BL = hundredths
; ES:DI = destination for ASCII string
; CX = parsing
; 0 - HH
; 1 - HH:MM
; 2 - HH:MM:SS
; 3 - HH:MM:SS.ss
; Exit:
; None
;----------------------------------------------------------------------
; Changes: CX DI
;----------------------------------------------------------------------
ASC_TIME PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
PUSH DX ;Preserve register
;----------------------------------------------------------------------
; Convert the time numbers to ASCII and store in the string at ES:DI.
;----------------------------------------------------------------------
MOV DL,AL ;Save MM
MOV DH,":" ;Save colon char
MOV AL,AH ;Get hours
CALL STO_ASC ;Convert it
JCXZ AT_EXIT
DEC CX ;Change parse flag
MOV AL,DH ;Write a colon
STOSB ; to ES:DI
MOV AL,DL ;Get minutes
CALL STO_ASC ;Convert it
JCXZ AT_EXIT
DEC CX ;Change parse flag
MOV AL,DH ;Write a colon
STOSB ; to ES:DI
MOV AL,BH ;Get seconds
CALL STO_ASC ;Convert it
JCXZ AT_EXIT
MOV AL,"." ;Write a decimal point
STOSB ; to ES:DI
MOV AL,BL ;Get hundredths
CALL STO_ASC ;Convert it
AT_EXIT:
POP DX ;Restore register
RET
ASC_TIME ENDP
;======================================================================
; STO_ASC (Near)
;
; Convert a number in AL to two denary ASCII characters and write to
; ES:DI. The number must fall within the range 0-99.
;----------------------------------------------------------------------
; Entry:
; AL = number to convert. 0-99 only.
; Exit:
; None
;----------------------------------------------------------------------
; Changes: AX DI
;----------------------------------------------------------------------
STO_ASC PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
AAM ;Split denary digits
OR AX,"00" ;Convert to ASCII
XCHG AL,AH ;Reverse digits
STOSW ;Write them
RET
STO_ASC ENDP
;======================================================================
; Data here is allocated after the program loads into memory to save
; space in the COM file. The PC assembler variable is used to keep
; track of relative addresses.
;----------------------------------------------------------------------
PC = $ ;Set imaginary counter
SCREEN_BUF = PC ;DB NROW*NCOL*2 DUP(?)
PC = PC+(NROW+1)*(NCOL+2)*2
LAST_BYTE = PC
;======================================================================
; MAIN (Near)
;
; Execution begins here when POPWATCH is run from the command line.
;----------------------------------------------------------------------
; Transient data.
;----------------------------------------------------------------------
RESIDENT DB -1 ;Cleared to 0 if not resident
UMB_LINK DB -1 ;Holds UMB link status
;----------------------------------------------------------------------
; Messages.
;----------------------------------------------------------------------
COPYRIGHT$ DB CR,LF,"PopWatch 1.00 Copyright (c) 1992 ",254
DB " Robert L. Hummel",CR,LF,"PC Magazine "
DB "Assembly Language Lab Notes",LF,CR,LF,"$"
RESIDENT$ DB LF,"PopWatch 1.00 Already Resident",CR,LF,LF
USAGE$ DB "Interactive : Pop up with ALT-P",CR,LF
DB "Command Line: PopWatch [D][T][E][G][S][R]"
DB "[C[+|-]][P[+|-]]",CR,LF,LF
DB "where",CR,LF
DB " D = display date",CR,LF
DB " T = display time",CR,LF
DB " E = display elapsed time",CR,LF
DB " G = start elapsed timer",CR,LF
DB " S = stop elapsed timer",CR,LF
DB " R = reset, then start elapsed timer",CR,LF
DB " C = toggle console output",CR,LF
DB " P = toggle printer output",CR,LF,LF
DB " + = force output on",CR,LF
DB " - = force output off",CR,LF,"$"
;----------------------------------------------------------------------
MAIN PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
CLD ;String moves forward
;----------------------------------------------------------------------
; Determine if a copy of POPWATCH is already resident in memory by
; searching for a duplicate of the copyright notice.
; Modify marker string to avoid false matches when searching memory.
;----------------------------------------------------------------------
NOT WORD PTR [RES_MARKER] ;Modify marker
;----------------------------------------------------------------------
; Get the DOS version. If ver 5 or later, save the current UMB state,
; then link them to search all memory.
;----------------------------------------------------------------------
MOV AH,30H ;Get DOS version in AX
INT 21H ; thru DOS
CMP AL,5 ;Dos 5 or later
JB M_1
MOV AX,5802H ;Get current UMB link
INT 21H ; thru DOS
JC M_1
MOV [UMB_LINK],AL ;Save it
MOV AX,5803H ;Set UMB to
MOV BX,1 ; linked in chain
INT 21H ; thru DOS
M_1:
;----------------------------------------------------------------------
; Get the segment address of the first MCB in BX.
;----------------------------------------------------------------------
MOV AH,52H ;Get ES:BX -> IVARS
INT 21H ; thru DOS
ASSUME ES:NOTHING
MOV BX,ES:[BX-2] ;Get first MCB
;----------------------------------------------------------------------
; Point ES to the segment in BX and look for the modified copyright.
;----------------------------------------------------------------------
MOV AX,DS ;Current seg in AX
M_2A:
MOV ES,BX ;Point ES to MCB
ASSUME ES:NOTHING
INC BX ;Point to block
MOV SI,OFFSET RES_MARKER ;Compare DS:SI
LEA DI,[SI+10H] ; to ES:DI
MOV CX,MARKER_LEN ;Compare full string
REPE CMPSB ;CMP DS:SI TO ES:DI
JNE M_2B
CMP AX,BX ;If not this copy, done
JNE M_2C
M_2B:
;----------------------------------------------------------------------
; No match. Move to the next memory block.
;----------------------------------------------------------------------
ADD BX,ES:[3] ;Add block length
CMP BYTE PTR ES:[0],"Z" ;This block the last?
JNE M_2A
;----------------------------------------------------------------------
; If we get here, no resident copy was found in all of memory. Point ES
; to this copy, but assume NOTHING to consistently address resident
; copy.
;----------------------------------------------------------------------
MOV BYTE PTR [RESIDENT],0 ;Say none resident
MOV BX,AX ;Set ES to this seg
M_2C:
MOV ES,BX ;Set to memory block
ASSUME ES:NOTHING
;----------------------------------------------------------------------
; Restore the UMB link to its previous state.
;----------------------------------------------------------------------
MOV BL,CS:[UMB_LINK] ;Original link state
CMP BL,-1 ;Was it recorded?
JE M_2D
SUB BH,BH ;Link in BX
MOV AX,5803 ;Set UMB link
INT 21H ; thru DOS
M_2D:
;----------------------------------------------------------------------
; Update the date buffer. The DOS Get Date function returns with
; CX=YEAR, DH=MONTH, DL=DAY.
;----------------------------------------------------------------------
MOV AH,2AH ;Get date
INT 21H ; thru DOS
SUB CX,1900 ;Want last 2 digits
MOV DI,OFFSET DATE_B+2 ;Destination
MOV AL,DH
CALL STO_ASC ;Write month
INC DI
MOV AL,DL
CALL STO_ASC ;Write day
INC DI
MOV AL,CL
CALL STO_ASC ;Write year
;----------------------------------------------------------------------
; Read the command line and process any parameters in the order in
; which they appear.
;----------------------------------------------------------------------
MOV SI,81H ;Command line address
M_3A:
LODSB ;Get character
CMP AL,SPACE ;Skip blanks
JE M_3A
CMP AL,0DH ;CR means end
JE M_3D
MOV AH,AL ;Save switch in AH
LODSB ;Get next char
CMP AL,"+" ;+ means turn on
JE M_3B
CMP AL,"-" ;- means turn off
JE M_3B
DEC SI ;Backup character
SUB AL,AL ;Clear AL to 0
M_3B:
XCHG AH,AL ;Put switch in AL
OR AL,20H ;Make it lower case
CALL DO_CMD ;Act on the command
JMP M_3A
M_3D:
;----------------------------------------------------------------------
; If there was no resident copy, go resident.
;----------------------------------------------------------------------
CMP BYTE PTR [RESIDENT],0 ;0 = not resident
JE M_5
;----------------------------------------------------------------------
; If no command line parameters were given, print the usage message.
; If command line parameters were present, simply terminate.
;----------------------------------------------------------------------
MOV DX,OFFSET RESIDENT$ ;Just print usage
CMP BYTE PTR DS:[80H],0 ;0 if no parameters
JNE M_4
MOV AH,9 ;Display string fn
INT 21H ; thru DOS
M_4:
MOV AH,4CH ;Terminate
INT 21H ; thru DOS
;----------------------------------------------------------------------
; Get a pointer to the DOS Critical Flag, a one-byte location in low
; memory that is set when DOS is in an uninterruptable state. Location
; is returned in ES:BX.
;----------------------------------------------------------------------
M_5:
MOV AH,34H ;Get DOS BUSY flag ptr
INT 21H ; thru DOS
ASSUME ES:NOTHING
MOV WORD PTR DOS_BUSY[0],BX ;offset
MOV WORD PTR DOS_BUSY[2],ES ;segment
;----------------------------------------------------------------------
; Hook Int 9 for the hot-key detection routine.
; Hook Int 8 and Int 28h to pop up.
;----------------------------------------------------------------------
PUSH DS ;Reset ES to point to same
POP ES ; segment as DS
ASSUME ES:CSEG
MOV AL,8 ;Interrupt number
MOV DI,OFFSET OLDINT8 ;Store vector here
MOV DX,OFFSET INT_8 ;New interrupt procedure
CALL SET_INT ;Make change
ASSUME ES:NOTHING
MOV AL,9 ;Interrupt number
MOV DI,OFFSET OLDINT9 ;Store vector here
MOV DX,OFFSET INT_9 ;New interrupt procedure
CALL SET_INT ;Make change
ASSUME ES:NOTHING
MOV AL,28H ;Interrupt number
MOV DI,OFFSET OLDINT28 ;Store vector here
MOV DX,OFFSET INT_28 ;New interrupt procedure
CALL SET_INT ;Make change
ASSUME ES:NOTHING
;----------------------------------------------------------------------
; Deallocate the copy of the environment loaded with the program.
;----------------------------------------------------------------------
MOV AX,WORD PTR DS:[2CH] ;Address of environment
MOV ES,AX ;In ES register
ASSUME ES:NOTHING
MOV AH,49H ;Release allocated memory
INT 21H ; thru DOS
;----------------------------------------------------------------------
; Display copyright notice and usage message.
;----------------------------------------------------------------------
MOV AH,9 ;Display string
MOV DX,OFFSET COPYRIGHT$ ;Copyright notice
INT 21H ; thru DOS
MOV AH,9 ;Display string
MOV DX,OFFSET USAGE$ ;Show usage
INT 21H ; thru DOS
MOV DX,(OFFSET LAST_BYTE - CSEG + 15) SHR 4
MOV AH,31H ;Keep (TSR)
INT 21H ; thru DOS
MAIN ENDP
;======================================================================
; SET_INT (Near)
;
; Get, save, and set an interrupt vector.
;----------------------------------------------------------------------
; Entry:
; AL = vector number.
; ES:DI = destination for old address.
; DS:DX = new interrupt address.
; Exit:
; None
;----------------------------------------------------------------------
; Changes: AX BX ES
;----------------------------------------------------------------------
SET_INT PROC NEAR
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
PUSH AX ;Save vector # in AL
MOV AH,35H ;Get Int vector
INT 21H ; thru DOS
ASSUME ES:NOTHING
MOV [DI+0],BX ;Save address in ES:DI
MOV [DI+2],ES
POP AX ;Get AL back
MOV AH,25H ;Set Int vector
INT 21H ; thru DOS
RET
SET_INT ENDP
CSEG ENDS
END ENTPT