[alt.sources] NPRINT: a TSR to submit a file to DOS's PRINT.COM

chris@lxn.chi.il.us (Christopher D. Orr) (03/17/90)

I have written a TSR in Assembly that is fairly useful to our organization.
I am interested in getting feedback from other users about its style and
implementation.

NOTE:  this program makes use of the undocumented interrupt 28h and DOS's
function 34h (Critical Section Flag).  I believe that this was the best way
to implement it, but, as they say, let the NET decide :-)



#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	nprint.asm
#	nprint.doc
# This archive created: Fri Mar 16 13:54:17 1990
export PATH; PATH=/bin:$PATH
if test -f 'nprint.asm'
then
	echo shar: will not over-write existing file "'nprint.asm'"
else
cat << \SHAR_EOF > 'nprint.asm'
;****************************************************************************
;  NPRINT.COM:   A TSR to submit a file to PRINT.COM
;****************************************************************************

cseg	segment	para public 'CODE'
	assume	cs:cseg, ds:nothing, es:nothing, ss:nothing


	org	100h
entpt:	jmp	install		; Jump to the Install code

VIDEO		EQU	10h	; Interrupts used within NPRINT
KEYBOARD	EQU	16h
PRINTER		EQU	17h
MULTIPLEX	EQU	2Fh
DOS		EQU	21h
MONOSCR		EQU	0B000h	; Monochrome Screen Memory Location
COLORSCR	EQU	0B800h	; CGA (COLOR) Screen Memory Location

copyright	db	'NPRINT v1.0:  (c) Copyright 1990 by Christopher D. Orr',13,10,'$'
prompt		db	'Enter Filename: ',0
no_print	db	'Printer is not responding.  Please correct.  Hit any Key',0
printpack	db	0,0,0,0,0
invoke_flag	db	0	; Flag set whenever a request for us in pending
cursor		dw	0	; Original cursor position
pr_attribute	db	07h	; Attribute for the Prompt/Messages
scr_attribute	db	07h	; Attribute for data entry
do_prtsc	db	0	; Flag to indicate whether to do a screen print
display_page	db	0	; Current Screen display page
tone		dw	0	; Storage for frequency of bell when rung
CritSectFlag	dd	0	; Critical Section Flag
old_int_5	dd	0	; Address of original print screen handler
old_int_28	dd	0	; Address of original DOS handler
pause_msg	db	' ... ',0
errmsg		db	'PRINT returned error code '
errcode		db	0,0
		db	'h:  ',0

err_table	dw	errmsg1, errmsg2, errmsg3, errmsg4, errmsg5
		dw	e_unknown, e_unknown, errmsg8, e_unknown, e_unknown
		dw	e_unknown, errmsgc, e_unknown, e_unknown, errmsgf
errmsg1		db	'Function Invalid',0
errmsg2		db	'File not Found',0
errmsg3		db	'Path not Found',0
errmsg4		db	'Too many files open',0
errmsg5		db	'Access Denied',0
errmsg8		db	'PRINT Queue full',0
errmsgc		db	'Filename too long',0
errmsgf		db	'Drive invalid',0
e_unknown	db	'Unknown Return Code',0

datasize	=	($ - offset copyright)


;-----------------------------------------------------------------------------
;  Interrupt Handlers - This procedure defines code to determine when/if we
;                       should invoke the NPRINT main routine.
;-----------------------------------------------------------------------------
nprint	proc	far
int28:
	cmp	cs:[invoke_flag], 0FFh	; Should we invoke ourselves ?
	jnz	short int_return	; No, so get out
	call	main			; Invoke NPRINT
	pushf				; Push the flags
	cli				; Disable interrupts
	push	cs			; Push the CS register
	mov	di,offset cs:int_return	; When we return, we want to exit
	push	di
	jmp	dword ptr cs:old_int_28	; Transfer control to old int 28h

int5:
	push	bx
	push	es
	mov	bx,word ptr CritSectFlag[0]	; Look at the Crit. Sec. Flag
	mov	es,word ptr CritSectFlag[2]
	cmp	byte ptr es:[bx],0h		; If non-zero, DOS is busy
	je	ok2run				; Zero, so invoke NPRINT
	pop	es
	pop	bx
	mov	cs:[invoke_flag], 0FFh		; Leave a flag to tell us to
	jmp	short int_return

ok2run:
	pop	es
	pop	bx
	call	main				; Call NPRINT

int_return:
	iret					; and we return ...

endp	nprint


;-----------------------------------------------------------------------------
;  Main Procedure - Responsible for processing user request
;-----------------------------------------------------------------------------
main	proc	near
	sti			; Allow Interrupts

	push	bp		; Access to stack
	mov	bp,sp		; Before we push anything

	pushf			; Save the state of the machine
	push	es		; because we're gonna make
	push	ds		; a mess of it
	push	di
	push	si
	push	dx
	push	cx
	push	bx
	push	ax

	mov	ax,cs			; Establish the proper segments
	mov	ds,ax
	mov	es,ax			; Make ES point to code segment
	assume	ds:cseg, es:cseg	; Tell the assembler


ok2load:
	mov	[invoke_flag], 0h	; Clear Invocation flag

	mov	ah,0fh			; Get the current Display Mode
	int	VIDEO
	mov	[display_page],bh	; Save current page
	cmp	al,07h			; Is this a Monochrome Monitor ?
	je	mono			; Yes, so we are okay
	cmp	al,03h			; Is this a CGA Monitor in 80 col mode?
	je	cga			; Yes, so we are okay
	call	ring_bell		; No - so don't execute.  Just abort.
	jmp	done			; Exit NPRINT

mono:
	mov	ax,MONOSCR	; Monochrome Display
	jmp	short saveline
cga:
	mov	ax,COLORSCR	; CGA display

