comment ^

  MouseClip - mouse-based copy and paste in DOS text mode.
  Copyright 2002 Jason Hood. Freeware.

  Originally PC-Mouse by Jrgen G. Weber.


  MAKE: tasm/m mousclip
	tlink mousclip


  Acknowledgements: Jrgen G. Weber for writing a good program to begin with.
		    Douglas Boling's DOSCLIP for instance data setup.
		    Grant B. Gustafson's xPC-Mouse for the idea of cursor
		     movement, using class pairs and the usage display.
		    Sverre H. Huseby who gave me the source to his MouseBuf
		     program so I could see how he took over the mouse from
		     other programs (maybe next version).

 ------------------------------------------------------------------------------
  PC-Mouse history:

VERSIONS: 1.0	  First public release
	  1.1	  if you specify option /T PCMOUSE's buffer is stuffed
		  into the keyboard buffer at every timer tick, too.
	  1.2	  Un-install Option /U
		  8088 Version
	  1.3	  Support of extended text modes
		  Option /Mnnn to use decimally given marker byte nnn
	  1.4	  Option /Q for quiet start up
		  Martin Buck found and eliminated some screen selection
		  bugs
	  1.5	  Option /N so that int 21h is not patched
		  But then if a program does not restore the previous
		  mouse handler, pcmouse first must be reactivated with
		  pcmouse /R
		  pcmouse does a mouse hardware reset after de-install now
	  1.5a	  Grant B. Gustafson found out that pspadr was overwritten
		  by buffer, so uninstall wouldn't work correctly

	  1.6	  Jason Hood, 23 to 26 December, 1996.
		  changed [?s:??] references to ?s:[??]
		  corrected some typos/spelling errors/English
		  removed the "stream" select and used block select
		  ctrl-left click will select by lines
		  ctrl-left double-click will select a "paragraph"
		  reformatted
		  changed movz macro

	  1.61	  Jason Hood, 2 January, 1997.
		  corrected stack problems with new_21h
		  modified rd_from_scr to suit above

	  1.62	  Jason Hood, 12 February, 1997.
		  saved AX after the EXEC call in new_21h

	  1.63	  Jason Hood, 13 to 16 March, 1997.
		  did the EXEC patch a little differently.
		  changed show_ and hide_mouse to procedures.
		  added switcher instance data support.
		  removed xor_vert_line, modified xor_block accordingly.

	  1.64	  Jason Hood, 10 to 17 June, 1998.
		  size optimizations - LOTS of changes, including:
		   using ES as videoseg and assuming DS as code;
		   redoing show_ and hide_mouse as macros (sans AX save);
		   removing xor_horiz_line;
		   removing unnecessary pushes and pops;
		   modifying the letter test for select_word;
		   rearranging some variables;
		   coordinates use low byte for x, high byte for y;
		   incorporating xor_scr into scr_select.
		  corrected blank first line bug;
		  set TRUE to 1, FALSE to 0;
		  added some more TIMER_TOO conditionals;
		  changed the MCB name;
		  shift-left click is a stream select;
		  shift-left double-click selects a (wrapped) line;
		  shift+ctrl-left double-click selects a paragraph, trimming
		   the leading and trailing space;
		  changed the default mask (it was 01010000);
		  added segregs to the x{push,pop}a macros;
		  alt-left click moves screen cursor to mouse cursor (by using
		   the arrow keys);
		  replaced charsave with keysave due to arrow keys;
		  right clicking will also remove the selection;
		  shift-right click to use space instead of enter (renamed
		   ctrl_rt_clck_flg to rt_clck_flg);
		  beep if the selection fills the buffer.

	  1.65	  Jason Hood, 27 February, 2000.
		  disable EXEC patch when Windows starts, add "pcmouse /w"
		   in StartUp group to re-activate.

 ------------------------------------------------------------------------------

	  1.00	  20 March to 8 April, 2000.
		  custom buffer size (/B<size>);
		  Windows clipboard support (alt-left click while selected to
		   copy, alt-right click to paste);
		  removed compression and used CR/LF to end lines, since this
		   is required by the clipboard;
		  three custom double-click selections by using shift+, ctrl+,
		   shift+ctrl+ with alt-left click. Corresponding command line
		   options (/D[123][=def'n], /D=filename);
		  changed installed test (and MOUSE_FN/IDENTCODE numbers);
		  modified most of the non-resident code;
		  corrected bugs with line/paragraph select when columns > 127
		   (still assumes < 255, rows < 128);
		  changed colsPline to a byte, test for 0 rows (for non-EGA,
		   but I don't know if it's zero, or just garbage);
		  install test returns version number in CX;
		  removed TIMER_TOO and /T since the clipboard needs it anyway;
		  removed all the XT code;
		  combined rt_clck_flg and mark_flag into click_flg;
		  removed safe_flag, test old_21h directly;
		  allow /N to toggle EXEC patch, if installed;
		  removed killed_flg, modify code directly.

	  1.10	  12 to 21 September, 2002.
		  explicitly set scancode 0 when pasting characters (the enter
		   scancode was not being reset);
		  removed blanking of control characters, except for NUL;
		  plugged potential buffer overflow when adding CRLF;
		  use the NUL terminator instead of buf_end_poi (which happens
		   to correct a bug pasting from the clipboard);
		  use right button release to paste, to prevent possible
		   problems with programs that recognise the modifier flags;
		  test for CRLF (not just CR, assuming a subsequent LF);
		  added self-load high capability (and EXEC_NEST equate); use
		   /L option to leave it low;
		  modified the timing (for double-click) to allow a left click
		   & release to move the cursor;
		  due to above, alt-left click always copies to clipboard;
		  some more optimisations, function reordering;
		  fixed potential pasting bug when using multiple DOS-boxes;
		  removed the API (don't return BX, variables have moved).
^

PVERSION	equ	<'1.10'>
PDATE		equ	<'21 September, 2002'>
NVERSION	equ	110h

TRUE		equ	1
FALSE		equ	0

		.286
		locals			; makes the local @@Label possible

MOUSE_FN	equ	81h		; new function of int 16h that tells if
					;  the program is already installed
IDENTCODE	equ	4d43h		; "MC"


; Maximum and default buffer sizes.
MAX_BUFLEN	equ	160 * 80	; The biggest screen possible, I think
MAX_BUFLEN_STR	equ	<'12800'>       ; String for the error message
DEFAULT_BUFLEN	equ	(80+2) * 25 / 2	; Half a standard screen (with CRLF)
MAX_PAIRS	equ	30		; 30 pairs should be sufficient
EXEC_NEST	equ	12 * 10 	; 12 nested programs seems enough

; XOR mask to display the selected screen area
DEFAULT_MASK	equ	01010101b	; Invert the red and blue components
MONO_MASK	equ	01110111b	; /M without a number
MONO_MASK_STR	equ	<'119'>         ; String for the help message

; Masks for mouse events
M_MOVED 	equ	00001b
M_LT_PRESSED	equ	00010b
M_LT_RELEASD	equ	00100b
M_RT_PRESSED	equ	01000b
M_RT_RELEASD	equ	10000b

; Scan codes for the arrow keys
LEFT_ARROW	equ	4bh
RIGHT_ARROW	equ	4dh
UP_ARROW	equ	48h
DOWN_ARROW	equ	50h

; Keyboard flags
SHIFT		equ	3
CTRL		equ	4
ALT		equ	8

; Masks to determine the selection and insertion (synonyms of above)
LINES		equ	4
STREAM		equ	3

; Time between clicks to register as a double-click
DCLCK_SPEED	equ	9		; about half a second

; Number of ticks to retry opening the clipboard
CLIP_RETRY	equ	36		; about two seconds


; the following macros make the listing more readable

movz macro reg,val1,val2
local is_zero
ifnb <val1>
  ifnb <val2>
	mov	reg,val1		; reg = ZR ? val1 : val2
	jz	is_zero
	mov	reg,val2
  else
	jnz	is_zero 		; movz reg,val == if ZR mov reg,val
	mov	reg,val1
  endif
else
	jz	is_zero 		; movz reg,,val == if NZ mov reg,val
	mov	reg,val2
endif					; movz reg == ??
is_zero:
endm


min macro reg1,reg2			; set reg1 to the smaller of the two,
local already_min			; reg2 to the larger (unsigned values)
	cmp	reg1,reg2
	jbe	already_min
	xchg	reg1,reg2
already_min:
endm


pushr macro regs			; eg: pushr <bx,ax,cx>
local reg
  irp reg,<regs>
	push	reg
  endm
endm

popr  macro regs			; eg: popr  <cx,ax,bx>
local reg
  irp reg,<regs>
	pop	reg
  endm
endm


xpusha macro segregs
	pushr	<segregs>
	pusha
endm

xpopa macro segregs
	popa
	popr	<segregs>
endm


; Save a few bytes by using indexed addressing, when BX is 40h for the
; BIOS data segment. eg: instead of "mov click_flg,ah", use
; "mov [click_flg-bxo],ah". ofs0 is necessary to force byte displacement.
bxo		equ	<ofs0+bx-40h>


		code	segment
		assume	cs:code,ds:code


ofs0		=	$


classes 	dw	offset class_1	; These must be first! (so array index
		dw	offset class_2	;  is the same as offset:
		dw	offset class_3	;  classes[i] == i*2)


StartupInfo	=	$
 sisVersion	dw	3		; Switcher structure ID
 sisNextDev	dd	0		; Ptr to prev startup structure
 sisVirtDevFile dd	0		; Ptr to name of opt dev drvr
 sisReferenceData dd	0		; Data for Win dev drivr
 sisInstData	dd	0		; Ptr to instance mem list

DataBlockPtr1	dw	80h,0		; Ptr to instance data (PSP cmdline)
DataBlockSize1	dw	EXEC_NEST	; Size of instance data
DataBlockPtr2	dw	DataBlock2Offset,0
DataBlockSize2	dw	DataBlock2Size
		dd	0		; Ptr to next block = 0 to
		dw	0		;  terminate list


end_buffer	dw	offset rd_buffer + DEFAULT_BUFLEN


; Character class pairs for the double-click. The pairs must be low-high,
; but the ordering doesn't matter. Character zero should never need to be
; selected, so a single zero is used to terminate the list.


class_1 	db	'a', 'z'        ; Words and numbers
		db	'A', 'Z'
		db	'0', '9'
		db	'_', '_'
		db	080h, 0a5h	; European special characters
		db	0e0h, 0ebh	; Greek letters
		db	0

class_2 	db	'a', 'z'        ; Filenames and URLs
		db	'@', 'Z'
		db	'+', ':'        ; includes -,./ and the digits
		db	'\', '\'
		db	'_', '_'
		db	'#', '#'
		db	'~', '~'
		db	080h, 0a5h	; European special characters
		db	0e0h, 0ebh	; Greek letters
		db	0

class_3 	db	33, 255 	; Between spaces
		db	0

		; Make room for the remaining pairs (+3 for terminators)
		db	MAX_PAIRS * 2 + 3 - ($ - offset class_1) dup (0)


; Instance data
DataBlock2Offset = $

buffer_valid	db	FALSE		; becomes true after right click
buf_poi 	dw	offset rd_buffer ; pointer to buffer (read from there)
keysave 	dw	0		; if keyboard buffer full
select_reset	label	word
select_flag	db	-1		; there is an area marked on screen
left_press_flg	db	FALSE		; left button is pressed
pressed_coord	label	word		; screen co-ordinate at mouse click
pressed_coord_x db	(?)		; low byte = x, high byte = y
pressed_coord_y db	(?)
old_coord	label	word		; where the mouse was
old_coord_x	db	(?)
old_coord_y	db	(?)
arrow_len	label	word		; number of arrow keys to stuff
arrow_len_x	db	0
arrow_len_y	db	0
arrow_key	label	word		; the arrow keys to stuff
arrow_key_x	db	(?)
arrow_key_y	db	(?)
scr_mark_mask	db	DEFAULT_MASK
mouse_on_flg	db	FALSE		; mouse is on
click_flg 	db	0		; ctrl- and/or shift-click?
in10_flg	db	0		; is incremented at each call of int 10h
in08_flg	db	-1		; zero if inside int 08h
colsPline	db	(?)		; characters per line on screen
colsPlineby2	dw	(?)		; 2 * above
lastcol 	db	(?)		; value of last column (colsPline-1)
rows		db	(?)		; number of rows-1 (for paragraph)
videoseg	dw	0b800h		; segment of screen memory
videooffs	dw	(?)		; offset of actual video page
retrace_port	dw	3dah		; port to read for retrace
timer_reset	label	word
lclcktime	db	DCLCK_SPEED	; time to register a double-click
timing		db	-1		; which event is being timed
bxds_save	label	dword
bx_save 	dw	(?)		; Save BX & DS for the int 21h patch
ds_save 	dw	(?)
exec_stack	label	dword
stack_ptr	dw	100h		; Use PSP cmdline for EXEC patch stack
pspadr		dw	(?)		; pointer to MouseClip seg in memory
clip_wanted	dw	(?)		; Offset of function to retry
clip_counter	db	CLIP_RETRY	; Number of times to retry the clipboard

DataBlock2Size = $ - offset DataBlock2Offset


show_mouse macro al0			; if present, AL is assumed to be zero
ifb <al0>
	mov	ax,1
else
	inc	ax
	cbw
endif
	int	33h
endm

hide_mouse macro al1			; if present, AL is assumed to be one
ifb <al1>
	mov	ax,2
else
	inc	ax
	cbw
endif
	int	33h
endm


; Use teletype to make a sound.

beep proc
	mov	ax,0e07h
	pushf
	call	dword ptr old_10h
	ret
beep endp


; Mouse co-ordinates to screen co-ordinates.
;  in: cx = mouse x,  dx = mouse y
; out: cl = screen x, ch = screen y

mxy2sxy proc
	shr	cx,3
	cmp	colsPline,40
	jne	@@no_40
	shr	cx,1			; divide by 16 for 40 columns per line
@@no_40:
	shr	dx,3
	mov	ch,dl
	ret
mxy2sxy endp


; Calculate video offset from x and y.
; In:  cl = x, ch = y
; Out: si = (y * ColsPerLine + x) * 2 + <Page Offset>

xy2offs proc
	pushr	<ax,cx>
	sub	ax,ax
	xchg	ch,al			; ax = y, cx = x
	mul	colsPline
	add	ax,cx
	add	ax,ax			; 2 bytes per character
	add	ax,videooffs
	xchg	si,ax
	popr	<cx,ax>
	ret
xy2offs endp


; Select single word chosen by double-click.
; In:  cx = dx = pressed coordinate
;      al = click_flg
;      es = video segment
;      (bx = 40h)
; Out: cx and dx new coordinates
;      CY for valid selection
;      NC for invalid selection

select_word proc
	call	xy2offs
	sub	ch,ch			; Use CX for the coordinate
	push	si
	std
@@go_left:				; search for word begin
	call	@@tst_let
	jc	@@found_left
	dec	cx
	jns	@@go_left		; stop at edge of screen
@@found_left:
	inc	cx
	pop	si
	cld
	cmp	cl,dl			; double-clicking a blank
	ja	@@exit			;  will clear carry
@@go_right:				; search for word end
	call	@@tst_let
	jc	@@found_right
	inc	dx
	cmp	lastcol,dl		; Will set carry for valid selection
	jae	@@go_right
@@found_right:
	dec	dx
@@exit:
	mov	ch,dh			; Restore the line
	ret


; Determine if a character should be selected.
; In:  ES:SI points to character
; Out: NC to select it
;      CY to not
;      ES:SI pointing to next character

@@tst_let:
	seges
	lodsw
	mov	di,ds:[2] ; classes[1]
current_class	equ byte ptr $-2

@@tst_loop:
	mov	bx,[di]
	test	bl,bl			; (TEST clears carry)
	jz	@@tst_exit		; Ran out of pairs
	inc	di
	inc	di
	cmp	al,bl
	jb	@@tst_loop
	cmp	al,bh
	ja	@@tst_loop
	stc				; BL <= AL <= BH
@@tst_exit:
	cmc
	ret

select_word endp


; Select a "paragraph" depending on the flags:
;   LINES:	a region between blank lines
;   STREAM:	one line that may wrap at the right edge
; Selecting stream will trim the leading and trailing spaces.
; Assumes screen height is less than 128 lines.
;
; In & Out: same as select_word

select_para proc
	sub	cl,cl
	mov	dl,[lastcol-bxo]
	mov	[pressed_coord_x-bxo],dl ; Use these variables to keep track
	mov	[old_coord_x-bxo],cl	 ;  of the overall block size
	call	xy2offs
	mov	bx,[colsPlineby2-bxo]
	test	al,LINES
	jnz	@@paragraph
	call	@@blank
	jnc	@@exit
	mov	di,pressed_coord	; Remember the start column
@@stream_down_loop:
	cmp	old_coord_x,dl		; If the last non-space is on the
	jne	@@stream_found		;  edge of the screen,
	cmp	dh,rows 		;  it's not the last row
	je	@@stream_found
	cmp	byte ptr es:[si],' '    ;  and the first column of the next
	je	@@stream_found		;  line is not a space
	inc	dh			;  then the line wraps.
	mov	old_coord_x,cl		; Reset the last non-space column
	call	@@blank
	jmp	short @@stream_down_loop
@@stream_found:
	mov	pressed_coord,di
	jmp	short @@found_stream

@@paragraph:
	push	si
@@down_loop:
	call	@@blank
	jnc	@@found_end
	inc	dh
	cmp	dh,rows
	jbe	@@down_loop
@@found_end:
	dec	dh
	neg	bx			; Moving upwards
	pop	si
	cmp	ch,dh			; Double-clicking a blank line
	ja	@@exit			;  will clear carry
@@up_loop:
	call	@@blank
	jnc	@@found_start
	dec	ch			; (If screen height exceeds 127 lines
	jns	@@up_loop		;  have to use cmp ch,-1; jne)
@@found_start:
	inc	ch
	mov	al,LINES
	xchg	click_flg,al
	test	al,STREAM
	jz	@@full_line
@@found_stream:
	mov	cl,pressed_coord_x	; Remove the leading and
	mov	dl,old_coord_x		;  trailing spaces
@@full_line:
	stc
@@exit:
	ret


; Test if a line is blank and determine the first/last non-space column.
; In:  es:si points to the start of the line
;      dl    contains the last column
;      bx    contains line offset adjustment
; Out: NC for blank, CY for non-blank
;      si points to next line

@@blank:
	pushr	<si,cx,dx>
	sub	dh,dh
	call	trim_trail
	jnc	@@blank_exit
	cmp	dl,old_coord_x		; The one closest to the edge will
	jbe	@@find_start		;  be the end of the block
	mov	old_coord_x,dl
@@find_start:
	dec	cx			; Prime for the loop
@@start_loop:
	inc	cx
	seges
	lodsw
	cmp	al,' '
	je	@@start_loop
@@start_done:
	cmp	cl,pressed_coord_x	; The one closest to the edge will
	jae	@@not_edge		;  be the start of the block
	mov	pressed_coord_x,cl
@@not_edge:
	stc
@@blank_exit:
	popr	<dx,cx,si>
	lahf
	add	si,bx
	sahf
	ret

select_para endp


; Remove the trailing space from a line (NUL counts as a space).
; In:  es:si points to the line
;      dx has the length of the line, less one (ie. the last column)
; Out: dx has the new length, less one
;      NC if the line is blank

trim_trail proc
	push	si
	add	si,dx
	add	si,dx			; SI -> last character (and NC)
	inc	dx			; DX is line length
	std
@@blank:				; Find the end of the line
	dec	dx
	js	@@exit			; Blank line
	seges
	lodsw
	or	al,' '                  ; Space is one bit, so I can test
	cmp	al,' '                  ;  for zero or space like this
	je	@@blank 		; (ZR is also NC)
	stc
@@exit:
	cld
	pop	si
	ret
trim_trail endp


; Convert the corner/stream coordinates into a
; screen offset and width and height counts.
; In:  [pressed_coord] has one corner
;      [old_coord] has the opposite corner
; Out: si = offset of top-left corner
;      dx = number of columns (less one)
;      cx = number of rows (less one)

corners2counts proc
	mov	cx,pressed_coord
	mov	ax,old_coord
	test	click_flg,STREAM
	jnz	@@stream
	xchg	dx,ax
	min	cl,dl			; CX -> top-left
	min	ch,dh			; DX -> bottom-right
	call	xy2offs
	sub	dx,cx			; Subtract columns & rows in one op.
	sub	cx,cx
	xchg	cl,dh
	ret

@@stream:
	min	ax,cx			; ax -> start, cx -> end
	call	xy2offs 		; end offset
	push	si
	xchg	cx,ax
	call	xy2offs 		; start offset
	pop	dx
	sub	dx,si
	shr	dx,1			; Halve it for the count
	sub	cx,cx			; A stream is only one line
	ret
corners2counts endp


; Xor a block onto the screen.
; In:  [pressed_coord] has one corner
;      [old_coord] has the opposite corner
; Out: ES contains videoseg

xor_block proc
	xpusha
	hide_mouse
	call	corners2counts
	inc	si			; -> attribute
	mov	al,scr_mark_mask
	mov	es,videoseg
@@xor_row:
	pushr	<si,dx>
@@xor_col:
	xor	es:[si],al
	inc	si
	inc	si
	dec	dx
	jns	@@xor_col
	popr	<dx,si>
	add	si,colsPlineby2
	dec	cx
	jns	@@xor_row
	show_mouse
	xpopa
	ret
xor_block endp


; Select screen area that was passed during mouse movement.
; In: cx,dx = mouse x,y
; (BX is 40h from the mouse handler.)

scr_select proc
	call	mxy2sxy
	test	byte ptr [click_flg-bxo],LINES	; Force the last column if
	movz	cl,,[lastcol-bxo]		;  selecting by line
	cmp	cx,[old_coord-bxo]
	je	@@exit			; was moved too little to change

	mov	dx,[retrace_port-bxo]
@@in_retrace:
	in	al,dx
	test	al,8
	jnz	@@in_retrace
@@wait_retrace:
	in	al,dx
	test	al,8
	jz	@@wait_retrace

	call	start_selection

	call	xor_block		; Remove the old mark
	mov	[old_coord-bxo],cx
do_xor: 				; Entry point for scr_un_select and
					;  start_selection
	call	xor_block		; Display the new one
@@exit:
	ret
scr_select endp


; Deselect screen area that was passed during mouse movement.

scr_un_select proc near
	mov	ax,00ffh		; select_flag = -1
	xchg	select_reset,ax 	; left_press_flg = FALSE
	test	al,al			; AL = select_flag
	jns	do_xor
	ret
scr_un_select endp


; Display the initial selection, if not already displayed.

start_selection proc
	inc	select_flag
	jz	do_xor
	;dec	select_flag		; Prevent movement from overflowing
	ret
start_selection endp


; Remove a selection from the screen and hide the mouse, if visible.

remove_selection proc
	pushr	<ax,es>
	mov	al,FALSE
	xchg	mouse_on_flg,al
	test	al,al
	jz	@@no_hide
	hide_mouse 1			; AL is one from M_MOVED
@@no_hide:
	call	scr_un_select
	popr	<es,ax>
	ret
remove_selection endp


; After left release read selected characters into buffer.
; (BX is 40h from the mouse handler.)

rd_from_scr proc
	call	corners2counts
	mov	di,offset rd_buffer
	mov	es,[videoseg-bxo]

@@rd_row:
	push	dx
	call	trim_trail
	jnc	@@next_row
	push	si
@@copy_line:
	cmp	di,[end_buffer-bxo]
	jae	@@buffer_full
	seges
	lodsw
	test	al,al
	jnz	@@not_nul
	mov	al,' '
@@not_nul:
	mov	[di],al
	inc	di
	dec	dx
	jns	@@copy_line
	pop	si
@@next_row:
	pop	dx
	add	si,[colsPlineby2-bxo]
	dec	cx
	js	@@rd_exit
	inc	di
	cmp	di,[end_buffer-bxo]	; Make sure there's room for CRLF
	jae	@@buffer_full1
	mov	word ptr [di-1],0a0dh	; CRLF to mark the end of the line
	inc	di
	jmp	short @@rd_row

@@buffer_full:
	popr	<si,dx> 		; Tidy up the stack
@@buffer_full1:
	call	beep

@@rd_exit:
	mov	[di],bh 		; Terminating NUL
	ret
rd_from_scr endp


; Read characters from buffer
; EOLN is CRLF, ignore the LF (paste the Enter key, not the CR character).
; Test for arrow keys first
; Out: cx = character & scan code
;      CY if empty, cx undefined

rd_from_buf proc
	mov	ax,arrow_len
	test	ax,ax
	jnz	@@arrows
	cmp	buffer_valid,al
	jnz	@@buf_valid
@@buf_empty:
	stc
	ret

@@arrows:
	sub	cl,cl			; Character code is 0
	test	al,al
	jz	@@vertical
	mov	ch,arrow_key_x
	dec	ax
	jmp	short @@stuff_arrow
@@vertical:
	mov	ch,arrow_key_y
	dec	ah
@@stuff_arrow:
	mov	arrow_len,ax
	;jmp	short @@char_found	; The TEST will clear carry
	ret

@@buf_valid:
	mov	al,click_flg
	mov	bx,buf_poi
	mov	cx,[bx] 		; get next character
	test	cl,cl
	jnz	@@not_empty
	mov	buffer_valid,cl
	mov	buf_poi,offset rd_buffer ; pointer reset, to make readable again
	test	al,LINES		; append CR, too ?
	jz	@@buf_empty
@@enter:
	mov	cx,1c0dh		; scan and character code for ENTER
	;jmp	short @@char_found	; TEST will clear carry
	ret

@@not_empty:
	inc	buf_poi
	cmp	cx,0a0dh		; End-of-line?
	jne	@@char
	inc	buf_poi 		; Skip past the LF
	test	al,STREAM
	jz	@@enter
	mov	cl,' '
@@char:
	sub	ch,ch			; 0 scancode, same as Alt+keypad entry
@@char_found:				;  (also clears carry)
	;clc
	ret
rd_from_buf endp


; Move the screen cursor to the mouse cursor by using
; a series of arrow keypresses.
; In:  [pressed_coord] = mouse cursor
; Out: [arrow_len] & [arrow_key] updated
;      ES points to the BIOS segment

move_cursor proc
	mov	bx,40h
	mov	es,bx
	mov	bl,es:[bx+22h]		; currently active page
	add	bx,bx
	mov	dx,es:[50h+bx]		; screen cursor
	mov	cx,pressed_coord	; mouse cursor

	mov	ax,RIGHT_ARROW or (DOWN_ARROW shl 8)
	sub	cl,dl
	jns	@@vertical
	neg	cl
	mov	al,LEFT_ARROW
@@vertical:
	sub	ch,dh
	jns	@@store
	neg	ch
	mov	ah,UP_ARROW
@@store:
	mov	arrow_len,cx
	mov	arrow_key,ax
	;ret
	; fall through to stuff2kbb
move_cursor endp


; Write characters into keyboard buffer until it is full.

stuff2kbb proc				; stuff to keyboard buffer
	inc	in08_flg		; Don't let the timer interfere (cli
	sub	cx,cx			;  doesn't work)
	xchg	cx,keysave		; character left from last trial ?
	test	cx,cx			; 0 == nothing left
	jnz	@@after_rd		; save character from last trial
@@loop:
	call	rd_from_buf
	jc	@@exit			; nothing to store
@@after_rd:
	mov	ah,5
	pushf				; to simulate int
	call	dword ptr old_16h	; store to keyboard buffer
	test	al,al			; 0 == save was successful
	jz	@@loop
	mov	keysave,cx		; save for next trial
@@exit:
	dec	in08_flg
	ret
stuff2kbb endp


; Check the existence of the Windows clipboard and open it, if available.
; Out: NC if clipboard available
;	 ES:BX pointing to buffer
;      CY if clipboard unavailable (and beep if in Windows).

open_clipboard proc
	pop	ax			; Retrieve the return address to
	push	ax			;  determine the calling routine
if (((copy_to_clipboard - ofs0) and 255) ge 3) and\
   (((paste_from_clipboard - ofs0) and 255) ge 3)
	sub	al,3
else
	sub	ax,3
endif
	mov	clip_wanted,ax
	mov	ax,1701h		; Open clipboard
	int	2fh
	cmp	ax,1701h		; Not in Windows
	je	@@no_clip
	test	ax,ax
	jz	@@bad_clip		; Clipboard already opened
	;clc				; (TEST clears carry)
	push	cs
	pop	es
	mov	bx,offset rd_buffer	; ES:BX pointer to data
@@reset:
	mov	clip_counter,CLIP_RETRY
	ret

@@bad_clip:
	dec	clip_counter
	jnz	@@no_clip
	call	beep
	stc
	jmp	short @@reset
@@no_clip:
	stc
	ret
open_clipboard endp


; Copy the buffer to the Windows clipboard.

copy_to_clipboard proc
	call	open_clipboard		; ES:BX -> buffer (must be first!)
	jc	@@no_clip
	mov	ax,1702h		; Empty clipboard
	int	2fh
	mov	di,bx			; Find the length (including the NUL)
	mov	cx,-1
	sub	al,al
	repne	scasb
	not	cx
	sub	si,si			; SI:CX length
	mov	ax,1703h		; Copy data to clipboard
	mov	dx,7			; OEM text
	int	2fh
	;test	ax,ax			; Make sure it worked
	;jnz	@@ok
	;call	beep
@@ok:
	mov	ax,1708h		; Close clipboard
	int	2fh
@@no_clip:
	ret
copy_to_clipboard endp


; Copy the Windows clipboard into the internal buffer.

paste_from_clipboard proc
	call	open_clipboard		; Must be first!
	mov	buffer_valid,FALSE
	jc	@@no_clip
	mov	ax,1704h		; Get clipboard data size
	mov	dx,7			; OEM text
	int	2fh
	test	dx,dx			; DX:AX length
	jnz	@@bad_data		; Too big
	test	ax,ax
	jz	@@bad_data		; Empty
	add	ax,bx
	cmp	ax,end_buffer
	jae	@@bad_data		; Too big
	mov	ax,1705h		; Get data from clipboard
	mov	dl,7			; OEM text (DH already zero)
	int	2fh
	;test	ax,ax			; Make sure it worked
	;jz	@@bad_data
	inc	buffer_valid
	jmp	short @@ok
@@bad_data:
	call	beep
@@ok:
	mov	ax,1708h		; Close clipboard
	int	2fh
@@no_clip:
	ret
paste_from_clipboard endp


ms_handler proc far			; NOT the entry point

@@mode_test:
	cmp	ah,7			; AH = video mode
	je	@@mode_ok
	cmp	ah,13h			; rest of vga modes graphics
	jbe	@@goext

; assume it to be a text mode if it takes less
; than 4100h in video memory
	cmp	es:[bx+0dh],bl		; BX = 40h
	jbe	@@mode_ok
@@goext:
	ret


; The mouse handler is called by the mouse driver
; when one of the events defined in the mask occurs.
; If interrupt 10h is active, or there's no text mode,
; get out immediately.

ms_hndler label far
	push	cs
	pop	ds

	mov	bl,40h			; BIOS segment and base offset
	cmp	[in10_flg-bxo],bh	; was Int 10h interrupted ?
	jnz	@@goext 		; yes (BH is 0 from button status)

	mov	es,bx			; set to bios data area
	mov	ah,es:[bx+9h]		; screen mode
	cmp	ah,3
	ja	@@mode_test
@@mode_ok:
	cld				; Take no chances

	cmp	al,M_MOVED
	jne	@@not_moved
@@mouse_moved:
	xchg	[mouse_on_flg-bxo],al	; AL == M_MOVED == 1
	test	al,al
	jnz	@@is_on
	show_mouse 0			; switch on cursor after first movement
@@is_on:
	cmp	[left_press_flg-bxo],bl
	jne	@@goext 		; select if left button still pressed
	call	scr_select
	ret

@@not_moved:
	test	al,M_LT_RELEASD
	jnz	@@left_released
	test	al,M_LT_PRESSED
	jnz	@@left_pressed

	cmp	[left_press_flg-bxo],bh
	jnz	@@rt_exit		; both buttons simultaneously pressed

	test	al,M_RT_RELEASD
	jnz	@@rt_released
	;test	al,M_RT_PRESSED 	; The only event remaining
	;jz	@@rt_exit

@@rt_pressed:
	call	remove_selection
	mov	al,es:[17h]		; Keyboard flag
	mov	click_flg,al
@@rt_exit:
	ret

@@rt_released:
	mov	[buffer_valid-bxo],al	; AL is not zero from the mask
	test	byte ptr [click_flg-bxo],ALT
	jz	@@rt_no_alt
	call	paste_from_clipboard
@@rt_no_alt:
	call	stuff2kbb		; to end a possibly active int 16h,0
	ret

@@left_released:
	mov	[left_press_flg-bxo],bh
	cmp	[timing-bxo],bh
	js	@@lr_read		; already timed-out or using alt
	inc	byte ptr [timing-bxo]
@@lr_read:
	cmp	byte ptr [select_flag-bxo],-1	; (select_flag might become
	je	@@lr_out			;  > 127, so can't use bh)
	call	rd_from_scr
@@lr_out:
	ret

@@lt_alt:				; Alt+Left click
	and	al,SHIFT or CTRL
	jz	@@only_alt
	cmp	al,CTRL
	mov	al,2			; Shift      = 0 (CY)
	ja	@@shift 		; Ctrl	     = 1 (NC)
	sbb	al,1			; Shift+Ctrl = 2 (above)
@@shift:
	shl	al,1
	mov	current_class,al
	ret

@@only_alt:
	call	copy_to_clipboard
	ret

@@left_pressed:
	mov	al,es:[17h]		; Keyboard flag
	test	al,ALT
	jnz	@@lt_alt
	push	ax			; Keep for the selection style
	mov	ax,es:[4ah]		; get screen data after left click
	mov	colsPline,al
	dec	ax
	mov	lastcol,al
	inc	ax
	add	ax,ax
	mov	colsPlineby2,ax
	mov	ax,es:[4eh]
	mov	videooffs,ax
	mov	al,es:[63h]
	add	al,6
	mov	byte ptr retrace_port,al
	cmp	al,0bah 		; monochrome ?
	movz	al,0b0h,0b8h
	mov	byte ptr videoseg+1,al
	mov	al,es:[84h]
	cmp	al,0
	jg	@@rows_exists
	mov	al,24
@@rows_exists:
	mov	rows,al

	call	scr_un_select		; remove possible previous selection

	call	mxy2sxy
	mov	dx,cx

	pop	ax
	mov	click_flg,al
	test	al,LINES		; (Clears carry)
	jz	@@not_line
	sub	cl,cl			; (Clears carry)
	mov	dl,[lastcol-bxo]
@@not_line:
	inc	byte ptr [timing-bxo]
	jz	@@exit1 		; First click
; double-click
	mov	es,[videoseg-bxo]
	pushr	<ax,bx>
	test	al,LINES or STREAM
	jz	@@word
	call	select_para
	jmp	short @@done_dbl
@@word:
	call	select_word
@@done_dbl:
	popr	<bx,ax>
	jnc	@@exit			; Invalid selection
	;stc				; So movement won't alter the selection
@@exit1:
	mov	[pressed_coord-bxo],cx
	mov	[old_coord-bxo],dx
	lahf
	adc	[left_press_flg-bxo],bl
	sahf
	jc	@@start
	test	al,LINES or STREAM
	jz	@@exit			; The timer does the initial display
@@start:
	call	start_selection
@@exit:
	ret
ms_handler endp


; Patch for int 08h.

new_08h proc
	pushf
	db 9ah ; call far
old_08h dw 0,0

	xpusha	<ds,es>
	push	cs
	pop	ds
	inc	in08_flg
	jnz	@@out
	mov	al,timing
	test	al,al
	jns	@@timer
	cmp	clip_counter,CLIP_RETRY
	je	@@go_stuff
	call	[clip_wanted]
@@go_stuff:
	call	stuff2kbb		; write characters into keyboard buffer
@@out:
	dec	in08_flg
	xpopa	<es,ds>
	iret

@@timer:
	dec	lclcktime
	jnz	@@out
	cmp	al,1
	mov	ax,DCLCK_SPEED or 0ff00h
	mov	timer_reset,ax		; lclcktime = DCLCK_SPEED; timing = -1
	je	@@move
	ja	@@out
	call	start_selection
	jmp	short @@out
@@move:
	cmp	select_flag,ah
	jne	@@out			; something selected
	call	move_cursor
	jmp	short @@out
new_08h endp


; Patch for Interrupt 10h.

new_10h proc
	push	ds
	push	cs
	pop	ds
	call	remove_selection
	inc	in10_flg		; Flag for int 10h active
	pushf				; simulate int
	db 9ah	; call far
old_10h dw 0,0
	dec	in10_flg
	pop	ds
	iret
new_10h endp


; Patch for int 16h.

new_16h proc
	cmp	ah,MOUSE_FN		; hello, are you there ?
	je	@@new
	xpusha	ds
	push	cs
	pop	ds
	call	stuff2kbb		; write character into keyboard buffer
	xpopa	ds
	db 0eah ; jmp far
old_16h dw 0,0

@@new:
	mov	ax,IDENTCODE
	push	cs
	pop	es			; ES = resident segment
	;mov	bx,offset rd_buffer
	mov	cx,NVERSION
	iret
new_16h endp


	ASSUME	ds:NOTHING

; Install mouse handler.

inst_ms_handler proc far
	mov	ax,21h			; Software Reset, to put handler into
	int	33h			;  defined state
	push	cs
	pop	es
	mov	dx,offset ms_hndler
	mov	cx,(M_MOVED or M_LT_PRESSED or M_LT_RELEASD\
			    or M_RT_PRESSED or M_RT_RELEASD)
	mov	ax,0ch			; Set Interrupt Subroutine
	int	33h
	ret
inst_ms_handler endp


; Patch for EXEC Function.

new_21h proc
	cmp	ax,4b00h		; Load and Execute
toggle_21h label word
	je	@@new_exec
	db 0eah ; jmp far
old_21h dw 0,0

@@new_exec:

; activate mouse handler, as it is possible that a program
; that changed the mouse handler, started COMMAND.COM

	xpusha	<ds,es>
	sub	cx,cx			; only get old routine
	mov	ax,14h			; Swap Interrupt Subroutines
	int	33h
	lds	bx,exec_stack
	mov	ds:[bx-2],ss		; The stack gets corrupted by EXEC
	mov	ds:[bx-4],sp		;  so save it.
	add	word ptr ds:[bx-4],20	; Skip everything currently on the stack
	mov	ds:[bx-6],cx		; I'll save the old routine here as
	mov	ds:[bx-8],es		;  well, where here is the PSP command
	mov	ds:[bx-10],dx		;  line (128 bytes otherwise unused).
	sub	stack_ptr,10
	call	inst_ms_handler 	; set handler of MouseClip
	xpopa	<es,ds>

	pushf
	call	dword ptr old_21h

; iret after original int 21h leads to here
	mov	bx_save,bx
	mov	ds_save,ds
	lds	bx,exec_stack
	mov	ss,ds:[bx+8]
	mov	sp,ds:[bx+6]
	pushf
	xpusha	es
	les	dx,ds:[bx]		; Original mouse routine
	mov	cx,ds:[bx+4]		; Original mask
	add	stack_ptr,10

killed_exec label word
	nop				; Replaced with jmp short no_res_old
	nop				;  in de_inst routine
	mov	ax,21h			; Software Reset
	int	33h
	mov	ax,0ch			; Set old routine again
	int	33h
no_res_old:
	xpopa	es
	popf
	lds	bx,bxds_save
	retf	2			; ignore old flags as EXEC returns
					;  result status in flag register
new_21h endp


; Set up the instance data (int 2f patch).

new_2fh proc
	cmp	ax,1605h		; Windows launch
	je	@@no_exec
	cmp	ax,4b05h		; Switcher instance data
	je	@@instance
	db 0eah ; jmp far
old_2fh dd ?

@@no_exec:
	mov	toggle_21h,9090h	; Disable EXEC patch
@@instance:
	pushf
	call	[old_2fh]
	mov	word ptr sisNextDev,bx
	mov	word ptr sisNextDev+2,es
	push	cs			; ES:BX point to switcher struc
	pop	es
	mov	bx,offset StartupInfo
	iret
new_2fh endp


; Buffer for read characters, overwrites initialisation code.
rd_buffer	db	0


; ============================================================================


		ASSUME	ds:code

DE_INSTALL	equ	 2
REACTIVATE	equ	 3
WINDOWS 	equ	 4

options		db	 0
O_QUIET		equ	 1
O_REACTIVATE	equ	 2
O_WINDOWS	equ	 4
O_MARKER	equ	 8
O_DOUBLE	equ	16
O_EXEC		equ	32
O_LOW		equ	64

is_inst 	db	FALSE		; TRUE if installed
handler 	label	dword		; Pointer to resident mouse handler
mcofs		dw	offset inst_ms_handler
mcseg		dw	(?)		; Segment of installed version
handle		dw	(?)		; Handle of double-click file
exec_flag	dw	(?)		; Non-zero if int 21h was patched


output macro msg
	mov	ah,9
ifnb <msg>
	mov	dx,offset msg
endif
	int	21h
endm


get_opt proc
	mov	si,81h			; es:si => command line parameters
@@findslash:
	seges
	lodsb
	cmp	al,13
	jne	@@parm_next
@@parm_done_1:
	jmp	@@parm_done
@@parm_next:
	cmp	al,' '
	jbe	@@findslash
	cmp	al,'/'
	je	@@good_sep
	cmp	al,'-'
	je	@@good_sep
	mov	dx,offset no_opt_str
	jmp	@@parm_err
@@good_sep:
	seges
	lodsb
	cmp	al,'?'
	jne	@@to_upper
	mov	dx,offset help_str
@@parm_err_1:
	jmp	@@parm_err

@@to_upper:
	mov	unknown_opt,al
	and	al,not ('a'-'A')

	cmp	al,'H'
	mov	dx,offset usage
	je	@@parm_err_1

	cmp	al,'U'
	mov	ah,DE_INSTALL
	je	@@exit_1

	cmp	al,'W'
	mov	ah,WINDOWS
	jne	@@no_windows
	cmp	is_inst,TRUE
	je	@@exit_1
	or	options,O_WINDOWS
	jmp	@@findslash

@@exit_1:
	;jmp	@@exit
	ret

@@no_windows:

	cmp	al,'R'
	jne	@@no_reactivate
	or	options,O_REACTIVATE
	jmp	@@findslash
@@no_reactivate:

	cmp	al,'N'
	jne	@@no_safe
	or	options,O_EXEC
	jmp	@@findslash
@@no_safe:

	cmp	al,'Q'
	jne	@@no_quiet
	or	options,O_QUIET
	jmp	@@findslash
@@no_quiet:

	cmp	al,'M'
	jne	@@no_mark_byte
	call	process_mark
	jc	@@parm_err
	or	options,O_MARKER
	jmp	@@findslash
@@no_mark_byte:

	cmp	al,'B'
	jne	@@no_buf_size
	call	process_buffer
	jc	@@parm_err
	jmp	@@findslash
@@no_buf_size:

	cmp	al,'D'
	jne	@@no_double
	call	process_class
	jc	@@parm_err
	or	options,O_DOUBLE
	jmp	@@findslash
@@no_double:

	cmp	al,'L'
	jne	@@no_low
	or	options,O_LOW
	jmp	@@findslash
@@no_low:

	mov	dx,offset unknown_opt
@@parm_err:
	mov	ah,FALSE
	ret
@@parm_done:
	test	options,O_REACTIVATE
	jz	@@parm_okay
	cmp	is_inst,FALSE
	je	@@parm_okay
	mov	ah,REACTIVATE
	ret
@@parm_okay:
	mov	ah,TRUE
@@exit:
	ret
get_opt endp


; Convert a string to an unsigned decimal number (0-65535).
; In:  ES:SI pointer to string
; Out: NZ - string converted OK
;	 AX number
;	 ES:SI pointing to character that stopped conversion
;      ZF - no number found
;	 AX zero

parse_num proc
	pushr	<bx,cx,dx>
	mov	bx,10
	sub	ax,ax
	mov	ch,al
	push	si
@@addloop:
	mov	cl,es:[si]
	sub	cl,'0'
	jb	@@done
	cmp	cl,9
	ja	@@done
	mul	bx
	add	ax,cx
	inc	si
	jmp	short @@addloop
@@done:
	pop	bx
	cmp	si,bx
	popr	<dx,cx,bx>
	ret
parse_num endp


; Procedures to process options.
; In:  ES:SI pointer to character after option letter
; Out: NC - option processed okay
;	 ES:SI ready for next option
;      CY - option had errors
;	 DX pointer to error message

process_mark proc
	call	parse_num
	jnz	@@valid
	mov	al,MONO_MASK
@@valid:
	mov	dx,offset bad_marker
	test	ah,ah			; Greater than 255?
	jnz	@@exit			; (TEST clears carry)
	push	ds
	mov	ds,mcseg
	mov	scr_mark_mask,al
	pop	ds
	stc
@@exit:
	cmc
	ret
process_mark endp


process_buffer proc
	mov	dx,offset no_resize
	cmp	is_inst,FALSE
	je	@@okay
@@bad_exit:
	stc
	ret
@@okay:
	mov	dx,offset number_required
	call	parse_num
	jz	@@bad_exit
	mov	dx,offset bad_buffer
	cmp	ax,MAX_BUFLEN
	ja	@@bad_exit
	add	ax,offset rd_buffer
	mov	end_buffer,ax
	clc
	ret
process_buffer endp


process_class proc
	seges
	lodsb
	cmp	al,'?'
	mov	dx,offset class_help
	je	@@bad_exit
	cmp	al,'='
	je	@@file
	sub	al,'1'
	jl	@@no_class
	cmp	al,2
	ja	@@no_class
	sub	ah,ah
	cmp	byte ptr es:[si],'='
	je	@@make_class
	push	ds
	mov	ds,mcseg
	shl	al,1
	mov	current_class,al
	pop	ds
@@exit:
	clc
	ret
@@make_class:
	inc	si
	call	parse_pairs
	ret				; parse_pairs sets carry

@@no_class:
	mov	dx,offset bad_class
@@bad_exit:
	stc
	ret

@@file:
	mov	bx,offset file_buffer
	mov	dx,bx
@@file_loop:
	mov	al,es:[si]
	cmp	al,' '
	jbe	@@file_found
	inc	si
	mov	[bx],al
	inc	bx
	jmp	@@file_loop
@@file_found:
	mov	byte ptr [bx],0
	mov	ax,3d00h		; Open file for read
	int	21h
	jc	@@bad_file
	mov	handle,ax
	sub	ax,ax
	mov	cx,3
	pushr	<es,si>
	push	ds
	pop	es
@@read_loop:
	call	read_line
	mov	si,offset file_buffer
	cmp	byte ptr [si],' '
	jbe	@@read_next
	pushr	<ax,cx>
	call	parse_pairs
	popr	<cx,ax>
	jc	@@file_bad_exit
@@read_next:
	inc	ax
	loop	@@read_loop
	mov	ah,3eh			; Close file
	mov	bx,handle
	int	21h
	popr	<si,es>
	clc
	ret
@@file_bad_exit:
	popr	<si,es>
	jmp	@@bad_exit

@@bad_file:
	mov	dx,offset bad_file
	jmp	@@bad_exit
process_class endp


; Translate the string into character pairs.
; In:  ES:SI - string (eg. "lo-hi:lo,hi:char:hi-lo")
;      AX - class number (0, 1 or 2)
; Out: NC - parsed okay
;      CY - errors found
;	 DX points to message

parse_pairs proc
	mov	bx,ax
	sub	cx,cx			; Count bytes
	mov	di,offset pair_buffer
	dec	si
@@loop:
	inc	si
	call	parse_num
	jz	@@no_num
	test	ah,ah
	jnz	@@no_good
	test	al,al
	jz	@@no_good
	mov	dl,al
	cmp	byte ptr es:[si],':'
	je	@@only_one
	cmp	byte ptr es:[si],' '
	jbe	@@only_one
	inc	si			; Don't care what the separator is
	call	parse_num
	jz	@@no_num
	test	ah,ah
	jnz	@@no_good
	test	al,al
	jz	@@no_good
	min	dl,al
@@only_one:
	mov	[di],dl
	inc	di
	mov	[di],al
	inc	di
	inc	cx
	inc	cx
	cmp	byte ptr es:[si],':'
	jne	@@done
	cmp	cx,MAX_PAIRS * 2
	jb	@@loop
	mov	dx,offset too_many_pairs
	jmp	short @@bad_exit
@@done:
	test	cx,cx
	jz	@@no_num
	mov	byte ptr [di],0
	inc	cx
	jmp	short @@adjust
@@no_num:
	mov	dx,offset no_pairs
	jmp	short @@bad_exit
@@no_good:
	mov	dx,offset bad_pair
@@bad_exit:
	stc
	ret

@@adjust:
	pushr	<ds,es,si>
	push	ds
	pop	es
	mov	di,offset class_buffer
	cmp	bx,1
	mov	bx,offset class_ofs
	je	@@copy_one
	ja	@@copy_two
	call	@@copy_pairs
	mov	si,offset classes+2
	call	@@copy_class
	mov	si,offset classes+4
	call	@@copy_class
	jmp	short @@adjust_done

@@copy_one:
	mov	si,offset classes
	call	@@copy_class
	call	@@copy_pairs
	mov	si,offset classes+4
	call	@@copy_class
	jmp	short @@adjust_done

@@copy_two:
	mov	si,offset classes
	call	@@copy_class
	mov	si,offset classes+2
	call	@@copy_class
	call	@@copy_pairs

@@adjust_done:
	mov	si,offset class_buffer
	mov	cx,di
	sub	cx,si
	mov	di,offset class_1
	mov	es,mcseg
	rep	movsb
	mov	si,offset class_ofs
	mov	bx,offset classes
	mov	dx,offset class_buffer - offset class_1
	mov	cx,3
@@adjust_loop:
	mov	ax,[si]
	sub	ax,dx
	mov	es:[bx],ax
	inc	si
	inc	si
	inc	bx
	inc	bx
	loop	@@adjust_loop
	popr	<si,es,ds>
	clc
	ret

@@copy_pairs:
	mov	ax,di
	add	ax,cx
	cmp	ax,offset class_ofs
	ja	@@too_many
	mov	[bx],di
	inc	bx
	inc	bx
	mov	si,offset pair_buffer
	rep	movsb
	ret

@@copy_class:
	mov	[bx],di
	inc	bx
	inc	bx
	push	ds
	mov	ds,mcseg
	mov	si,[si]
@@copy:
	cmp	di,offset class_ofs
	je	@@bad_copy
	lodsb
	stosb
	test	al,al
	jnz	@@copy
	pop	ds
	ret

@@bad_copy:
	pop	ds
@@too_many:
	output	too_many_pairs
	mov	ax,4cffh
	int	21h

parse_pairs endp


; Simple way to read a line from file.

read_line proc
	xpusha	es
	mov	bx,offset file_buffer
	mov	byte ptr [bx],0
	mov	dx,bx
	mov	cx,258
	mov	bx,handle
	mov	ah,3fh
	int	21h
	jc	@@exit
	test	ax,ax
	jz	@@exit
	mov	cx,ax
	mov	di,offset file_buffer
	push	ds
	pop	es
	mov	al,10
	repne	scasb
	je	@@lf_found
	mov	[di],al 		; File didn't have a final CRLF,
	jmp	short @@exit		;  or the line was too long.
@@lf_found:
	mov	dx,-1			; Reset the file pointer ready for
	xchg	cx,dx			;  the next line
	neg	dx
	mov	ax,4201h		; SEEK_CUR
	int	21h
@@exit:
	xpopa	es
	ret
read_line endp


inst_tst proc
	push	es
	push	ds
	pop	es
	mov	ah,MOUSE_FN		; test for installed, call patched int16
	int	16h			; es = resident segment
	cmp	ax,IDENTCODE
	jne	@@exit
	mov	is_inst,TRUE
@@exit:
	mov	mcseg,es

	mov	ax,es:old_21h		; Check if int 21h was patched
	mov	dx,es:old_21h+2
	or	ax,dx
	mov	exec_flag,ax
	pop	es
	ret
inst_tst endp


cmp_fptr proc				; cmp es:bx and [mcseg]:dx
	xpusha
	sub	si,si
	mov	di,si

	mov	ax,es			; ax:bx
	mov	cx,mcseg		; cx:dx

; si:ax *= 16
	rept 4
	  shl	ax,1
	  rcl	si,1
	endm
	add	ax,bx
	adc	si,0

; di:cx *= 16
	rept 4
	  shl	cx,1
	  rcl	di,1
	endm
	add	cx,dx
	adc	di,0

	cmp	cx,ax
	jne	@@exit
	cmp	si,di
@@exit:
	xpopa
	ret
cmp_fptr endp


de_inst proc
	PUSHR	<ds,es>

rstvec macro vec,old_vec_dist
	lds	dx,dword ptr es:old_vec_dist
	mov	ax,(25h shl 8) + vec	; set vector
	int	21h
endm

cmpvec macro vec,new_vec_dist
	mov	ax,(35h shl 8) + vec	; get vector
	int	21h			; to es:bx

	mov	dx,offset new_vec_dist
	call	cmp_fptr		; cmp es:bx and [mcseg]:dx
	jne	@@wrong_vec
endm

	cmp	exec_flag,0
	je	@@no_21_test
	cmpvec	21h,new_21h
@@no_21_test:
	cmpvec	10h,new_10h
	cmpvec	16h,new_16h
	cmpvec	2fh,new_2fh
	cmpvec	08h,new_08h
	jmp	short @@de_inst

@@wrong_vec:
	mov	dx,offset wro_de_inst
	mov	al,-1
@@goexit:
	jmp	short @@exit

@@de_inst:
	mov	es,mcseg
	cmp	exec_flag,0
	je	@@no_21_deinst
	rstvec	21h,old_21h
@@no_21_deinst:
	rstvec	10h,old_10h
	rstvec	16h,old_16h
	rstvec	2fh,old_2fh
	rstvec	08h,old_08h

	sub	ax,ax
	int	33h			; reset mouse driver & HW

	; Prevent new_exec from restoring the mouse driver
	mov	es:killed_exec,0ebh + ((no_res_old - killed_exec - 2) shl 8)
	mov	es,es:pspadr		; es -> memory to be freed
	mov	ah,49h			; free memory
	int	21h

	mov	dx,offset ok_de_inst
	mov	al,0

@@exit:
	POPR	<es,ds>
	ret
de_inst endp


; If loading high, allocate the high memory and copy the program to it.
; Modify the MCB name.

init_hi proc
	test	options,O_LOW
	jnz	@@no_high

	mov	ax,5802h		; Get current UMB state
	int	21h
	push	ax			;  and save it
	mov	ax,5800h		; Get current allocation strategy
	int	21h
	push	ax			;  and save it
	mov	bx,1			; Link in UMB
	mov	ax,5803h
	int	21h
	mov	bx,40h			; High memory only, first fit
	mov	ax,5801h
	int	21h
	mov	bx,end_buffer		; Size of the program and read buffer
	add	bx,EXEC_NEST + 15	; Plus exec stack and paragraph rounding
	shr	bx,4			; In paragraphs, not bytes
	mov	cx,bx			; Keep it for later
	mov	ah,48h			; Allocate the memory
	int	21h
	jnc	@@hi_okay
	or	options,O_LOW		; No high memory, so stick with low
@@hi_okay:
	mov	dx,ax			; Keep new segment in DX
	pop	bx			; Restore allocation strategy
	mov	ax,5801h
	int	21h
	pop	bx			; Restore UMB state
	mov	ax,5803h
	int	21h
	test	options,O_LOW		; Was memory actually allocated?
	jnz	@@no_high		; Nope
	mov	ax,dx
	dec	ax
	mov	es,ax			; The MCB of the newly allocated memory
	inc	ax
	mov	es:[1],ax		; Make the memory block own itself
	mov	pspadr,ax
	shl	cx,4			; Back to bytes
	mov	stack_ptr,cx
	sub	cx,EXEC_NEST
	mov	DataBlockPtr1,cx
	dec	cx			; One less for the terminating NUL
	mov	end_buffer,cx
	sub	si,si			; Copy from low memory to high
	sub	di,di
	mov	es,ax
	mov	cx,offset rd_buffer + 1 ; Include the NUL
	rep	movsb
	mov	ds,ax			; Use the new memory
@@no_high:
	ret
init_hi endp


init proc
	mov	ax,cs
	mov	ds,ax

	mov	pspadr,es

	call	inst_tst

	call	get_opt
	mov	al,-1			; assume error
	cmp	ah,TRUE
	je	@@cont_inst
	jb	@@do_exit_1

	cmp	is_inst,TRUE
	je	@@inst_okay
	mov	dx,offset not_yet_str
	jmp	@@do_exit

@@inst_okay:
	cmp	ah,REACTIVATE
	jne	@@tst_de_inst
	call	handler
	mov	dx,offset re_inst_str
	mov	al,0
@@do_exit_1:
	jmp	@@do_exit

@@tst_de_inst:
	cmp	ah,DE_INSTALL
	jne	@@tst_windows
	call	de_inst
	jmp	@@do_exit

@@tst_windows:				; Can't be anything else, yet
	call	handler
	mov	es,mcseg
	mov	es:toggle_21h,0574h	; je @@new_exec
	mov	ax,4c00h
	int	21h

@@cont_inst:
	cmp	is_inst,FALSE
	je	@@inst_tst
	test	options,O_EXEC
	jz	@@no_exec
	mov	dx,offset exec_bad_str
	cmp	exec_flag,0
	je	@@do_err_exit
	mov	es,mcseg
	xor	es:toggle_21h,9090h xor 0574h
	mov	al,1			; errorlevel 1 == already installed
	mov	dx,offset exec_off_str
	js	@@do_exit
	call	handler
	mov	dx,offset exec_on_str
	mov	al,1
	jmp	@@do_exit
@@no_exec:
	test	options,O_MARKER or O_DOUBLE
	jnz	@@no_msg
	mov	dx,offset is_ins_str
	jmp	@@do_err_exit
@@no_msg:
	mov	ax,4c01h
	int	21h

@@inst_tst:
; Test for 80188 or successor
	mov	dx,offset wro_cpu_str
	mov	cl,33
	shl	ax,cl
	test	cl,cl			; 188+ maximally shift 32 positions
	jz	@@errexit

; Test for modern Bios with int 16,5
	mov	ax,40h
	mov	es,ax
	test	byte ptr es:[96h],10000b ; enhanced keyboard installed ?
	jz	@@errexit

; use function 21h (instead of 0) to ensure that new
; mouse driver is installed
	test	options,O_WINDOWS
	jnz	@@install

	mov	ax,21h			; Test, if mouse driver installed
	int	33h
	inc	ax			; installed => 0
	jz	@@install

	mov	dx,offset nomouse_str
@@errexit:
	output
	mov	dx,offset noins_str
@@do_err_exit:
	mov	al,-1
@@do_exit:
	cmp	al,-1
	je	@@always_display
	test	options,O_QUIET
	jnz	@@get_out
@@always_display:
	push	ax
	push	dx
	output	crlf
	pop	dx
	output
	pop	ax
@@get_out:
	mov	ah,4ch
	int	21h


@@install:
	mov	es,pspadr
	sub	ax,ax
	xchg	ax,es:[2ch]		; get and zero environment
	mov	es,ax
	mov	ah,49h			; free memory
	int	21h

	call	init_hi 		; Set up high memory (changes DS!!)

	mov	ax,pspadr		; Set up the switcher structure
	mov	word ptr DataBlockPtr1+2,ax
	test	cs:options,O_LOW
	jz	@@seg_set
	mov	ax,cs
@@seg_set:
	mov	word ptr DataBlockPtr2+2,ax
	mov	word ptr sisInstData+2,ax
	mov	ax,offset DataBlockPtr1
	mov	word ptr sisInstData,ax


getvec macro vector,address
	mov	ax,(35h shl 8) + vector
	int	21h
	mov	word ptr address,bx
	mov	word ptr address+2,es
endm

setvec macro vector,address
	mov	ax,(25h shl 8) + vector
	mov	dx,offset address
	int	21h
endm

; with option /N don't install int 21h patch
	test	cs:options,O_EXEC
	jnz	@@no_21_inst
	getvec	21h,old_21h
	setvec	21h,new_21h
@@no_21_inst:
	getvec	10h,old_10h
	setvec	10h,new_10h
	getvec	16h,old_16h
	setvec	16h,new_16h
	getvec	2fh,old_2fh
	setvec	2fh,new_2fh
	getvec	08h,old_08h
	setvec	08h,new_08h

	mov	cs:mcseg,ds
	call	cs:handler

	push	cs			; Restore DS to the installer
	pop	ds
	test	options,O_QUIET
	jnz	@@no_imsg
	output	ins_str
@@no_imsg:

; Change the resident name, just for the heck of it.
	mov	ax,pspadr
	dec	ax
	mov	es,ax			;ES -> Memory control block
	mov	si,offset mcb_name
	mov	di,8
	movsw
	movsw
	movsw
	movsw

	mov	ax,4c00h		; terminate
	test	options,O_LOW
	jz	@@exit
	mov	dx,end_buffer
	or	dl,0fh			; Align it to the paragraph boundary
	mov	end_buffer,dx
	inc	dx
	add	dx,256			; Don't forget the PSP
	shr	dx,4			; Paragraphs, not bytes
	mov	ah,31h			; terminate stay resident
@@exit:
	int	21h

init endp


NL	equ	<13,10>
EOLN	equ	<13,10,'$'>

help_str:
	db 'MouseClip by Jason Hood <jadoxa@yahoo.com.au>.',NL
	db 'Version ',PVERSION,' (',PDATE,'). Freeware.',NL
	db 'http://mouseclip.adoxa.cjb.net/',NL
	db NL
	db 'Options: /Bnnn   : Use buffer size nnn',NL
	db '         /M      : Use monochrome marker (',MONO_MASK_STR,')',NL
	db '         /Mnnn   : Use marker nnn',NL
	db '         /Dn     : Use class n for double-click',NL
	db '         /Dn=str : Assign class pairs to class n',NL
	db '         /D=file : Read class pairs from file',NL
	db '         /N      : No mouse handler saving (installing)',NL
	db '                   Disable/enable saving (if installed)',NL
	db '         /W      : Skip mouse test (installing)',NL
	db '                   Reactivate after starting Windows (StartUp)',NL
	db '         /R      : Reactivate',NL
	db '         /L      : Do not install to high memory',NL
	db '         /U      : Un-install',NL
	db '         /Q      : Quiet: no messages (should be first)',NL
	db '         /H      : Usage help',NL
	db '         /D?     : Help on class pairs'
crlf	db EOLN

class_help:
	db 'Class pairs are defined as a range of numbers (or a',NL
	db 'single number), separated by colons. The order of the',NL
	db 'pairs does not matter. Example:',NL
	db NL
	db '/D2=97-122:64-90:43-58:92:95:35:126:128-165:224-235',NL
	db NL
	db 'The file has the same format, where the first three lines',NL
	db 'correspond to the three classes. A blank line will leave',NL
	db 'the class unchanged. Extra lines are ignored and can',NL
	db 'serve as comments.',EOLN

usage	db 'Left button ......... Select a rectangular area',NL
	db ' with Shift ......... Select a stream of characters',NL
	db ' with Ctrl .......... Select lines',NL
	db ' with Alt ........... Copy to Windows clipboard',NL
	db ' immediate release .. Position text cursor (simulates arrows)',NL
	db NL
	db 'Double-click ........ Select particular characters (see below)',NL
	db ' with Shift ......... Select a (wrapped) line',NL
	db ' with Ctrl .......... Select a paragraph',NL
	db ' with Shift+Ctrl .... Select a paragraph, trimming spaces',NL
	db NL
	db 'Character classes for double-click (use with Alt+Left button):',NL
	db ' Shift .............. Class 1 (words)',NL
	db ' Ctrl ............... Class 2 (filenames/URLs, default)',NL
	db ' Shift+Ctrl ......... Class 3 (strings)',NL
	db NL
	db 'Right button ........ Paste',NL
	db ' with Ctrl .......... Paste and add Enter',NL
	db ' with Shift ......... Stream paste (Space instead of Enter)',NL
	db ' with Alt ........... Paste from Windows clipboard',EOLN

wro_cpu_str	db NL,'MouseClip needs at least an 80286 with'
		db ' extended keyboard support.',EOLN
nomouse_str	db NL,'No mouse driver found or driver too old.',EOLN
noins_str	db 'MouseClip not installed.',EOLN

ins_str 	db NL,'MouseClip is installed.',EOLN
is_ins_str	db 'MouseClip is already installed.',EOLN
not_yet_str	db 'MouseClip has not been installed.',EOLN
ok_de_inst	db 'MouseClip is un-installed.',EOLN
wro_de_inst	db 'MouseClip could not be un-installed.',EOLN
re_inst_str	db 'MouseClip is re-activated.',EOLN

exec_on_str	db 'Mouse handler saving is active.',EOLN
exec_off_str	db 'Mouse handler saving has been disabled.',EOLN
exec_bad_str	db 'Mouse handler saving was not installed.',EOLN

no_opt_str	db 'Please use "/" or "-" to specify/separate options.',EOLN
bad_marker	db 'Marker is between 0 and 255.',EOLN
number_required db 'Buffer option requires a value.',EOLN
bad_buffer	db 'Buffer must be less than ',MAX_BUFLEN_STR,'.',EOLN
no_resize	db 'Buffer cannot be resized once installed.',EOLN
bad_class	db 'Class is 1, 2 or 3.',EOLN
bad_pair	db 'Character is between 1 and 255.',EOLN
no_pairs	db 'Class pairs are defined by number.',EOLN
too_many_pairs	db NL,'Too many pairs!', EOLN
bad_file	db 'Cannot open the file.',EOLN
unknown_opt	db '?: unknown option.',EOLN

mcb_name	db 'MousClip'

pair_buffer	db MAX_PAIRS * 2 + 1 dup (?)
class_buffer	db MAX_PAIRS * 2 + 3 dup (?)
class_ofs	dw 3 dup (?)
file_buffer	db 258 dup (?)		; 256 characters + CR + LF

if  ($-rd_buffer) lt MAX_BUFLEN
  db  MAX_BUFLEN - ($-rd_buffer) dup (?)
endif

code ends

; Stack segment is only used during initialisation

stck segment para stack 'stack'
  db 256 dup (?)
stck ends

end init
