cseg	segment para public 'code'
org	100h
alarm	proc far

; Memory-resident program to intercept the timer interrupt and display the
; system time in the upper right-hand corner of the display.
; This program is run as 'ALARM hh:mm x', where hh:mm is the alarm time and
; x is '-' to turn the display off. Any other value of x or no value will
; turn the clock on

intaddr equ 1ch*4		; interrupt address
segaddr equ 62h*4		; segment address of first copy
mfactor equ 17478		; minute conversion factor * 16
whozat	equ 1234h		; signature
color	equ 14h 		; color attribute

	assume cs:cseg,ds:cseg,ss:nothing,es:nothing
	jmp p150		; start-up code

jumpval dd 0			; address of prior interrupt
signature dw whozat		; program signature
state	db 0			; '-' = off, all else = on
wait	dw 18			; wait time - 1 second or 18 ticks
hour	dw 0			; hour of the day
atime	dw 0ffffh		; minutes past midnite for alarm
acount	dw 0			; alarm beep counter - number of seconds (5)
atone	db 5			; alarm tone - may be from 1 to 255 - the
				; higher the number, the lower the frequency
aleng	dw 8080h		; alarm length (loop count) may be from 1-FFFF

dhours	dw 0			; display hours
	db ':'
dmins	dw 0			; display minutes
	db ':'
dsecs	dw 0			; display seconds
	db '-'
ampm	db 0			; 'A' or 'P' for am or pm
	db 'm'

tstack	db 16 dup('stack   ')   ; temporary stack
estack	db 0			; end of stack
holdsp	dw 0			; original sp
holdss	dw 0			; original ss

p000:				; interrupt code
	push ax 		; save registers
	push ds
	pushf

	push cs
	pop ds			; make ds=cs
	mov ax,wait		; check wait time
	dec ax			; zero?
	jz p010 		; yes - 1 second has elapsed
	mov wait,ax		; not this time
	jmp p080		; return

p010:	cli			; disable interrupts
	mov ax,ss		; save stack
	mov holdss,ax
	mov holdsp,sp
	mov ax,ds
	mov ss,ax		; point to internal stack
	mov sp,offset estack
	sti			; allow interrupts

	push bx 		; save other registers
	push cx
	push dx
	push es
	push si
	push di
	push bp

	mov ax,18		; reset wait time
	mov wait,ax

	mov al,state		; are we disabled?
	cmp al,'-'
	jnz p015		; no
	jmp p070

p015:	mov ah,0		; read time
	int 1ah 		; get time of day
	mov ax,dx		; low part
	mov dx,cx		; high part
	mov cl,4
	shl dx,cl		; multiply by 16
	mov bx,ax
	mov cl,12
	shr bx,cl		; isolate top 4 bits of ax
	add dx,bx		; now in upper
	mov cl,4
	shl ax,cl		; multiply by 16
	mov bx,mfactor		; compute minutes
	div bx			; minutes in ax, remainder in dx
	cmp ax,atime		; time to sound the alarm?
	jnz p020		; no
	call p100		; yes - beep the speaker twice
	push ax
	mov ax,acount		; get beep count
	dec ax			; down by 1
	mov acount,ax		; save beep count
	cmp ax,0		; is it zero?
	jnz p018		; no - keep alarm on
	mov ax,0ffffh		; turn off alarm
	mov atime,ax
p018:	pop ax

p020:	mov dsecs,dx		; save remainder
	mov bx,60		; compute hours
	xor dx,dx		; zero it
	div bx			; hours in ax, minutes in dx
	mov dmins,dx		; save minutes

	cmp ax,0		; midnight?
	jnz p030		; no
	mov ax,12		; yes
	jmp p040a		; set am

p030:	cmp ax,12		; before noon?
	jb p040a		; yes - set am
	jz p040p		; noon - set pm
	sub ax,12		; convert the rest
p040p:	mov bl,'p'
	jmp p040x

p040a:	mov bl,'a'

p040x:	mov ampm,bl
	aam			; fix up hour
	cmp ax,hour		; top of the hour?
	jz p060 		; no

	mov hour,ax
	call p120		; beep the speaker once

p060:	add ax,3030h		; convert hours to ascii
	xchg ah,al
	mov dhours,ax

	mov ax,dmins		; get minutes
	aam
	add ax,3030h		; convert to ascii
	xchg ah,al
	mov dmins,ax

	mov ax,dsecs		; get seconds (remainder)
	xor dx,dx
	mov bx,60
	mul bx
	mov bx,mfactor
	div bx			; seconds in ax
	aam
	add ax,3030h
	xchg ah,al
	mov dsecs,ax

	xor ax,ax		; check monitor type
	mov es,ax
	mov ax,es:[410h]	; get config byte
	and al,30h		; isolate monitor type
	cmp al,30h		; color?
	mov ax,0b000h		; assume mono
	jz p061 		; its mono

	mov ax,0b800h		; color screen address

p061:	mov dx,es:[463h]	; point to 6845 base port
	add dx,6		; point to status port

	mov es,ax		; point to monitor
	mov bh,color		; color in bh
	mov si,offset dhours	; point to time
	mov di,138		; row 1, col 69
	cld
	mov cx,11		; loop count