saveline:
	mov	ds,ax		; Set up the Data Segment
	mov	si,3840		; Start at position row=25, column=1
	mov	cx,80		; 80 Character lines
	lea	di, screenbuf
	push	es		; Save screen memory locations
	push	ds
	push	si
	cld			; Make sure we copy from right to left
	rep	movsw		; Move character & Attribute

	mov	ax,cs		; Restore the Data Segment
	mov	ds,ax

	mov	ah,03h		; Get the Current Cursor Position
	int	VIDEO
	mov	[cursor], dx	;    and save it.

	call	clear_25	; Goto and Clear Line #25
	lea	si, prompt	; Display the filename prompt for the user
	call	wr_string

	lea	dx,filename	; Point to out filename buffer
	mov	ax,60		; Limit filename to 60 characters
	call	rd_string	; Request the filename from the user
	
	cmp	ax,0		; Did we get a file name ???
	jne	submit_file	; No, so don't do the print routine
	mov	ah,02h		; Let's see if the printer is busy
	mov	dx,0		; Check printer number 0
	int	PRINTER
	test	ah,01101001B	; Clear bits 8,5,3,2 -> meaningless to us
	jz	prtsc_okay
	lea	si, no_print	; No, then there is a problem w/
	call	clear_25	;   the printer.  Tell the user about it
	call	ring_bell
	call	wr_string
	call	pause
	jmp	restscreen	; No sense printing a screen now, so don't

prtsc_okay:
	mov	[do_prtsc],0FFh	; Set the Print Screen Flag
	jmp	restscreen

submit_file:
	mov	[do_prtsc],0h	; Clear the Print Screen Flag
	mov	word ptr [printpack]+3,ds	; Put the address into packet
	mov	word ptr [printpack]+1,offset filename

retry_file:
	lea	dx,printpack	; Point to the printer request packet
	mov	ax,0101h	; Submit packet to Print Spooler
	int	MULTIPLEX	; Invoke Multiplex Service Interrupt for PRINT
	jnc	restscreen	; If the carry flag is set, then we had an error

	cmp	ax,09h		; Is the spooler busy ?   (NOTE: endless loop?)
	je	retry_file	; Yes, so try again
	call	display_errmsg	; Some other kind of error

restscreen:
	lea	si, screenbuf	; Point to our screen buffer
	pop	di		; Restore Screen Memory Locations
	pop	es
	pop	ds		; Restore the Data Segment
	mov	cx,80		; Still name 80 character line
	cld			; Make sure we copy from right to left
	rep	movsw

	mov	ax,cs		; Restore the Data and Extended Segment values
	mov	ds,ax
	mov	es,ax

	mov	dx,[cursor]	; Restore the cursor position to what it was
	mov	bh,[display_page]
	mov	ah,02h
	int	VIDEO

;----------------------------------------------------------------------
; Restore the state of the machine when Int 05 occured
;----------------------------------------------------------------------
done:
	pop	ax
	pop	bx
	pop	cx
	pop	dx
	pop	si
	pop	di
	pop	ds
	pop	es
	popf
	pop	bp

IFDEF DEBUG
	mov	ax,4c00h
	int	DOS
ENDIF

assume	ds:nothing, es:nothing
	cmp	cs:[do_prtsc],0FFh	; Is the Print Screen flag set ?
	jne	nprint_exit		; no, so we should just exit

	pushf				; Push the flags
	cli				; Disable interrupts
	push	cs			; Push the CS register
	mov	di,offset cs:formfeed
	push	di
	jmp	dword ptr cs:old_int_5	; Transfer control to old int 05h

formfeed:
	pushf			; Save the state of the machine yet again
	push	es
	push	ds
	push	di
	push	si
	push	dx
	push	cx
	push	bx
	push	ax
	assume	ds:cseg, es:cseg	;Tell the assembler

	mov	ah,00h		; Print a character
	mov	al,0ch		; Namely a Formfeed (^L)
	mov	dx,0		; Printer number one
	int	PRINTER		; Invoke Print Interrupt

	pop	ax		; Restore everything again
	pop	bx
	pop	cx
	pop	dx
	pop	si
	pop	di
	pop	ds
	pop	es
	popf

nprint_exit:
	ret				; And we return ...
main	endp


;======================================================================
; WR_STRING - write string to console at specified location.
;	The string is ASCIIZ.   All registers are preserved.
;----------------------------------------------------------------------
wr_string	proc	near

		push	si
		push	bx
		push	cx
		push	ax

wnext_char:
		lodsb				; Load char in AL from DS:SI
		or	al,al			; If char is 0
		jz	end_string		;   Then end of ASCIIZ string
		mov	ah,09h			;   Else, write TTY
		mov	bh,[display_page]
		mov	bl,[pr_attribute]
		mov	cx,01h			; Only one
		int	VIDEO			;   thru BIOS
		mov	al,1
		call	move_cursor		; Move the cursor to the right
		jmp	short wnext_char	;   and do it all again
end_string:
		pop	ax
		pop	cx
		pop	bx
		pop	si
		ret

wr_string	endp


;-----------------------------------------------------------------------------
; Read a String from the Keyboard and returns a pointer to it
;    Point to String using DS:DX.  RETURNS:  AX contains number of chars read
;-----------------------------------------------------------------------------
rd_string	proc	near

		push	bx
		push	cx
		push	di
		push	dx

		mov	di,dx		; Setup our buffer pointer
		mov	bx,ax		; Store the max length in BX
