---------------------------- REMIND.DOC (cut here) ----------------------------
REMIND.COM is a program that can pop up a small reminder on line 25 of
your screen at a given time.  It displays messages via the ROM BIOS, and
thus will work on any IBM BIOS-compatible machine with any display
hardware in any graphics mode.  The syntax is: REMIND time message,
where time is one or two hour digits followed by a colon and two minute
digits.  REMIND can also be entered without any arguments, in which case
the pending, or if none, the previous message is shown.  The program
works by communicating with a resident daemon that steals the timer
interrupt (1c).  This communication is via the multiplex interrupt (2f),
using multiplex number f0.  The daemon is installed as a terminate-and-
stay-resident program the first time that remind is executed.  To run on
a machine with the Microsoft system card, first use the program SETTIME
to correctly set the BIOS timer (DOS's TIME command doesn't do this when
the system card's clock.sys device driver is installed).

Written by Robert Lenoil; June/July 1985.
------------------------ end of REMIND.DOC (cut here) -------------------------

---------------------------- REMIND.ASM (cut here) ----------------------------
; Displays user-supplied message on 25th screen line at user-specified time.
; Author: Robert Lenoil		Date: June, 1985

;Placed in the public domain, June 1986.
;Author's electronic mail address:
;USENET: lenoil@mit-eddie.uucp			ARPA: lenoil@eddie.mit.edu

DOSFN	MACRO	FNUM		;macro to make DOS function call
IF	FNUM/256
	MOV	AX,FNUM
ELSE
	MOV	AH,FNUM
ENDIF
	INT	21H
	ENDM

PRINT	MACRO	TEXT		;macro to print message at ds:text
	MOV	BX,OFFSET RESGRP:TEXT
	CALL	MSGOUT
	ENDM

ATTRIB	EQU	0F0H		;flashing black foreground, white background

BIOSDAT	SEGMENT	AT 40H
ORG	6CH
TIMER_LOW	DW	?	;low word of timer count
TIMER_HIGH	DW	?	;high word of timer count
TIMER_OFL	DB	?	;timer has rolled over since last read
BIOSDAT	ENDS

RESGRP	GROUP	DATA,RESDNT,NONRES

DATA	SEGMENT
ORG	2CH
ENVSEG	DW	?		;seg address of environment
ORG	70H
RINT2F	DD	?		;address of next in int2f chain
RINT1C	DD	?		;address of real int1c handler
MSGON	DB	?		;zero if message already on
MSGLOW	DW	?		;when to put up message (in timer ticks)
MSGHIGH	DW	?
MSG	DW	?		;ptr to start of message on command line
MSGLEN	DB	?		;message length
CMDLEN	DB	?		;command line length
CMD	LABEL	BYTE		;command line
ORG	100H
DATA	ENDS

RESDNT	SEGMENT			;handle timer interrupt
ASSUME	CS:RESGRP,DS:BIOSDAT

ENTRY:	JMP	NEAR PTR START

;note that on entry, caller's AX,DX are saved; DS points to BIOS data area
INT1C:	STI
	SUB	AX,AX
	CMP	MSGON,AL	;has message already been displayed?
	JE	EXINT		;yes, exit
	CMP	TIMER_OFL,AL	;has timer overflowed?
	JNE	EXINT		;yes, exit (it's after midnight)
	MOV	AX,TIMER_HIGH
	CMP	AX,MSGHIGH
	JB	EXINT
	JA	DISPLAY
	MOV	AX,TIMER_LOW
	CMP	AX,MSGLOW
	JB	EXINT

DISPLAY:			;it's time: print message
	PUSH	BX		;save regs
	PUSH	CX
	PUSH	DX
	PUSH	BP
	PUSH	SI
	PUSH	DI

	MOV	BL,2		;send two beeps
BEEPLP:	MOV	AX,0E07H
	INT	10H
	DEC	BL
	JNZ	BEEPLP

	MOV	AH,15
	INT	10H		;[BH]=active display page, [AH]=max # columns
	PUSH	AX		;[AL]=video mode
	MOV	AH,3
	INT	10H		;[DX] = cursorpos
	POP	AX
	PUSH	DX
	PUSH	AX
	MOV	SI,MSG		;[SI] = ptr to msg
	MOV	CL,MSGLEN
	SUB	CH,CH		;[CX] = msg length
	POP	AX
	CMP	CL,AH		;check if msg longer than screen width
	JLE	DISP1
	XCHG	CL,AH		;yes, truncate
DISP1:	MOV	BL,ATTRIB	;load screen attribute
	CMP	AL,4		;are we in a graphics mode (AL > 3)?
	JB	DISP2
	AND	BL,7FH		;yes, turn off bit 7 (otherwise characters are
				;XORed onto screen, which isn't what we want.)
DISP2:	MOV	DX,1800H	;set cursorpos to row 24, column 0
DISPLP:	MOV	AH,2		;set cursorpos
	INT	10H
	MOV	AH,9		;function = write char/attrib
	MOV	AL,CS:[SI]	;get character
	PUSH	SI
	PUSH	CX
	MOV	CX,1		;repeat count of one
	INT	10H		;write it
	POP	CX
	POP	SI
	INC	SI		;position to next char
	INC	DX		;increment cursorpos
	LOOP	DISPLP		;loop till cx=0

	POP	DX		;restore cursorpos
	MOV	AH,2
	INT	10H

	MOV	MSGON,CH	;set displayed flag

	POP	DI		;pop regs
	POP	SI
	POP	BP
	POP	DX
	POP	CX
	POP	BX

EXINT:	JMP	RINT1C		;jump to real timer tick handler

INT2F:	CMP	AH,0F0H		;if not our number, chain to next
	JE	OUR2F
	JMP	RINT2F

OUR2F:	CMP	AL,0		;is function Get Installed State?
	JE	XINT2F
	PUSH	CS		;otherwise load our segment into es
	POP	ES
XINT2F:	MOV	AL,0FFH		;tell caller that we're installed
	IRET
RESDNT	ENDS

NONRES	SEGMENT
ASSUME	DS:RESGRP
START:	;deallocate environment space
	MOV	AX,ENVSEG
	MOV	ES,AX
	DOSFN	49H

	;erase 25th screen line
	MOV	AH,15
	INT	10H		;[BH]=active display page, [AH]=max # columns
	MOV	BL,AH		;save ah
	MOV	AH,3
	INT	10H		;[DX] = cursorpos
	PUSH	DX
	MOV	DX,1800H	;set cursorpos to row 24, column 0
	MOV	AH,2
	INT	10H
	SUB	CX,CX
	XCHG	BL,CL		;[CX]=screen width, [BL]=0
	MOV	AH,9		;write (screen width) chars w/attribute 0
	INT	10H	
	POP	DX		;restore cursorpos
	MOV	AH,2
	INT	10H

	MOV	AX,0F000H	;perform installation check
	INT	2FH
	CMP	AL,0FFH		;are we installed?
	JNE	INSTALL
	MOV	AX,0F001H	;yes, get segment of resdnt in es
	INT	2FH
	XOR	CH,CH		;reset just-installed flag
	JMP	SHORT PARSE

ASSUME	ES:RESGRP
INSTALL:			;install resident code
	OR	AL,AL		;can we install?
	JNZ	CANT		;al != 0; can't install resdnt code
	MOV	MSGON,AL	;turn off display flag until we're ready
	DOSFN	352FH		;store address of int2f handler
	MOV	WORD PTR RINT2F,BX
	MOV	BX,ES
	MOV	WORD PTR RINT2F+2,BX
	MOV	DX,OFFSET RESGRP:INT2F	;set int2f vector to us
	DOSFN	25H
	DOSFN	351CH		;store address of real int1c handler
	MOV	WORD PTR RINT1C,BX
	MOV	BX,ES
	MOV	WORD PTR RINT1C+2,BX
	MOV	DX,OFFSET RESGRP:INT1C	;set int1c vector to us
	DOSFN	25H
	PRINT	LOADED
	MOV	CH,1		;flag that we just installed ourself

;parse command line
PARSE:	SUB	BX,BX
	MOV	CL,CMDLEN
	CALL	EAT_SPACE	;eat initial whitespace
	JC	GETHRS
;command line is empty. show pending message
	OR	CH,CH
	JNZ	STAY0		;just installed ourself; there is no message
	CMP	ES:MSGON,0	;is there a pending message?
	JE	NOMSG
	PRINT	PENDING		;yes, print "pending"
	JMP	SHORT PRCMD
NOMSG:	PRINT	LASTMSG		;no, print "last reminder"
PRCMD:	PUSH	ES		;get resident segment in ds
	POP	DS
	PRINT	CMD		;print reminder
EXIT0:	XOR	AL,AL		;exit with errorlevel = 0
EXIT:	DOSFN	4CH

CANT:	PRINT	NOLOAD
	MOV	AH,2
	JMP	SHORT EXIT

STAY0:	MOV	AL,0		;errorlevel = 0
STAY:	MOV	DX,OFFSET RESGRP:START	;terminate and stay resident
	MOV	CL,4
	SHR	DX,CL
	DOSFN	31H

GETHRS:	CALL	GETDIG1
	CMP	CMD[BX],':'
	JE	GOTHRS
	CALL	GETDIG2
	CMP	CMD[BX],':'
	JNE	SYNTAX
GOTHRS:	CMP	AL,24		;check for range 0-23
	JGE	SYNTAX
	INC	BX		;skip colon
	MOV	DX,65520
	MUL	DX		;convert hours to timer ticks
	MOV	MSGHIGH,DX
	MOV	MSGLOW,AX

	CALL	GETDIG1		;get seconds
	CALL	GETDIG2
	CMP	AL,60		;check for range 0-59
	JGE	SYNTAX
	MOV	DX,1092
	MUL	DX		;convert to timer ticks
	ADD	MSGLOW,AX	;and add to hours
	ADC	MSGHIGH,DX

	CMP	CMD[BX],20H	;at least one space required
	JNE	SYNTAX
	CALL	EAT_SPACE	;consume any others
	JNC	SYNTAX

	LEA	AX,CMD[BX]	;store start of message ptr
	MOV	MSG,AX
	MOV	DL,CL
	SUB	DL,BL		;store message length
	MOV	MSGLEN,DL

	INC	MSGON		;all fields are setup, set the display flag

	OR	CH,CH		;are we the resident code?
	JNZ	STAY0		;yes: we're done
	CMP	ES:MSGON,0	;no: will we overwrite a pending message?
	JE	DWNLD

	PUSH	CX		;yes, print it first
	PRINT	OVRWRT
	PUSH	ES
	POP	DS
	PRINT	CMD
	PUSH	CS
	POP	DS
	POP	CX

DWNLD:	STD			;no: download msg to resident code
	;we move backwards so that the display flag is the last byte written
	ADD	CL,9
	MOV	SI,OFFSET RESGRP:MSGON - 1
	ADD	SI,CX
	MOV	DI,SI
	REP	MOVSB
	JMP	EXIT0		;we're done

SYNTAX:	PUSH	CX		;save ch
	PRINT	SERROR
	POP	CX
	MOV	AL,1		;errorlevel = 1
	OR	CH,CH		;are we the resident code?
	JNZ	GOSTAY		;yes, then stay resident
	JMP	EXIT		;else just exit
GOSTAY:	JMP	STAY

EAT_SPACE	PROC	NEAR
;Advances cmd[bx] past any spaces.  Resets carry if ran off end of cmd.
	CMP	BL,CL
	JNC	ATE
	CMP	CMD[BX],20H
	STC
	JNE	ATE
	INC	BX
	JMP	SHORT EAT_SPACE
ATE:	RET
EAT_SPACE	ENDP

GETDIG1	PROC	NEAR
;GETDIG1 gets digit in AX.  GETDIG2 multiplies AX by 10 and adds new digit.
	SUB	AX,AX
GETDIG2:
	MOV	DL,CMD[BX]
	CMP	DL,'0'		;check for digit range
	JL	SYNTAX
	CMP	DL,'9'
	JG	SYNTAX
	INC	BX
	SUB	DL,'0'
	MOV	DH,10
	MUL	DH
	ADD	AL,DL
	RET
GETDIG1	ENDP

MSGOUT	PROC	NEAR		;displays string at ds:bx w/length byte at bx-1
	SUB	CH,CH		;output message to stderr
	MOV	CL,[BX]-1
	MOV	DX,BX
	MOV	BX,2
	DOSFN	40H
	MOV	DL,0DH		;output CRLF to console
	DOSFN	6H
	MOV	DL,0AH
	INT	21H
	RET
MSGOUT	ENDP

;Messages (each preceeded by a byte holding its length)
	DB	27
SERROR	DB	"Usage: REMIND hh:mm message"
	DB	34
NOLOAD	DB	"System error: Can't install daemon"
	DB	24
LOADED	DB	"REMIND daemon installed."
	DB	8
PENDING	DB	"Pending:"
	DB	35
LASTMSG	DB	"Nothing pending; last reminder was:"
	DB	28
OVRWRT	DB	"Overwriting pending message:"
NONRES	ENDS

END	ENTRY
------------------------ end of REMIND.ASM (cut here) -------------------------