p062:	mov bl,[si]		; get next character

p063:	in al,dx		; get crt status
	test al,1		; is it low?
	jnz p063		; no - wait
	cli			; no interrupts

p064:	in al,dx		; get crt status
	test al,1		; is it high?
	jz p064 		; no - wait

	mov ax,bx		; move color & character
	stosw			; move color & character again
	sti			; interrupts back on
	inc si			; point to next character
	loop p062		; done?

p070:	pop bp			; restore registers
	pop di
	pop si
	pop es
	pop dx
	pop cx
	pop bx
	cli			; no interrupts
	mov ax,holdss
	mov ss,ax
	mov sp,holdsp
	sti			; allow interrupts

p080:	popf
	pop ds
	pop ax
	jmp cs:[jumpval]

p100	proc near		; beep the speaker twice
	call p120
	push cx
	mov cx,20000
p105:	loop p105		; wait around
	pop cx
	call p120
	push cx
	mov cx,20000
p106:	loop p106		; wait around
	pop cx
	call p120
	ret
p100	endp

p120	proc near		; beep the speaker once
	push ax
	push cx
	mov al,182
	out 43h,al		; setup for sound
	mov al,0
	out 42h,al		; low part
	mov al,atone		; get alarm tone
	out 42h,al		; high part
	in al,61h
	push ax 		; save port value
	or al,3
	out 61h,al		; turn speaker on
	mov cx,aleng		; get loop count
p125:	loop p125		; wait around
	pop ax			; restore original port value
	out 61h,al		; turn speaker off
	pop cx
	pop ax
	ret
p120	endp

p150:				; start of transient code
	mov dx,offset copyr
	call p220		; print copyright
	mov ax,0
	mov es,ax		; segment 0
	mov di,segaddr+2	; this program's prior location
	mov ax,es:[di]		; get prior code segment
	mov es,ax		; point to prior program segment
	mov di,offset signature
	mov cx,es:[di]		; is it this program?
	cmp cx,whozat
	jnz p160		; no - install it
	call p200		; set state & alarm
	int 20h 		; terminate

p160:	mov di,segaddr+2	; point to int 62h
	mov ax,0
	mov es,ax		; segment 0
	mov ax,ds		; get current ds
	mov es:[di],ax		; set int 62h
	mov si,offset jumpval
	mov di,intaddr		; point to timer interrupt
	mov bx,es:[di]		; get timer ip
	mov ax,es:[di+2]	; and cs
	mov [si],bx		; save prior ip
	mov [si+2],ax		; and cs
	mov bx,offset p000
	mov ax,ds
	cli			; clear interrupts
	mov es:[di],bx		; set new timer interrupt
	mov es:[di+2],ax
	sti			; set interrupts
	push ds
	pop es
	call p200		; set state & alarm
	mov dx,offset p150	; last byte of resident portion
	inc dx
	int 27h 		; terminate

p200	proc near		; set state & alarm
	mov si,80h		; point to command line
	mov ax,0
	mov di,0ffffh		; init hours
	mov bh,0
	mov ch,0
	mov dh,0		; : counter
	mov es:[state],bh	; turn clock on
	mov cl,[si]		; get length
	jcxz p210		; it's zero

p203:	inc si			; point to next char
	mov bl,[si]		; get it
	cmp bl,'-'              ; is it a minus?
	jnz p204		; no
	mov es:[state],bl	; turn clock off
	push dx
	mov dx,offset msg3	; print msg
	call p220
	pop dx
	jmp p206

p204:	cmp dh,2		; seen 2nd colon?
	jz p206 		; yes - ignore seconds
	cmp bl,':'              ; colon?
	jnz p205		; no
	inc dh
	cmp dh,2		; second colon?
	jz p206 		; yes - ignore seconds
	push cx
	push dx
	mov cx,60
	mul cx			; multiply current ax by 60
	pop dx
	pop cx
	mov di,ax		; save hours
	mov ax,0
	jmp p206
p205:	cmp bl,'0'
	jb p206 		; too low
	cmp bl,'9'
	ja p206 		; too high - can be a problem
	sub bl,'0'              ; convert it to binary
	push cx
	push dx
	mov cx,10
	mul cx			; multiply current value by 10
	add ax,bx		; and add latest digit
	pop dx
	pop cx
p206:	loop p203		; done yet?
	cmp di,0ffffh		; any time to set?
	jz p210 		; no
	add ax,di		; add hours
	cmp ax,24*60
	jb p209 		; ok
	mov dx,offset msg1	; print error message
	call p220
	jmp p210

p209:	mov es:[atime],ax	; save minutes past midnight
	mov ax,5
	mov es:[acount],ax	; set alarm count
	mov dx,offset msg2	; print set msg
	call p220
p210:	ret
p200	endp

p220	proc near		; print message
	push ax
	mov ah,9
	int 21h
	pop ax
	ret
p220	endp

copyr	db 'Alarm - Clock',10,13,'$'
msg1	db 'Invalid time - must be from 00:00 to 23:59',10,13,'$'
msg2	db 'Resetting alarm time',10,13,'$'
msg3	db 'Turning clock display off',10,13,'$'

alarm	endp
cseg	ends
end	alarm