rnext_char:
		mov	ah,0h		; Read Keyboard Character Function
		int	KEYBOARD
		cmp	al,08h		; Backspace ?
		jne	check_enter	; No, so jump
		pop	dx		; Restore the head of buffer pointer
		push	dx
		cmp	di,dx		; Are we at the start of line?
		jle	rnext_char	; Yes, so ignore the backspace

		dec	di		; Decrement our buffer pointer
		mov	al,0		;  and move the cursor back
		call	move_cursor

		push	bx		; Now we have to erase the character
		mov	bh,[display_page]
		mov	cx,1
		mov	bl,[scr_attribute]
		mov	ah,09h		; Function for writing a character
		mov	al,20h		; Put a space on the screen
		int	VIDEO
		pop	bx
		jmp	short rnext_char

check_enter:		
		cmp	al,0dh		; Did we get a return ?
		jne	letter
		mov	byte ptr [di],0h; Already at end of string, so add null
		pop	dx		; Restore pointer to start of buffer
		sub	di,dx		; How many chars did we read ?
		mov	ax,di		; Return that number in AX
		pop	di		; Restore the Data Index Register
		pop	cx
		pop	bx
		ret		

letter:		
		cmp	al,20h		; Check for a valid character
		jle	rnext_char	; Ignore none printable characters/keys
		cmp	al,7eh
		jg	rnext_char

		pop	dx		; Restore the head of buffer pointer
		push	dx
		add	dx,bx		; Maximum of "bx" characters
		cmp	di,dx		; Yes, so are we at the end of buffer?
		jne	char_ok
		call	ring_bell	; Yes, so sound the alarm
		jmp	rnext_char

char_ok:
		mov	[di],al		; Store the character
		inc	di		; Increment our pointer

		push	bx
		mov	cx,1
		mov	bl,[scr_attribute]
		mov	ah,09h		; Display the character to the user
		mov	bh,[display_page]
		int	VIDEO
		mov	al,1		; Increment the cursor
		pop	bx
		call	move_cursor
		jmp	rnext_char	; Get the next character

rd_string	endp


;-----------------------------------------------------------------------------
; Increment or Decrement the current cursor position.  AX=0 implies decrement
;-----------------------------------------------------------------------------
move_cursor	proc	near
		push	bx
		push	cx
		push	dx

		mov	ah,03h		; Get cursor position
		mov	bh,[display_page]
		cmp	al,0		; If code 0, then decrement cursor
		je	dec_cursor
		int	VIDEO
		inc	dl
		jmp	short last_cur
dec_cursor:
		int	VIDEO
		dec	dl
last_cur:
		mov	ah,02h		; Set the Cursor Position
		int	VIDEO

		pop	dx
		pop	cx
		pop	bx
		ret
move_cursor	endp


;----------------------------------------------------------------------
;	HEX2 -  Convert the AL register to hexidecimal digits.
;		The characters produced are stored at ES:DI.
;		All regs preserved.
;----------------------------------------------------------------------
hex2	proc	near
		push	ax
		push	bx
		push	cx

		mov	bx,ax
		std				;String ptr decrement
		add	di,1			;Point to end of string
		mov	cx,2
h10:
		mov	al,bl			;Want lower half
		and	al,0fh			; of this byte
		add	al,90h			;Convert AL to ASCII
		daa
		adc	al,40h
		daa
		stosb				;Store at ES:DI
		shr	bx,1
		shr	bx,1
		shr	bx,1
		shr	bx,1
		loop	h10

		pop	cx
		pop	bx
		pop	ax
		inc	di
		cld
		ret
hex2	endp


;-----------------------------------------------------------------------------
; Erase the 25th line of the screen and position the cursor on that line
;-----------------------------------------------------------------------------
clear_25	proc	near
		push	ax
		push	dx
		push	cx
		push	bx

		mov	ah,02		; Set the Cursor Position
		mov	dh,24		; Go to the 25th line
		mov	dl,0h		; column number 1.
		int	VIDEO

		mov	ah,09h		; Clear the line ...
		mov	al,20h		; using spaces
		mov	bh,[display_page]
		mov	bl,07h		; Change the attributes to white
		mov	cx,80		; Write 80 characters
		int	VIDEO

		pop	bx
		pop	cx
		pop	dx
		pop	ax
		ret
clear_25	endp


;-----------------------------------------------------------------------------
;  Pause so the user can press a key ... any key ...
;-----------------------------------------------------------------------------
pause	proc	near
	mov	si, offset pause_msg
	call	wr_string

top_pause:
	mov	ah,01h			; Wait for a key to be pressed
	int	KEYBOARD
	jz	top_pause
	mov	ah,00h			; Eat the keystroke
	int	KEYBOARD	

;	cmp	al,0dh			; Only continue with the [RETURN] key
;	jne	top_pause

	ret
pause	endp


;-----------------------------------------------------------------------------
; Display the Error Code and Message
;-----------------------------------------------------------------------------
display_errmsg	proc	near
	call	clear_25
	call	ring_bell
	mov	di,offset errcode	; Display the error code
	call	hex2
	lea	si, errmsg
	call	wr_string
	cmp	al,0fh			; Is the error off the scale ??
	jle	table
	lea	si,e_unknown		; Yes, so display "unknown" message
	jmp	short show_error

table:
	xor	bh,bh			; Build offset into error table
	mov	bl,al			; Can't use AX as a memory index ptr
	dec	bx			; Table is base 0, so reduce the index
	shl	bx,1			; Make offset into table (*2)
	mov	si,err_table[bx]

show_error:
	call	wr_string		; Now display the error
	call	pause
	ret
display_errmsg	endp


;-----------------------------------------------------------------------------
; Ring the Bell to indicate that an error occured - we create a special tone
;               so that the user knows it is NPRINT that did it.
;-----------------------------------------------------------------------------
ring_bell	proc	near
		push	ax
		push	cx
		push	dx

		in	al,61h		; Get port data
		push	ax		;   and save it
		cli			; Clear Interrupts

		mov	dx,0ch		; Length of bell tone
		mov	[tone],500h	; Frequency
		call	speaker

		mov	dx,0ch		; Length of bell tone
		mov	[tone],1000h	; Frequency
		call	speaker

		pop	ax		; Reset
		out	61h,al		;   port data
		sti			; Reset Interrupts

		pop	dx
		pop	cx
		pop	ax
		ret
