;$VER: MyMouse.asm 1.0 (06.05.93)
;Written by Andrew Forrest
;Based on QMouse by Dan Babcock

;Assembled with Devpac 3
	nolist

; How it works:
;  The program spawns a child process which remains resident in memory and
;  does the funky stuff. If the resident process already exists, the program
;  alters the child's parameters and sends it an update signal. The main
;  program allocates stuff that's _always_ needed, the child allocates other
;  stuff on the fly (input.device, for example).

;  While it is setting up, the child makes itself invisible by changing its
;  name. This ensures that a seach for the child (by name) will always find
;  the child prepared.

; PROCESS IS GUARUNTEED TO STAY PUT (cos you can't totally unpatch functions)

	include	everything.i
	
Execbase	equ	4
_custom	equ	$dff000

	;*»»  Semaphore macros
	; Use carefully!
	; Takes a5=GlobalPtr; a6=ExecBase
	; Scratches a0
CriticalSection	macro
	  lea	  MySemaphore(a5),a0
	  just	  ObtainSemaphore
	endm

EndCritical	macro
	  lea	  MySemaphore(a5),a0
	  just	  ReleaseSemaphore
	endm

	;*»»  Code entrypoint
	move.l	Execbase,a6
	cmp.w	#37,LIB_VERSION(a6)
	bhs	GoodVersion
	
;Oh no, we're running under 1.3 or something
	lea	DosName(pc),a1
	moveq.l	#0,d0
	just	OpenLibrary
	tst.l	d0
	  beq.s	  ErrorEnd
	move.l	d0,a6
	just	Output
	move.l	d0,d1
	moveq	#SorryLength,d3
	lea	Sorry(pc),a0
	move.l	a0,d2
	just	Write
	move.l	a6,a1
	move.l	Execbase,a6
	just	CloseLibrary
ErrorEnd:	moveq	#RETURN_FAIL,d0
	rts

	dc.b	'$VER: MyMouse 1.0 (06.05.93)',LF,0

Sorry	dc.b	'Sorry, this program requires (at least) Kickstart 2.0!',LF,0
SorryLength	equ	*-Sorry
	even

GoodVersion:
***************************************************************************
* Start of Main program
***************************************************************************
; Registers:
;  A5=^Global data structure
;  D7=^Installed process, _if it exists yet_, else NULL

	lea	ProcName(pc),a1
	just	FindTask
	move.l	d0,d7
	bne.s	.else	; If process doesn't exist then
	  bsr	  Allocate	;   Reserve some space for it
	   bne	   Deallocate
	  bsr.s	  Read_Args	; (returns Z flag success)
	   bne	   Deallocate
	  bsr	  Install	; (returns pointer to new task)
	   beq	   Deallocate
	bra.s	.end_ok	; else
.else	  move.l	  d7,a1
	  move.l	  TC_Userdata(a1),a5	; Get global structure
	  bsr.s	  Read_Args
	   bne.s	   .end_fail	;(If couldn't read args, fail)
	  move.l	  d7,a1
	  move.l	  UpdateSig(a5),d0
	  just	  Signal	;   Send task a signal to wake it up
.end_ok	moveq	  #RETURN_OK,d0
.end_fail	rts

***************************************************************************
Read_Args:	;*»»  Replace arguments in global structure with new ones
; Takes a5=GlobalPtr; Returns OK or FAIL code; a6=Execbase
; Scratches d0-d4/a0-a1/a4
	CriticalSection
	lea	Options(a5),a0
	lea	(a0),a1
	moveq	#NumOptions-1,d0
.clear_loop	  clr.l	  (a0)+
	dbra	d0,.clear_loop
	lea	Template(pc),a0
	move.l	a0,d1
	move.l	a1,d2
	moveq	#0,d3	;no custom rdarg structure
	move.l	DosBase(a5),a6
	just	ReadArgs
	tst.l	d0
	  beq.s	  .fail
	move.l	ArgPtr(a5),d1	; get _old_ arguments
	beq.s	.use
	  push.l	  d0
	  just	  FreeArgs	; and free them if necessary
	  pop.l	  d0
.use	move.l	d0,ArgPtr(a5)	; then install _new_ arguments
	move.l	Execbase,a6
	bsr.s	StaticOptions	; Do some options _now_
	moveq.l	#RETURN_OK,d0

.end	EndCritical
	tst.l	d0		; (return D0 and CCR)
	rts
	 
.fail	move.l	Execbase,a6
	moveq.l	#RETURN_FAIL,d0
	bra.s	.end

***************************************************************************
StaticOptions:
; Takes a5=GlobalPtr; a6=Execbase; (& semaphore locked)
; Scratches d0-d4/a0-a1/a4
	;*»»  Perform NoClick, if desired.(d4=^MsgPort;a4=^IORequest)
	move.l	NoClickOption(a5),d2
	  beq	  .NoNoClick
	;*»»  Create an IORequest structure
	just	CreateMsgPort
	move.l	d0,d4	; MsgPort in d4
	  beq	  .NoNoClick
	move.l	d4,a0
	moveq	#IOSTD_SIZE,d0
	just	CreateIORequest
	tst.l	d0
	  beq.s	  .fail
	move.l	d0,a4	; IORequest in a4
	move.l	d2,a0	; ^Flags: drives to declick
	move.l	(a0),d2
	moveq	#31,d3	; Unit number (Allow for 32 floppies)
.ClickLoop	  btst	  d3,d2
	beq.s	.NextLoop
	  lea	  TrackName(pc),a0	;device name
	  move.l	  d3,d0	;unit number
	  lea	  (a4),a1	;IO request
	  moveq.l	  #0,d1	;flags
	  just	  OpenDevice
	  tst.l	  d0
	bne.s	.NextLoop
	  move.l	  IO_UNIT(a4),a0
	  bchg	  #TDPB_NOCLICK,TDU_PUBFLAGS(a0)
	  lea	  (a4),a1
	  just	  CloseDevice
.NextLoop	dbra	d3,.ClickLoop
	lea	(a4),a0
	just	DeleteIORequest
.fail	move.l	d4,a0
	just	DeleteMsgPort
.NoNoClick
	;*»»  Handle 'STAR' option.
	tst.l	StarOption(a5)
	  beq.s	  .NoStar
	move.l	DosBase(a5),a0
	move.l	dl_Root(a0),a0
	bchg	#0,rn_Flags(a0)
.NoStar
	rts

***************************************************************************
Install:
; Takes a5=GlobalPtr; a6=Execbase; Returns d0=^NewTask or NULL
; Scratches a0-a1/d0-d1
	;*»»  Copy resident part of code into allocated memory
	lea	StartResidentCode(pc),a0	;source
	lea	ResidentCode(a5),a1	;destination
	move.l	#ResidentCodeLength,d0	;size
	just	CopyMem
	just	CacheClearU	;for 020/030/040

	;*»»  Initialize static data
	move.b	#HANDLERPRI,InputInterrupt+LN_PRI(a5)
	move.l	a5,InputInterrupt+IS_DATA(a5)
	lea	IntRoutine-StartResidentCode+ResidentCode(a5),a0
	move.l	a0,InputInterrupt+IS_CODE(a5)

	move.l	a5,GlobalPtr-StartResidentCode+ResidentCode(a5)

	;*»»  Get stack for command.
	move.l	DosBase(a5),a6
	just	Cli
	move.l	d0,a0
	move.l	cli_DefaultStack(a0),d0
	lsl.l	#2,d0
	move.l	d0,CmdStackSize-StartResidentCode+ResidentCode(a5)

	;*»»  Start the baby process
	lea	NewProcTags-StartResidentCode+ResidentCode(a5),a0
	lea	NEW_LIFE-StartResidentCode+ResidentCode(a5),a1
	move.l	a1,EntryPointTag(a0)
	lea	ProcName-StartResidentCode+ResidentCode(a5),a1
	not.b	(a1)	; (So task is initially invisible)
	move.l	a1,ProcNameTag(a0)
	move.l	a0,d1	; Get tag list into d1
	just	CreateNewProc
	tst.l	d0

	move.l	Execbase,a6
	rts

***************************************************************************
Allocate:
; Takes a6=Execbase; Returns OK or FAIL; a5=GlobalPtr or NULL
; Scratches a0-a3/d0-d1
	;*»»  Allocate global data structure
	move.l	#Data_SIZEOF,d0
	move.l	#MEMF_CLEAR|MEMF_PUBLIC,d1	;(Got to be PUBLIC)
	just	AllocMem
	move.l	d0,a5	; Need to store in a5 _before_ testing
	tst.l	d0
	  beq.s	  .cant_alloc

	;*»»  Allocate Chip RAM data
	move.l	#MouseData_SIZEOF,d0
	move.l	#MEMF_CLEAR|MEMF_PUBLIC|MEMF_CHIP,d1
	just	AllocMem
	move.l	d0,MouseChipData(a5)
	  beq.s	  .cant_alloc

	;*»»  Open libraries
	lea	LibTable(pc),a2
	lea	FirstLibBase(a5),a3
	moveq	#NumLibs-1,d2
.loop	  move.w	  (a2)+,d0	; library version to open
	  ext.l	  d0	; version should be longword
	  move.w	  (a2)+,a1
	  lea	  LibTable(pc,a1.w),a1	; name of library
	  just	  OpenLibrary
	  move.l	  d0,(a3)+
	  beq.s	  .cant_alloc
	dbra	d2,.loop

	;*»»  Set up semaphore
	lea	MySemaphore(a5),a0
	just	InitSemaphore

	moveq	#RETURN_OK,d0	; Set d0 and CCR
	rts
	
.cant_alloc	moveq	#RETURN_FAIL,d0
	rts

***************************************************************************
LibTable:	dc.w	37,DosName-LibTable
	dc.w	33,IntName-LibTable
	dc.w	33,LayName-LibTable
NumLibs:	equ	(*-LibTable)/4

***************************************************************************
***************************************************************************
StartResidentCode:	;The following code is copied into allocated memory.
***************************************************************************
; Everything except Deallocate is called only from child process
***************************************************************************
Deallocate:	;*»» Close libraries (called either from parent or child)
; Takes a5=GlobalPtr; a6=Execbase; Returns FAILure code (always)
; Scratches a0-a2/d0-d2
	move.l	a5,d0	; Does global structure exist?
	  beq.s	  .noData

	lea	FirstLibBase(a5),a2
	moveq	#NumLibs-1,d2
.loop	move.l	(a2)+,d0
	beq.s	.endif
	  move.l	  d0,a1
	  just	  CloseLibrary
.endif	dbra	d2,.loop

	;*»» Free chip data.
	move.l	MouseChipData(a5),d0
	beq.s	.noChipData
	  move.l	  d0,a1
	  move.l	  #MouseData_SIZEOF,d0
	  just	  FreeMem
.noChipData
	;*»» Free global structure
	lea	(a5),a1
	move.l	#Data_SIZEOF,d0
	jmp	_LVOFreeMem(a6)	; Jump in case we are
.noData				; deallocating _this_ code
.end	moveq	#RETURN_FAIL,d0 ; If deallocating we must have failed
	rts

***************************************************************************
; Here lies the start of the child process' program
NEW_LIFE:	move.l	Execbase,a6
	move.l	GlobalPtr(pc),a5
	bsr	InitialiseResident	; Get signals
	  bne.s	  Deallocate	; Oh no!

BIG_LOOP:	bsr	EvaluateOptions
	  bne.s	  KillHandler
	bsr	AllocateResident	; Get so far ungot resources
wait_loop:	move.l	AllSignals(a5),d0
	just	Wait
	move.l	d0,d7
	and.l	CLISig(a5),d0
	beq.s	.noCLI
	  bsr	  CLI
.noCLI	and.l	UpdateSig(a5),d7
	  beq.s	  wait_loop
	bra.s	BIG_LOOP

***************************************************************************
KillHandler:
	bclr	#STB_HandlerInstalled,Status(a5)
	  beq.s	  .end
	move.l	InputIORequest(a5),a1
	move.w	#IND_REMHANDLER,IO_COMMAND(a1)
	lea	InputInterrupt(a5),a0
	move.l	a0,IO_DATA(a1)
	just	DoIO
.end	bra.s	wait_loop

CLI:	CriticalSection	; Critical so CMD parameter can't change
	move.l	CmdOption(a5),d1	;Pop CLI
	beq.s	.fail
	  pushm.l	  d1/a6
	  move.l	  IntBase(a5),a6	;Workbench To Front
	  just	  WBenchToFront
	  popm.l	  d1/a6
	  move.l	  DosBase(a5),a6
	  lea	  Tags(pc),a1
	  bsr.s	  OpenNil
	  move.l	  d0,InputTag(a1)
	beq.s	.end
	  bsr.s	  OpenNil
	  move.l	  d0,OutputTag(a1)
	beq.s	.closeinput
	  move.l	  a1,d2	;tags
	  just	  SystemTagList
	  tst.l	  d0
	  beq.s	  .end	; If sucessful, should deallocate itself
	    move.l	    OutputTag+Tags(pc),d1
	    just	    Close
.closeinput	    move.l	    InputTag+Tags(pc),d1
	    just	    Close
.end	  move.l	  Execbase,a6
.fail	EndCritical
	rts	

Tags:
	dc.l	SYS_UserShell,0
	dc.l	SYS_Asynch,0
	dc.l	SYS_Input,0
InputTag	equ	*-4-Tags
	dc.l	SYS_Output,0
OutputTag	equ	*-4-Tags
	dc.l	NP_Priority,ShellPriority
	dc.l	NP_StackSize
CmdStackSize:
	dc.l	0
	dc.l	0	;end of tag list

OpenNil:	pushm.l	a0-a1/d1-d2
	lea	NilName(pc),a0
	move.l	a0,d1
	move.l	#MODE_READWRITE,d2
	just	Open
	popm.l	a0-a1/d1-d2
	rts

***************************************************************************
InitialiseResident:
; Takes a5=GlobalPtr; a6=Execbase; Returns OK or FAIL
; Scratches d0-d2/a0-a1
	suba.l	a1,a1
	just	FindTask	; _Find_ yourself.
	move.l	d0,a0
	move.l	a5,TC_Userdata(a0)
	move.l	d0,Task(a5)

	;*»»  Allocate signals
	moveq	#NumSigs-1,d2
	lea	Sigs(a5),a2
.sigloop	  moveq	  #-1,d0
	  just	  AllocSignal
	  tst.l	  d0
	bmi.s	.fail
.ok	  moveq	  #0,d1
	  bset	  d0,d1
	  move.l	  d1,(a2)+
	  or.l	  d1,AllSignals(a5)
	dbra	d2,.sigloop

	just	Forbid
	lea	ProcName(pc),a1
	move.b	#InitialLetter,(a1)
	just	FindTask	; Search for process with same name
	tst.l	d0
	  bne.s	  .found_twin	; Oh, no -- duplicate name
	move.l	Task(a5),a1
	move.l	LN_NAME(a1),a1
	move.b	#InitialLetter,(a1)	; make process visible
	just	Permit

	moveq	#RETURN_OK,d0
	rts
	
.found_twin	
.fail	moveq	#31,d2
.loop	  btst	  d2,AllSignals(a5)
	  beq.s	  .next
	    move.l	    d2,d0
	    just	    FreeSignal
.next	dbra	d2,.loop
	moveq	#RETURN_FAIL,d0
	rts

***************************************************************************
; Here is the routine for allocating the baby task's resources
; If they cannot be allocated at first, the program tries again every time
; an update signal is recieved.
AllocateResident:
; Takes a5=GlobalPtr; a6=Execbase
; Scratches a0-a2/d0-d1
	;*»»  Create an IORequest structure
	move.l	InputMsgPort(a5),d0
	  bne.s	  .endCreatePort	; Skip if port already there
	just	CreateMsgPort
	move.l	d0,InputMsgPort(a5)
	beq.s	.fail
.endCreatePort
	move.l	d0,a0
	move.l	InputIORequest(a5),d0	; n.b. _not_ "tst.l Inpu..."
	  bne.s	  .endCreateReq	; Skip if req already there
	moveq	#IOSTD_SIZE,d0
	just	CreateIORequest
	move.l	d0,InputIORequest(a5)
	beq.s	.fail
.endCreateReq

	;*»»  Open the input.device
	btst	#STB_InputDeviceOpened,Status(a5)
	  bne.s	  .endOpenInput
	move.l	d0,a1	;IO request
	lea	InputName(pc),a0	;device name
	moveq	#0,d0	;unit number
	moveq	#0,d1	;flags
	just	OpenDevice
	tst.l	d0
	bne.s	.fail
	  bset	  #STB_InputDeviceOpened,Status(a5)
.endOpenInput

	;*»»  Install the input handler
	bset	#STB_HandlerInstalled,Status(a5)
	  bne.s	  .endInstallHandl
	move.l	InputIORequest(a5),a1
	move.w	#IND_ADDHANDLER,IO_COMMAND(a1)
	lea	InputInterrupt(a5),a0
	move.l	a0,IO_DATA(a1)
	just	DoIO
.endInstallHandl
.fail
	;*»»  Patch ONE system routine. (Do this last)
	tst.l	OldDisplayBeep(a5)
	  bne.s	  .endPatch	; Skip if already patched
	move.l	IntBase(a5),a1
	move.w	#_LVODisplayBeep,a0
	lea	NewDisplayBeep(pc),a2
	move.l	a2,d0
	just	SetFunction
	move.l	d0,OldDisplayBeep(a5)
.endPatch
.ok
	rts

***************************************************************************
NewDisplayBeep:
	move.l	GlobalPtr(pc),a1
	tst.l	NoBeepOption(a1)
	bne.s	.endif
	  move.l	  OldDisplayBeep(a1),-(sp)
.endif	rts

***************************************************************************
EvaluateOptions:
; Takes a5=GlobalPtr; Scratches d0-d1/a0-a2
	;*»»  Copy some parameters (for input-handler)
	CriticalSection	; So no one changes them while we do it
	lea	CacheTable(pc),a1
	lea	OptionCashes(a5),a2
	move.w	#CacheNum-1,d1
.loop	  move.w	  (a1)+,d0
	  move.l	  (a5,d0.w),d0
.if	  beq.s	  .else
	    move.l	    d0,a0
	    move.l	    (a0),d0
	  bra.s	  .endif
.else	    moveq	    #-1,d0	; Unselected options become -1
.endif	  move.l	  d0,(a2)+
	dbra	d1,.loop
.endloop	EndCritical

	;*»»  Initialize damping constant.
	move.l	Acceleration(a5),d0
	  bmi.s	  .no_damp
	move.l	d0,a0
	move.l	(a0),d0
	subq.l	#1,d0
	move.l	Threshold(a5),d1
	bpl.s	.use_thresh
	  moveq	  #0,d1
.use_thresh	addq.l	#1,d1
	mulu.w	d0,d1
	move.w	d1,DampingConstant(a5)
.no_damp:
.okay
	move.l	QuitOption(a5),d0	; non-zero if QUIT
	rts

CacheTable	dc.w	MBlankOption
	dc.w	SBlankOption
	dc.w	AccelOption
	dc.w	ThreshOption
	dc.w	CTFOption
	dc.w	ClickBorderOption
CacheNum	equ	(*-CacheTable)/2

***************************************************************************
***************************************************************************
IntRoutine:			;Start of input handler code
***************************************************************************
; * Routine can't get semaphore since this causes deadlock when we try to
; * change the parameters. For this reason, we must use only atomic variables.

; Takes a0=^oldEventChain; a1=^GlobalPtr
; Returns d0=^newEventChain
; Scratches d1, a0, a1
; Uses a4=^first link; a5=^global structure; d1=TimeStamp of last event
	pushm.l	a4-a5
	lea	(a0),a4	; First link in event chain
	lea	(a1),a5	; Global Ptr
.loop	  push.l	  a0
	  bsr.s	  Do_Event
	  pop.l	  a0
	  move.l	  ie_NextEvent(a0),d0
	beq.s	.end_loop
	  move.l	  d0,a0
	bra.s	.loop
.end_loop	move.l	ie_TimeStamp+TV_SECS(a0),d1	; time in seconds
	bsr	Time_Outs
	move.l	a4,d0
	popm.l	a4-a5
	rts

***************************************************************************
Do_Event:
; Takes a0=^Event; a5=^GlobalPtr; 
; scratches d1, a0, a1
	pushm.l	a2/a6
	move.l	IntBase(a5),a6

	;*»»  Perform SunMouse (Activate window)
	tst.l	SunMouseOption(a5)
	  beq.s	  .SkipSunMouse1
	cmp.b	#IECLASS_TIMER,ie_Class(a0)
	  bne.s	  .SkipSunMouse1
	bclr	#STB_SunMouse,Status(a5)
	  beq.s	  .SkipSunMouse1	; Skip if already Sunned
	bsr	GetWindow
	push.l	a0
	move.l	d0,a0
	cmp.l	ib_ActiveWindow(a6),a0
	  beq.s	  .EndSun
	just	ActivateWindow
.EndSun:	pop.l	a0
.SkipSunMouse1:

	cmp.b	#IECLASS_TIMER,ie_Class(a0)
	  beq	  .not_key	; If it's a timer event, don't unblank

	;*»»  Reset screen blank
	tst.l	ScreenBlank(a5)
	  ble.s	  .SkipSRestore
	move.l	ie_TimeStamp(a0),ScreenTime(a5)
	bsr	RestoreScreen
.SkipSRestore

	cmp.b	#IECLASS_RAWKEY,ie_Class(a0)
	  bne	  .not_key		; skip this bit if not a key


	;*»»  Do Northgate keyboard mapping.
	tst.l	NorthgateOption(a5)
	  beq.s	  .SkipNorthgate
	lea	NorthgateTable(pc),a2
	move.w	ie_Code(a0),d0
	bclr	#IECODEB_UP_PREFIX,d0
.NorthgateLoop:
	move.b	(a2),d1
	  beq.s	  .EndNorthgate
	addq.l	#3,a2
	cmp.b	d0,d1
	  bne.s	  .NorthgateLoop
	and.w	#IECODE_UP_PREFIX,ie_Code(a0)
	move.b	-2(a2),d0
	or.b	d0,ie_Code+1(a0)
	move.b	-1(a2),d0
	or.b	d0,ie_Qualifier+1(a0)
.EndNorthgate
.SkipNorthgate

	tst.b	ie_Code+1(a0)
	  bmi	  .end_event	;henceforth ignore key-ups
	btst	#IEQUALIFIERB_LCOMMAND,ie_Qualifier+1(a0)
	  bne.s	  .do_LAmiga

	;*»»  Blank mouse pointer
	tst.l	MouseBlank(a5)
	bmi.s	.NoMBlank
	  bsr	  BlankMouse	; Blank mouse on keypress
.NoMBlank	bra	.end_event
	
.do_LAmiga:
	;*»»  Check for Amiga-ESC
	tst.l	CmdOption(a5)
	  beq	  .end_event
	cmp.w	#KEYCODE_ESC,ie_Code(a0)
	  bne	  .end_event
	clr.b	ie_Class(a0)
	move.l	CLISig(a5),d0
	bsr	SigTask
	  bra	  .end_event

.not_key:
	move.w	ib_MouseX(a6),d0
	move.w	ib_MouseY(a6),d1
	cmp.b	#IECLASS_RAWMOUSE,ie_Class(a0)
	  beq.s	  .do_mouse
	cmp.w	CurrentX(a5),d0
	  bne.s	  .do_mouse
	cmp.w	CurrentY(a5),d1
	  beq	  .end_event

.do_mouse:	move.w	d0,CurrentX(a5)
	move.w	d1,CurrentY(a5)
	move.l	ie_TimeStamp(a0),MouseTime(a5)
	tst.l	MBlankWindow(a5)
	beq.s	.no_restore
	  bsr	  RestoreMouse
.no_restore

	;*»»  Test Sunnyness of mouse
	tst.l	SunMouseOption(a5)
	  beq.s	  .noSunMouse
	move.w	ie_Qualifier(a0),d0
	and.w	#IEQUALIFIER_LEFTBUTTON+IEQUALIFIER_RBUTTON+IEQUALIFIER_MIDBUTTON,d0
	  bne.s	  .noSunMouse
	cmp.w	#IECODE_LBUTTON+IECODE_UP_PREFIX,ie_Code(a0)
	  beq.s	  .noSunMouse
	bset	#STB_SunMouse,Status(a5)
.noSunMouse

	;*»»  Perform click-to-back.
	tst.l	CTBOption(a5)
	  beq	  .no_CTB
	cmp.w	#IECODE_RBUTTON,ie_Code(a0)
	  bne	  .no_CTB
	move.w	ie_Qualifier(a0),d0
	btst	#IEQUALIFIERB_LEFTBUTTON,d0
	  beq	  .no_CTB
	pushm.l	d2/a0-a1
	bsr	GetWindow
	tst.l	d0
	  beq.s	  .end_CTB
	move.l	d0,a1
	move.l	wd_Flags(a1),d2
	and.l	#WFLG_BACKDROP,d2
	  bne.s	  .end_CTB
	;Is this the only window on this screen, except for backdrop windows?
	move.l	wd_WScreen(a1),a1
	move.l	sc_FirstWindow(a1),a1
.back_loop	  cmp.l	  a1,d0
	beq.s	.endback_lp
	  move.l	  wd_Flags(a1),d2
	  and.l	  #WFLG_BACKDROP,d2
	beq.s	.flipwindow
.endback_lp	  move.l	  wd_NextWindow(a1),a1
	  move.l	  a1,d2
	bne.s	.back_loop
	bra.s	.end_CTB
.flipwindow	move.l	d0,a0
	just	WindowToBack
.end_CTB	popm.l	d2/a0-a1
	clr.b	ie_Class(a0)
	bra	.end_event
.no_CTB

	;*»»  Perform click-to-front.
	tst.l	CTF(a5)
	  ble	  .no_CTF
	cmp.w	#IECODE_LBUTTON,ie_Code(a0)
	  bne	  .no_CTF
	btst	#IEQUALIFIERB_LCOMMAND,ie_Qualifier+1(a0)
	  bne	  .no_CTF	;to avoid interferring with Snap
	bsr	GetWindow
	tst.l	d0
	  beq	  .no_CTF
	push.l	d2
	move.l	CTFB(a5),d2
	bmi.s	.no_border
	  move.l	  d0,a1
	  btst	  #BBB_LEFT,d2
	  beq.s	  .test_right
.test_left	    move.w	    wd_MouseX(a1),d1
	    move.b	    wd_BorderLeft(a1),d0
	    ext.w	    d0
	    cmp.w	    d0,d1
	      blo.s	      .in_border
.test_right	  btst	  #BBB_RIGHT,d2
	  beq.s	  .test_top
	    move.b	    wd_BorderRight(a1),d0
	    ext.w	    d0
	    add.w	    d0,d1
	    cmp.w	    wd_Width(a1),d1
	      bhs.s	      .in_border
.test_top	  btst	  #BBB_TOP,d2
	  beq.s	  .test_botto
	    move.w	    wd_MouseY(a1),d1
	    move.b	    wd_BorderTop(a1),d0
	    ext.w	    d0
	    cmp.w	    d0,d1
	      blo.s	      .in_border
.test_botto	  btst	  #BBB_BOTTOM,d2
	  beq	  .end_CTF
	    move.b	    wd_BorderBottom(a1),d0
	    ext.w	    d0
	    add.w	    d0,d1
	    cmp.w	    wd_Height(a1),d1
	      blo	      .end_CTF
.in_border	  move.l	  a1,d0	  
.no_border	move.l	CTF(a5),d1
	cmp.l	#1,d1
	bhi.s	.more_thn_1
	bsr	DoWindowToFront
	  bra	  .end_CTF
.more_thn_1	cmp.l	ClickWindow(a5),d0
	  beq.s	  .same_win
	move.l	d0,ClickWindow(a5)
	moveq	#1,d0
	move.l	d0,ClickCount(a5)
	bra.s	.save_time
.same_win	; Check to see whether MaxClickDelay has been exceeded. If not
	; increment ClickCount and compare with (CTFOption). If equal, do
	; a WindowToFront. Window under pointer in D0.
	push.l	d0
	pushm.l	d2-d3/a0
	move.l	ClickTime+TV_SECS(a5),d0
	move.l	ClickTime+TV_MICRO(a5),d1
	move.l	ie_TimeStamp+TV_SECS(a0),d2
	move.l	ie_TimeStamp+TV_MICRO(a0),d3
	just	DoubleClick
	popm.l	d2-d3/a0
	tst.l	d0
	  beq.s	  .time_exceeded
	addq.l	#1,ClickCount(a5)
	move.l	CTF(a5),d0
	cmp.l	ClickCount(a5),d0
	  bhi.s	  .need_more_clicks
	clr.l	ClickCount(a5)
	pop.l	d0
	bsr	DoWindowToFront
	  bra.s	  .end_CTF
.time_exceeded
	moveq	#1,d0
	move.l	d0,ClickCount(a5)
.need_more_clicks
	pop.l	d0
.save_time	move.l	ie_TimeStamp+TV_SECS(a0),ClickTime+TV_SECS(a5)
	move.l	ie_TimeStamp+TV_MICRO(a0),ClickTime+TV_MICRO(a5)
.end_CTF	pop.l	d2
.no_CTF

	;*»»  Perform acceleration.
	tst.l	Acceleration(a5)
	  bmi	  .end_event
	move.l	Threshold(a5),d0
	bpl.s	.T1
	  moveq	  #0,d0
.T1	move.w	ie_X(a0),d1
	  bpl.s	  .PosX
	neg.w	d1
.PosX	cmp.w	d0,d1
	  bls.s	  .SkipX	;below threshold
	move.l	Acceleration(a5),d1
	muls.w	ie_X(a0),d1
	  bpl.s	  .SubDampX
	add.w	DampingConstant(a5),d1
	  bra.s	  .DampX
.SubDampX	sub.w	DampingConstant(a5),d1
.DampX	move.w	d1,ie_X(a0)
.SkipX	move.w	ie_Y(a0),d1
	  bpl.s	  .PosY
	neg.w	d1
.PosY	cmp.w	d0,d1
	  bls.s	  .EndAccel
	move.l	Acceleration(a5),d1
	muls.w	ie_Y(a0),d1
	  bpl.s	  .SubDampY
	add.w	DampingConstant(a5),d1
	  bra.s	  .DampY
.SubDampY	sub.w	DampingConstant(a5),d1
.DampY	move.w	d1,ie_Y(a0)
.EndAccel

.end_event	popm.l	a2/a6
	rts

***************************************************************************
Time_Outs:	;*»»  Check for time-outs (mouse and screen blanking)
; Takes d1=TimeStamp seconds; a5=GlobalPtr
; scratches d0, d1, a0, a1
	push.l	a6
	move.l	IntBase(a5),a6

	;*»»  Check if window has changed with blanked pointer
	move.l	MBlankWindow(a5),d0
	  beq.s	  .EndMBW
	cmp.l	ib_ActiveWindow(a6),d0
	  beq.s	  .EndMBW
	bsr	BlankMouse	; Blank mouse for this new window
.EndMBW:

	tst.l	MouseBlank(a5)
	  ble.s	  .no_MBlank ; If user-mouse-blank-time=0, don't blank
	move.l	d1,d0
	sub.l	MouseBlank(a5),d0	; this_time - user_pause
	tst.l	MouseTime(a5)
	  beq.s	  .InitMBlank
	sub.l	MouseTime(a5),d0	; sub last mouse move time
	blo.s	.no_MBlank	; If this_time - last_move > user_pause
	  move.l	  d0,MouseTime(a5)
	  bsr	  BlankMouse
.no_MBlank:

	tst.l	ScreenBlank(a5)
	  ble.s	  .no_SBlank ; If screen-blank time-out=0, don't blank
	move.l	d1,d0
	sub.l	ScreenBlank(a5),d0	; this_time - user_pause
	tst.l	ScreenTime(a5)
	  beq.s	  .InitSBlank
	sub.l	ScreenTime(a5),d0
	blo.s	.no_SBlank
	  bset	  #STB_SBlanked,Status(a5)
	bne.s	.no_SBlank	; Don't blank if already blanked
	  move.w	  #DMAF_COPPER+DMAF_RASTER,dmacon+_custom.l
	  clr.w	  color+_custom.l
.no_SBlank:
	pop.l	a6
	rts

.InitMBlank:
	move.l	d1,MouseTime(a5)
	bra.s	.no_MBlank
.InitSBlank:
	move.l	d1,ScreenTime(a5)
	bra.s	.no_SBlank

***************************************************************************
;Utility routines

	;*»»  Get window associated with current mouse position.
; Takes a5=GlobalPtr; a6=IntBase; Returns d0=Window; D1=Screen
	; (assume we don't need to lock IntuitionBase)
GetWindow:
	pushm.l	d4/a0-a1/a4/a6
	lea	(a6),a4
	move.l	LayBase(a5),a6
	move.l	ib_FirstScreen(a4),d4
.loop	  tst.l	  d4
	beq.s	.err_end
	  move.l	  d4,a0
	  move.l	  sc_NextScreen(a0),d4
	  move.w	  sc_MouseX(a0),d0
	bmi.s	.loop
	  move.w	  sc_MouseY(a0),d1
	bmi.s	.loop
	push.l	a0
	lea	sc_LayerInfo(a0),a0
	just	WhichLayer
	pop.l	d1
	tst.l	d0
	  beq.s	  .end
	move.l	d0,a0
	move.l	lr_Window(a0),d0
.end	popm.l	d4/a0-a1/a4/a6
	rts
.err_end:	moveq	#0,d0
	moveq	#0,d1
	bra	.end

DoWindowToFront:
; Takes d0=^Window or NULL (no effect); a5=GlobalPtr
; scratches nothing
	tst.l	d0
	  beq.s	  .abort
	pushm.l	d0-d1/a0-a1/a6
	move.l	IntBase(a5),a6
	move.l	d0,a0
	move.l	wd_WLayer(a0),d0
	  beq.s	  .end
	move.l	d0,a1
	move.l	lr_ClipRect(a1),d0
	  beq.s	  .end
	move.l	d0,a1
	tst.l	cr_Next(a1)	; Do nothing if it is not covered
	  beq.s	  .end
	move.l	wd_Flags(a0),d0
	and.l	#WFLG_BACKDROP,d0
	  bne.s	  .end	; or if it is a backdrop window
	just	WindowToFront
.end	popm.l	d0-d1/a0-a1/a6
.abort	rts

	;*»»  Blank the current window's mouse pointer
	; Takes a5=GlobalPtr; a6=IntBase
	; Scratches nothing
BlankMouse:	pushm.l	a0-a1/d0-d3
	move.l	ib_ActiveWindow(a6),a0
	tst.l	MBlankWindow(a5)	; No problem if not blanked
	beq.s	.notblanked
	  cmp.l	  MBlankWindow(a5),a0	; If blanked a different window,
	bne.s	.other_win		; then restore its pointer
	  move.l	  wd_Pointer(a0),d0
	  cmp.l	  MouseChipData(a5),d0	; If still blanked, give up
	beq.s	.end_blank
.notblanked	move.l	a0,MBlankWindow(a5)	; Remember window & its pointer
	move.l	wd_Pointer(a0),OldPointer+Ptr_Image(a5)
	move.l	wd_PtrDimensions(a0),OldPointer+Ptr_Dimensions(a5)
	move.l	MouseChipData(a5),a1
***	lea	ZeroMouse(a1),a1	*** No Operation
	moveq	#1,d0	;height
	moveq	#16,d1	;width
	moveq	#0,d2	;xoffset
	moveq	#0,d3	;yoffset
	just	SetPointer
.end_blank	popm.l	a0-a1/d0-d3
	rts

.other_win	bsr.s	RestoreMouse	; If another window was blank-
	bra.s	.notblanked		; ed, unblank it first.

	;*»»  Restore old mouse image
	; Takes a5=GlobalPtr; a6=IntBase
	; scratches nothing
RestoreMouse:
	pushm.l	a0-a1/d0-d3
	move.l	MBlankWindow(a5),d0	; Skip if nothing is blanked
	beq.s	.no_RMouse
	  move.l	  d0,a0
	  move.l	  wd_Pointer(a0),a1
	  cmp.l	  MouseChipData(a5),a1
	  bne.s	  .end_RMouse	; Skip if already unblanked
	    move.l	    OldPointer+Ptr_Image(a5),a1
	    move.l	    a1,d0
	      beq.s	      .ClearPtr	; Restore default pointer
	    move.b	    OldPointer+Ptr_Height(a5),d0
	    move.b	    OldPointer+Ptr_Width(a5),d1
	    move.b	    OldPointer+Ptr_XOffset(a5),d2
	    move.b	    OldPointer+Ptr_YOffset(a5),d3
	    ext.w	    d0
	    ext.w	    d1
	    ext.w	    d2
	    ext.w	    d3
	    just	    SetPointer
.end_RMouse	clr.l	MBlankWindow(a5)
.no_RMouse	popm.l	a0-a1/d0-d3
	rts

.ClearPtr	just	ClearPointer
	bra.s	.end_RMouse

RestoreScreen:
	bclr	#STB_SBlanked,Status(a5)
	beq.s	.endif
	  move.w	  #DMAF_SETCLR+DMAF_COPPER+DMAF_RASTER,dmacon+_custom.l
.endif	rts

SigTask:
; Takes d0=signal; a5=GlobalPtr
	pushm.l	d0-d1/a0-a1/a6
	move.l	Task(a5),a1
	move.l	Execbase,a6
	just	Signal
	popm.l	d0-d1/a0-a1/a6
	rts

***************************************************************************

GlobalPtr	ds.l	1

NorthgateTable:
	;*»»  Assign PageUp/PageDown/Home/End to shift-cursor sequences
	dc.b	$6b,$4f,IEQUALIFIER_LSHIFT
	dc.b	$6c,$4e,IEQUALIFIER_LSHIFT
	dc.b	$6d,$4c,IEQUALIFIER_LSHIFT
	dc.b	$6e,$4d,IEQUALIFIER_LSHIFT
;	;*»»  Change a couple keypad key assignments to be more Amiga-like.
;	dc.b	$5c,$5b,0	;'/' key -> ')' key
;	dc.b	$5d,$5c,0	;'*' key -> '/' key
;	dc.b	$4a,$5d,0	;'-' key -> '*' key
	dc.b	0

ProcName:	dc.b	'Funky baby Mouse process',0 ;»» Name of child process
InitialLetter	equ	'F'	;»» child's first initial
InputName:	dc.b	'input.device',0
NilName:	dc.b	'NIL:',0
	even

NewProcTags:	; In allocated memory so command can be pure
	dc.l	NP_Entry
EntryPointTag	equ	*-NewProcTags
	dc.l	0
	dc.l	NP_Name
ProcNameTag	equ	*-NewProcTags
	dc.l	0
	dc.l	NP_Cli,-1
	dc.l	NP_Priority,TaskPriority
	dc.l	0	;end of tags

***************************************************************************
EndResidentCode:	;end of code copied into allocated memory
***************************************************************************
***************************************************************************
ResidentCodeLength	equ	EndResidentCode-StartResidentCode


***************************************************************************
* Global data and constants
***************************************************************************

HANDLERPRI		equ	100	;Higher than pesky AMOS
TaskPriority	equ	20
ShellPriority	equ	0

NULL	equ	0
KEYCODE_ESC	equ	$45

	BITDEF	BB,TOP,0
	BITDEF	BB,BOTTOM,1
	BITDEF	BB,LEFT,2
	BITDEF	BB,RIGHT,3

DosName	dc.b	"dos.library",0
IntName	dc.b	"intuition.library",0
LayName	dc.b	"layers.library",0
TrackName:	dc.b	"trackdisk.device",0

;*****************************************************************
;* Global data structure definitions

	STRUCTURE	PointerStruct,0
	APTR	Ptr_Image
	LABEL	Ptr_Dimensions
	BYTE	Ptr_Height
	BYTE	Ptr_Width
	BYTE	Ptr_XOffset
	BYTE	Ptr_YOffset
	LABEL	Ptr_SIZEOF

wd_PtrDimensions	equ	wd_PtrHeight

	STRUCTURE	Data,0
	BYTE	Nothing
	BYTE	Status	;various status bits (defined below)
	LONG	MouseChipData
	LABEL	FirstLibBase
	LONG	DosBase
	LONG	IntBase
	LONG	LayBase

	LABEL	Sigs
	LONG	CLISig
	LONG	UpdateSig
	LABEL	EndSigs
NumSigs	equ	(EndSigs-Sigs)/4
	LONG	AllSignals	;Logical OR of all signals

	LONG	Task
	LONG	ArgPtr
	LONG	OldDisplayBeep
	WORD	CurrentX
	WORD	CurrentY
	WORD	DampingConstant
	LONG	MouseTime	;timeout for mouse blanking
	LONG	MBlankWindow
	STRUCT	OldPointer,Ptr_SIZEOF
	LONG	ScreenTime	;for screen blanking
	LONG	ClickWindow
	LONG	ClickCount
	STRUCT	ClickTime,TV_SIZE
	LONG	InputMsgPort
	LONG	InputIORequest
	STRUCT	InputInterrupt,IS_SIZE
	STRUCT	MySemaphore,SS_SIZE

	LABEL	Options
	LONG	MBlankOption
	LONG	SBlankOption
	LONG	CmdOption
	LONG	AccelOption
	LONG	ThreshOption
	LONG	CTFOption
	LONG	ClickBorderOption
	LONG	CTBOption
	LONG	SunMouseOption
	LONG	NorthgateOption
	LONG	NoBeepOption
	LONG	NoClickOption
	LONG	StarOption
	LONG	QuitOption
	LABEL	EndOptions
NumOptions	equ	(EndOptions-Options)/4

	LABEL	OptionCashes
	LONG	MouseBlank
	LONG	ScreenBlank
	LONG	Acceleration	; Copies of parameters
	LONG	Threshold	; to avoid having to wait on semaphore
	LONG	CTF	; which could deadlock input handler
	LONG	CTFB

Template:	dc.b	'M=MBLANK/K/N,'
	dc.b	'S=SBLANK/K/N,'
	dc.b	'CMD=COMMAND/K,'
	dc.b	'A=ACCELERATION/K/N,'
	dc.b	'T=THRESHOLD/K/N,'
	dc.b	'CTF=CLICKTOFRONT/K/N,'
	dc.b	'CTFB=CLICKTOFRONTBORDER/K/N,'
	dc.b	'CTB=CLICKTOBACK/S,'
	dc.b	'SUNMOUSE/S,'
	dc.b	'NORTHGATE/S,'
	dc.b	'NOBEEP/S,'
	dc.b	'NOCLICK/K/N,'
	dc.b	'STAR/S,'
	dc.b	'QUIT/S',0

	STRUCT	ResidentCode,ResidentCodeLength
	LABEL	Data_SIZEOF

;Bit definitions for Status
	BITDEF	ST,SBlanked,0
	BITDEF	ST,SunMouse,1
	BITDEF	ST,HandlerInstalled,2
	BITDEF	ST,InputDeviceOpened,3

	STRUCTURE	MouseData,12
	LABEL	MouseData_SIZEOF

	END