ring_bell	endp

		
;-----------------------------------------------------------------------------
; Activate the speaker for a specified time and tone.
;-----------------------------------------------------------------------------
speaker		proc	near
BELL30:
		and	al,11111100B	; Set bits 0 & 1 off
		out	61h,al		; Transmit to speaker
		mov	cx,[tone]	; Set the length
BELL40:
		loop	BELL40		; Time Delay
		or	al,00000010B	; Set bit 1 on
		out	61h,al		; Transmit to speaker
		mov	cx,[tone]	; Set length
BELL50:
		loop	BELL50		; Time Delay
		dec	dx		; Reduce Duration
		jnz	BELL30		; Continue ?
		ret			; Nope, we are all done.
speaker		endp


filename	db	61 DUP (?)	; Storage for user inputted filename
screenbuf	db	80*2 DUP (?)	; Storage for screen line we clear
lastbyte = $


; ---------------------------------------------------------------------------
;  Main routine to install NPRINT as a TSR
; ---------------------------------------------------------------------------

install	proc	near
	assume	cs:cseg,ds:cseg,es:cseg,ss:cseg

	lea	dx,copyright
	mov	ah,09h		; Print a copyright message
	int	DOS

	mov	ah,01h		; PRINT Function
	mov	al,00h		; Print Installation Check Subfunction
	int	MULTIPLEX	; Multiplex Service Interrupt
	cmp	al,0FFh
	je	ok2inst		; If we get FFh returned, then PRINT is there.
	lea	dx,noprint
	mov	ah,09h		; Print a failing message
	int	DOS
	mov	ax,4c01h	; Terminate with error code 1
	int	DOS

ok2inst:
IFDEF DEBUG
	call	main
ENDIF

	mov	ah,35h		; Determine Interrupt vector for PrintScreen
	mov	al,05h
	int	DOS
	mov	word ptr old_int_5[0],bx	;offset
	mov	word ptr old_int_5[2],es	;segment

	mov	ah,35h		; Determine Interrupt vector for DOS call
	mov	al,28h
	int	DOS
	mov	word ptr old_int_28[0],bx	;offset
	mov	word ptr old_int_28[2],es	;segment

	lea	si,copyright	; Lets see if we are already installed.
	mov	di,bx		; Put the offset into the DI register
	sub	di,datasize	; Position ourselves to the start of the data
	mov	cx,6		; Compare 6 bytes (NPRINT)
	repe	cmpsb		; Repeat as long as they match
	jne	not_installed	; If it doesn't match, then we aren't installed
	lea	dx,inst_err	; OOPS, they match.  Tell the user about it.
	mov	ah,09h
	int	DOS
	mov	ax,4c02h	; Return to DOS with errorlevel 2
	int	DOS

not_installed:
	mov	ah,34h				; Locate the Critical Sec. Flag
	int	21h
	mov	word ptr CritSectFlag[0],bx	;offset
	mov	word ptr CritSectFlag[2],es	;segment

	mov	ah,09h		; Tell the user that we are installing
	lea	dx,inst_msg
	int	DOS

	mov	ah,25h		; Set Interrupt Vector for Print Screen
	mov	al,05h
	lea	dx,int5
	int	DOS

	mov	ah,25h		; Set Interrupt Vector for DOS Check
	mov	al,28h
	lea	dx,int28
	int	DOS

	mov	dx,code_pars	; Tell DOS how much memory we require
	mov	ax,3100h	;   and become a TSR
	int	DOS

install	endp

noprint		db	'DOS PRINT.COM is not installed.',13,10,'$'
inst_err	db	'NPRINT already installed.',7,13,10,'$'
inst_msg	db	'NPRINT installed successfully.',13,10,'$'

code_size = (offset lastbyte - offset entpt)
code_pars = (code_size / 16) + 32

cseg	ends
	end	entpt
SHAR_EOF
if test 17331 -ne "`wc -c < 'nprint.asm'`"
then
	echo shar: error transmitting "'nprint.asm'" '(should have been 17331 characters)'
fi
fi # end of overwriting check
if test -f 'nprint.doc'
then
	echo shar: will not over-write existing file "'nprint.doc'"
else
cat << \SHAR_EOF > 'nprint.doc'
			   NPRINT v1.0

		 (c) 1990 by Christopher D. Orr


OVERVIEW:

   NPRINT is a TSR designed to prompt the user for a filename and
pass that file off to DOS's PRINT.COM.  If no filename is
specified, NPRINT will attempt a print screen.  If no printer is
detected (or a print error occurs), NPRINT will inform the user
and abort the print screen.  If a print screen is successful,
NPRINT will send a formfeed to the printer.



FUNCTIONAL DESCRIPTION:

  *  NPRINT will only activate in modes 3 or 7.
  *  The 25th text line is saved.
  *  The user is prompted to enter a filename on the 25th text line.
  *  A request is made to PRINT to print the file.
  *  Errors returned by PRINT are displayed to the user.
  *  The 25th text line is restored.

  *  A blank filename will cause a print screen to occur
  *  NPRINT checks to see if the printer is available
  *  If the printer is not available, NPRINT will not attempt a print screen
  *  After a successful print screen, NPRINT will send a formfeed to the printer



ACTIVATION:
  NPRINT is activated by pressing the [PrtScr] key.
SHAR_EOF
if test 1086 -ne "`wc -c < 'nprint.doc'`"
then
	echo shar: error transmitting "'nprint.doc'" '(should have been 1086 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0
Newsgroups: comp.sys.ibm.pc.programmer
Subject: NPRINT:  a TSR to submit a file to DOS's PRINT.COM
Distribution: usa

I have written a TSR in Assembly that is fairly useful to our organization.
I am interested in getting feedback from other users about its style and
implementation.

NOTE:  this program makes use of the undocumented interrupt 28h and DOS's
function 34h (Critical Section Flag).  I believe that this was the best way
to implement it, but, as they say, let the NET decide :-)



#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	nprint.asm
#	nprint.doc
# This archive created: Fri Mar 16 13:54:17 1990
export PATH; PATH=/bin:$PATH
if test -f 'nprint.asm'
then
	echo shar: will not over-write existing file "'nprint.asm'"
else
cat << \SHAR_EOF > 'nprint.asm'
;****************************************************************************
;  NPRINT.COM:   A TSR to submit a file to PRINT.COM
;****************************************************************************

cseg	segment	para public 'CODE'
	assume	cs:cseg, ds:nothing, es:nothing, ss:nothing


	org	100h
entpt:	jmp	install		; Jump to the Install code

VIDEO		EQU	10h	; Interrupts used within NPRINT
KEYBOARD	EQU	16h
PRINTER		EQU	17h
MULTIPLEX	EQU	2Fh
DOS		EQU	21h
MONOSCR		EQU	0B000h	; Monochrome Screen Memory Location
COLORSCR	EQU	0B800h	; CGA (COLOR) Screen Memory Location

copyright	db	'NPRINT v1.0:  (c) Copyright 1990 by Christopher D. Orr',13,10,'$'
prompt		db	'Enter Filename: ',0
no_print	db	'Printer is not responding.  Please correct.  Hit any Key',0
printpack	db	0,0,0,0,0
invoke_flag	db	0	; Flag set whenever a request for us in pending
cursor		dw	0	; Original cursor position
pr_attribute	db	07h	; Attribute for the Prompt/Messages
scr_attribute	db	07h	; Attribute for data entry
do_prtsc	db	0	; Flag to indicate whether to do a screen print
display_page	db	0	; Current Screen display page
tone		dw	0	; Storage for frequency of bell when rung
CritSectFlag	dd	0	; Critical Section Flag
old_int_5	dd	0	; Address of original print screen handler
old_int_28	dd	0	; Address of original DOS handler
pause_msg	db	' ... ',0
errmsg		db	'PRINT returned error code '
errcode		db	0,0
		db	'h:  ',0

err_table	dw	errmsg1, errmsg2, errmsg3, errmsg4, errmsg5
		dw	e_unknown, e_unknown, errmsg8, e_unknown, e_unknown
		dw	e_unknown, errmsgc, e_unknown, e_unknown, errmsgf
errmsg1		db	'Function Invalid',0
errmsg2		db	'File not Found',0
errmsg3		db	'Path not Found',0
errmsg4		db	'Too many files open',0
errmsg5		db	'Access Denied',0
errmsg8		db	'PRINT Queue full',0
errmsgc		db	'Filename too long',0
errmsgf		db	'Drive invalid',0
e_unknown	db	'Unknown Return Code',0

datasize	=	($ - offset copyright)


;-----------------------------------------------------------------------------
;  Interrupt Handlers - This procedure defines code to determine when/if we
;                       should invoke the NPRINT main routine.
;-----------------------------------------------------------------------------
nprint	proc	far
int28:
	cmp	cs:[invoke_flag], 0FFh	; Should we invoke ourselves ?
	jnz	short int_return	; No, so get out
	call	main			; Invoke NPRINT
	pushf				; Push the flags
	cli				; Disable interrupts
	push	cs			; Push the CS register
	mov	di,offset cs:int_return	; When we return, we want to exit
	push	di
	jmp	dword ptr cs:old_int_28	; Transfer control to old int 28h

int5:
	push	bx
	push	es
	mov	bx,word ptr CritSectFlag[0]	; Look at the Crit. Sec. Flag
	mov	es,word ptr CritSectFlag[2]
	cmp	byte ptr es:[bx],0h		; If non-zero, DOS is busy
	je	ok2run				; Zero, so invoke NPRINT
	pop	es
	pop	bx
	mov	cs:[invoke_flag], 0FFh		; Leave a flag to tell us to
	jmp	short int_return

ok2run:
	pop	es
	pop	bx
	call	main				; Call NPRINT

int_return:
	iret					; and we return ...

endp	nprint


;-----------------------------------------------------------------------------
;  Main Procedure - Responsible for processing user request
;-----------------------------------------------------------------------------
main	proc	near
	sti			; Allow Interrupts

	push	bp		; Access to stack
	mov	bp,sp		; Before we push anything

	pushf			; Save the state of the machine
	push	es		; because we're gonna make
	push	ds		; a mess of it
	push	di
	push	si
	push	dx
	push	cx
	push	bx
	push	ax

	mov	ax,cs			; Establish the proper segments
	mov	ds,ax
	mov	es,ax			; Make ES point to code segment
	assume	ds:cseg, es:cseg	; Tell the assembler


ok2load:
	mov	[invoke_flag], 0h	; Clear Invocation flag

	mov	ah,0fh			; Get the current Display Mode
	int	VIDEO
	mov	[display_page],bh	; Save current page
	cmp	al,07h			; Is this a Monochrome Monitor ?
	je	mono			; Yes, so we are okay
	cmp	al,03h			; Is this a CGA Monitor in 80 col mode?
	je	cga			; Yes, so we are okay
	call	ring_bell		; No - so don't execute.  Just abort.
	jmp	done			; Exit NPRINT

mono:
	mov	ax,MONOSCR	; Monochrome Display
	jmp	short saveline
cga:
	mov	ax,COLORSCR	; CGA display

saveline:
	mov	ds,ax		; Set up the Data Segment
	mov	si,3840		; Start at position row=25, column=1
	mov	cx,80		; 80 Character lines
	lea	di, screenbuf
	push	es		; Save screen memory locations
	push	ds
	push	si
	cld			; Make sure we copy from right to left
	rep	movsw		; Move character & Attribute

	mov	ax,cs		; Restore the Data Segment
	mov	ds,ax

	mov	ah,03h		; Get the Current Cursor Position
	int	VIDEO
	mov	[cursor], dx	;    and save it.

	call	clear_25	; Goto and Clear Line #25
	lea	si, prompt	; Display the filename prompt for the user
	call	wr_string

	lea	dx,filename	; Point to out filename buffer
	mov	ax,60		; Limit filename to 60 characters
	call	rd_string	; Request the filename from the user
	
	cmp	ax,0		; Did we get a file name ???
	jne	submit_file	; No, so don't do the print routine
	mov	ah,02h		; Let's see if the printer is busy
	mov	dx,0		; Check printer number 0
	int	PRINTER
	test	ah,01101001B	; Clear bits 8,5,3,2 -> meaningless to us
	jz	prtsc_okay
	lea	si, no_print	; No, then there is a problem w/
	call	clear_25	;   the printer.  Tell the user about it
	call	ring_bell
	call	wr_string
	call	pause
	jmp	restscreen	; No sense printing a screen now, so don't

prtsc_okay:
	mov	[do_prtsc],0FFh	; Set the Print Screen Flag
	jmp	restscreen

submit_file:
	mov	[do_prtsc],0h	; Clear the Print Screen Flag
	mov	word ptr [printpack]+3,ds	; Put the address into packet
	mov	word ptr [printpack]+1,offset filename

retry_file:
	lea	dx,printpack	; Point to the printer request packet
	mov	ax,0101h	; Submit packet to Print Spooler
	int	MULTIPLEX	; Invoke Multiplex Service Interrupt for PRINT
	jnc	restscreen	; If the carry flag is set, then we had an error

	cmp	ax,09h		; Is the spooler busy ?   (NOTE: endless loop?)
	je	retry_file	; Yes, so try again
	call	display_errmsg	; Some other kind of error

restscreen:
	lea	si, screenbuf	; Point to our screen buffer
	pop	di		; Restore Screen Memory Locations
	pop	es
	pop	ds		; Restore the Data Segment
	mov	cx,80		; Still name 80 character line
	cld			; Make sure we copy from right to left
	rep	movsw

	mov	ax,cs		; Restore the Data and Extended Segment values
	mov	ds,ax
	mov	es,ax

	mov	dx,[cursor]	; Restore the cursor position to what it was
	mov	bh,[display_page]
	mov	ah,02h
	int	VIDEO

;----------------------------------------------------------------------
; Restore the state of the machine when Int 05 occured
;----------------------------------------------------------------------
done:
	pop	ax
	pop	bx
	pop	cx
	pop	dx
	pop	si
	pop	di
	pop	ds
	pop	es
	popf
	pop	bp

IFDEF DEBUG
	mov	ax,4c00h
	int	DOS
ENDIF

assume	ds:nothing, es:nothing
	cmp	cs:[do_prtsc],0FFh	; Is the Print Screen flag set ?
	jne	nprint_exit		; no, so we should just exit

	pushf				; Push the flags
	cli				; Disable interrupts
	push	cs			; Push the CS register
	mov	di,offset cs:formfeed
	push	di
	jmp	dword ptr cs:old_int_5	; Transfer control to old int 05h

formfeed:
	pushf			; Save the state of the machine yet again
	push	es
	push	ds
	push	di
	push	si
	push	dx
	push	cx
	push	bx
	push	ax
	assume	ds:cseg, es:cseg	;Tell the assembler

	mov	ah,00h		; Print a character
	mov	al,0ch		; Namely a Formfeed (^L)
	mov	dx,0		; Printer number one
	int	PRINTER		; Invoke Print Interrupt

	pop	ax		; Restore everything again
	pop	bx
	pop	cx
	pop	dx
	pop	si
	pop	di
	pop	ds
	pop	es
	popf

nprint_exit:
	ret				; And we return ...
main	endp


;======================================================================
; WR_STRING - write string to console at specified location.
;	The string is ASCIIZ.   All registers are preserved.
;----------------------------------------------------------------------
wr_string	proc	near

		push	si
		push	bx
		push	cx
		push	ax

wnext_char:
		lodsb				; Load char in AL from DS:SI
		or	al,al			; If char is 0
		jz	end_string		;   Then end of ASCIIZ string
		mov	ah,09h			;   Else, write TTY
		mov	bh,[display_page]
		mov	bl,[pr_attribute]
		mov	cx,01h			; Only one
		int	VIDEO			;   thru BIOS
		mov	al,1
		call	move_cursor		; Move the cursor to the right
		jmp	short wnext_char	;   and do it all again
end_string:
		pop	ax
		pop	cx
		pop	bx
		pop	si
		ret

wr_string	endp


;-----------------------------------------------------------------------------
; Read a String from the Keyboard and returns a pointer to it
;    Point to String using DS:DX.  RETURNS:  AX contains number of chars read
;-----------------------------------------------------------------------------
rd_string	proc	near

		push	bx
		push	cx
		push	di
		push	dx

		mov	di,dx		; Setup our buffer pointer
		mov	bx,ax		; Store the max length in BX
rnext_char:
		mov	ah,0h		; Read Keyboard Character Function
		int	KEYBOARD
		cmp	al,08h		; Backspace ?
		jne	check_enter	; No, so jump
		pop	dx		; Restore the head of buffer pointer
		push	dx
		cmp	di,dx		; Are we at the start of line?
		jle	rnext_char	; Yes, so ignore the backspace

		dec	di		; Decrement our buffer pointer
		mov	al,0		;  and move the cursor back
		call	move_cursor

		push	bx		; Now we have to erase the character
		mov	bh,[display_page]
		mov	cx,1
		mov	bl,[scr_attribute]
		mov	ah,09h		; Function for writing a character
		mov	al,20h		; Put a space on the screen
		int	VIDEO
		pop	bx
		jmp	short rnext_char

check_enter:		
		cmp	al,0dh		; Did we get a return ?
		jne	letter
		mov	byte ptr [di],0h; Already at end of string, so add null
		pop	dx		; Restore pointer to start of buffer
		sub	di,dx		; How many chars did we read ?
		mov	ax,di		; Return that number in AX
		pop	di		; Restore the Data Index Register
		pop	cx
		pop	bx
		ret		

letter:		
		cmp	al,20h		; Check for a valid character
		jle	rnext_char	; Ignore none printable characters/keys
		cmp	al,7eh
		jg	rnext_char

		pop	dx		; Restore the head of buffer pointer
		push	dx
		add	dx,bx		; Maximum of "bx" characters
		cmp	di,dx		; Yes, so are we at the end of buffer?
		jne	char_ok
		call	ring_bell	; Yes, so sound the alarm
		jmp	rnext_char

char_ok:
		mov	[di],al		; Store the character
		inc	di		; Increment our pointer

		push	bx
		mov	cx,1
		mov	bl,[scr_attribute]
		mov	ah,09h		; Display the character to the user
		mov	bh,[display_page]
		int	VIDEO
		mov	al,1		; Increment the cursor
		pop	bx
		call	move_cursor
		jmp	rnext_char	; Get the next character

rd_string	endp


;-----------------------------------------------------------------------------
; Increment or Decrement the current cursor position.  AX=0 implies decrement
;-----------------------------------------------------------------------------
move_cursor	proc	near
		push	bx
		push	cx
		push	dx

		mov	ah,03h		; Get cursor position
		mov	bh,[display_page]
		cmp	al,0		; If code 0, then decrement cursor
		je	dec_cursor
		int	VIDEO
		inc	dl
		jmp	short last_cur
dec_cursor:
		int	VIDEO
		dec	dl
last_cur:
		mov	ah,02h		; Set the Cursor Position
		int	VIDEO

		pop	dx
		pop	cx
		pop	bx
		ret
move_cursor	endp


;----------------------------------------------------------------------
;	HEX2 -  Convert the AL register to hexidecimal digits.
;		The characters produced are stored at ES:DI.
;		All regs preserved.
;----------------------------------------------------------------------
hex2	proc	near
		push	ax
		push	bx
		push	cx

		mov	bx,ax
		std				;String ptr decrement
		add	di,1			;Point to end of string
		mov	cx,2
h10:
		mov	al,bl			;Want lower half
		and	al,0fh			; of this byte
		add	al,90h			;Convert AL to ASCII
		daa
		adc	al,40h
		daa
		stosb				;Store at ES:DI
		shr	bx,1
		shr	bx,1
		shr	bx,1
		shr	bx,1
		loop	h10

		pop	cx
		pop	bx
		pop	ax
		inc	di
		cld
		ret
hex2	endp


;-----------------------------------------------------------------------------
; Erase the 25th line of the screen and position the cursor on that line
;-----------------------------------------------------------------------------
clear_25	proc	near
		push	ax
		push	dx
		push	cx
		push	bx

		mov	ah,02		; Set the Cursor Position
		mov	dh,24		; Go to the 25th line
		mov	dl,0h		; column number 1.
		int	VIDEO

		mov	ah,09h		; Clear the line ...
		mov	al,20h		; using spaces
		mov	bh,[display_page]
		mov	bl,07h		; Change the attributes to white
		mov	cx,80		; Write 80 characters
		int	VIDEO

		pop	bx
		pop	cx
		pop	dx
		pop	ax
		ret
clear_25	endp


;-----------------------------------------------------------------------------
;  Pause so the user can press a key ... any key ...
;-----------------------------------------------------------------------------
pause	proc	near
	mov	si, offset pause_msg
	call	wr_string

top_pause:
	mov	ah,01h			; Wait for a key to be pressed
	int	KEYBOARD
	jz	top_pause
	mov	ah,00h			; Eat the keystroke
	int	KEYBOARD	

;	cmp	al,0dh			; Only continue with the [RETURN] key
;	jne	top_pause

	ret
pause	endp


;-----------------------------------------------------------------------------
; Display the Error Code and Message
;-----------------------------------------------------------------------------
display_errmsg	proc	near
	call	clear_25
	call	ring_bell
	mov	di,offset errcode	; Display the error code
	call	hex2
	lea	si, errmsg
	call	wr_string
	cmp	al,0fh			; Is the error off the scale ??
	jle	table
	lea	si,e_unknown		; Yes, so display "unknown" message
	jmp	short show_error

table:
	xor	bh,bh			; Build offset into error table
	mov	bl,al			; Can't use AX as a memory index ptr
	dec	bx			; Table is base 0, so reduce the index
	shl	bx,1			; Make offset into table (*2)
	mov	si,err_table[bx]

show_error:
	call	wr_string		; Now display the error
	call	pause
	ret
display_errmsg	endp


;-----------------------------------------------------------------------------
; Ring the Bell to indicate that an error occured - we create a special tone
;               so that the user knows it is NPRINT that did it.
;-----------------------------------------------------------------------------
ring_bell	proc	near
		push	ax
		push	cx
		push	dx

		in	al,61h		; Get port data
		push	ax		;   and save it
		cli			; Clear Interrupts

		mov	dx,0ch		; Length of bell tone
		mov	[tone],500h	; Frequency
		call	speaker

		mov	dx,0ch		; Length of bell tone
		mov	[tone],1000h	; Frequency
		call	speaker

		pop	ax		; Reset
		out	61h,al		;   port data
		sti			; Reset Interrupts

		pop	dx
		pop	cx
		pop	ax
		ret
ring_bell	endp

		
;-----------------------------------------------------------------------------
; Activate the speaker for a specified time and tone.
;-----------------------------------------------------------------------------
speaker		proc	near
BELL30:
		and	al,11111100B	; Set bits 0 & 1 off
		out	61h,al		; Transmit to speaker
		mov	cx,[tone]	; Set the length
BELL40:
		loop	BELL40		; Time Delay
		or	al,00000010B	; Set bit 1 on
		out	61h,al		; Transmit to speaker
		mov	cx,[tone]	; Set length
BELL50:
		loop	BELL50		; Time Delay
		dec	dx		; Reduce Duration
		jnz	BELL30		; Continue ?
		ret			; Nope, we are all done.
speaker		endp


filename	db	61 DUP (?)	; Storage for user inputted filename
screenbuf	db	80*2 DUP (?)	; Storage for screen line we clear
lastbyte = $


; ---------------------------------------------------------------------------
;  Main routine to install NPRINT as a TSR
; ---------------------------------------------------------------------------

install	proc	near
	assume	cs:cseg,ds:cseg,es:cseg,ss:cseg

	lea	dx,copyright
	mov	ah,09h		; Print a copyright message
	int	DOS

	mov	ah,01h		; PRINT Function
	mov	al,00h		; Print Installation Check Subfunction
	int	MULTIPLEX	; Multiplex Service Interrupt
	cmp	al,0FFh
	je	ok2inst		; If we get FFh returned, then PRINT is there.
	lea	dx,noprint
	mov	ah,09h		; Print a failing message
	int	DOS
	mov	ax,4c01h	; Terminate with error code 1
	int	DOS

ok2inst:
IFDEF DEBUG
	call	main
ENDIF

	mov	ah,35h		; Determine Interrupt vector for PrintScreen
	mov	al,05h
	int	DOS
	mov	word ptr old_int_5[0],bx	;offset
	mov	word ptr old_int_5[2],es	;segment

	mov	ah,35h		; Determine Interrupt vector for DOS call
	mov	al,28h
	int	DOS
	mov	word ptr old_int_28[0],bx	;offset
	mov	word ptr old_int_28[2],es	;segment

	lea	si,copyright	; Lets see if we are already installed.
	mov	di,bx		; Put the offset into the DI register
	sub	di,datasize	; Position ourselves to the start of the data
	mov	cx,6		; Compare 6 bytes (NPRINT)
	repe	cmpsb		; Repeat as long as they match
	jne	not_installed	; If it doesn't match, then we aren't installed
	lea	dx,inst_err	; OOPS, they match.  Tell the user about it.
	mov	ah,09h
	int	DOS
	mov	ax,4c02h	; Return to DOS with errorlevel 2
	int	DOS

not_installed:
	mov	ah,34h				; Locate the Critical Sec. Flag
	int	21h
	mov	word ptr CritSectFlag[0],bx	;offset
	mov	word ptr CritSectFlag[2],es	;segment

	mov	ah,09h		; Tell the user that we are installing
	lea	dx,inst_msg
	int	DOS

	mov	ah,25h		; Set Interrupt Vector for Print Screen
	mov	al,05h
	lea	dx,int5
	int	DOS

	mov	ah,25h		; Set Interrupt Vector for DOS Check
	mov	al,28h
	lea	dx,int28
	int	DOS

	mov	dx,code_pars	; Tell DOS how much memory we require
	mov	ax,3100h	;   and become a TSR
	int	DOS

install	endp

noprint		db	'DOS PRINT.COM is not installed.',13,10,'$'
inst_err	db	'NPRINT already installed.',7,13,10,'$'
inst_msg	db	'NPRINT installed successfully.',13,10,'$'

code_size = (offset lastbyte - offset entpt)
code_pars = (code_size / 16) + 32

cseg	ends
	end	entpt
SHAR_EOF
if test 17331 -ne "`wc -c < 'nprint.asm'`"
then
	echo shar: error transmitting "'nprint.asm'" '(should have been 17331 characters)'
fi
fi # end of overwriting check
if test -f 'nprint.doc'
then
	echo shar: will not over-write existing file "'nprint.doc'"
else
cat << \SHAR_EOF > 'nprint.doc'
			   NPRINT v1.0

		 (c) 1990 by Christopher D. Orr


OVERVIEW:

   NPRINT is a TSR designed to prompt the user for a filename and
pass that file off to DOS's PRINT.COM.  If no filename is
specified, NPRINT will attempt a print screen.  If no printer is
detected (or a print error occurs), NPRINT will inform the user
and abort the print screen.  If a print screen is successful,
NPRINT will send a formfeed to the printer.



FUNCTIONAL DESCRIPTION:

  *  NPRINT will only activate in modes 3 or 7.
  *  The 25th text line is saved.
  *  The user is prompted to enter a filename on the 25th text line.
  *  A request is made to PRINT to print the file.
  *  Errors returned by PRINT are displayed to the user.
  *  The 25th text line is restored.

  *  A blank filename will cause a print screen to occur
  *  NPRINT checks to see if the printer is available
  *  If the printer is not available, NPRINT will not attempt a print screen
  *  After a successful print screen, NPRINT will send a formfeed to the printer



ACTIVATION:
  NPRINT is activated by pressing the [PrtScr] key.
SHAR_EOF
if test 1086 -ne "`wc -c < 'nprint.doc'`"
then
	echo shar: error transmitting "'nprint.doc'" '(should have been 1086 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0
-- 
Christopher D. Orr                  | WISE OLD SAYING:
UUCP: {edsews,lehi3b15}!lxn!chris   |   Subtlety is the art of saying what you
 or   chris@lxn.chi.il.us           |   think and getting out of the way
 or   chris%lxn@clout.chi.il.us     |   before it is understood.