
*	MMBShift
*	By Preben Nielsen
*
*	This small program makes the middle button (on a three-button mouse)
*       act as a shift key. This means that it will be very easy to select
*       multiple icons on the workbench without having to press a Shift-Key.
*
*	Once installed, the program only uses 166 bytes of memory.
*	To get back to normal simply run the program again.
*
*	NOTE:	There's no need to 'RUN' or 'RUNBACK' this program from the
*		CLI. It terminates immediately.
*
*HISTORY
*          Made with Hisoft V2.12
*
*  V1.0   01-Sep-91: First attempt. Works of course.


	OPT O+
	OPT O1+			; Tells when a branch could be optimised to short
	OPT i+			; Tells when '#' is probably missing

		incdir		"AsmInc:"
		include		"P.i"
		include		"relMacros.i"
		include		"exec/exec_lib.i"
		include		"exec/io.i"
		include		"exec/memory.i"
		include		"exec/interrupts.i"
		include		"devices/input.i"
		include		"devices/inputevent.i"
		include		"libraries/dosextens.i"
		include		"libraries/dos_lib.i"

DB		EQUR		A4
 dcDeclare	A4
 dcAPtr		PProcess
 dcAPtr		WBMsg
 dcAPtr		DosBase
 dcAPtr		HMem
 dcEnd

Start		dcAlloc					; Allocate memory for variables
		dcReset					; Clear the memory
		Prepare		Exec_Call
		suba.l		A1,A1
		CallLib		FindTask		; Find us
		move.l		D0,PProcess(DB)
		movea.l		D0,A2
		tst.l		pr_CLI(A2)
		bne.S		GetLibs
WBStart		lea		pr_MsgPort(A2),A0
		CallLib		WaitPort		; wait for a message
		lea		pr_MsgPort(A2),A0
		CallLib		GetMsg			; then get it
		move.l		D0,WBMsg(DB)		; save it for later reply
GetLibs		lea		DosName(PC),A1
		CallLib		OldOpenLibrary
		move.l		D0,DosBase(DB)
		beq.S		Error
		lea		IHSName(PC),A1
		Call		FindThisPort
		beq.S		DoInstall
DoRemove	moveq		#REMOVED,D7
		lea		IHS(PC),A0
		lea		PSEndIHS1(PC),A1
		lea		PSEndIHS2(PC),A2
		Call		RemoveHandler
		beq.S		ShowMsg
		moveq		#CANTREMOVE,D7
		bra.S		ShowMsg
DoInstall	moveq		#INSTALLED,D7
		lea		IHS(PC),A0
		lea		PSPrepIHS1(PC),A1
		lea		PSPrepIHS2(PC),A2
		Call		InstallHandler
		beq.S		ShowMsg
		moveq		#CANTINSTALL,D7
ShowMsg		move.l		D7,D0
		Call		CONMsg

Error
Exit		Prepare		Exec_Call
FreeDos		move.l		DosBase(DB),D0
		beq.S		ReplyWB
		move.l		D0,A1
		CallLib		CloseLibrary
ReplyWB		move.l		WBMsg(DB),D2
		beq.S		AllDone
		CallLib		Forbid			; We were started from WB
		movea.l		D2,A1
		CallLib		ReplyMsg		; Reply WBMessage
AllDone		dcFree
		moveq		#0,D0
		rts

*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»» Console message routines »»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
FHandle		EQUR		D5
*»»» Call:	D0 = Msg-number
*»»» Writes a general copyright message and then a more specific message.
*»»» Writes to standard output when run from CLI, and opens its own window
*»»» when run from Workbench.
CONMsg		Push		D0-D7/A0-A6
		Prepare		Dos_Call
		move.l		D0,D4
		moveq		#0,D6
		CallLib		Output
		move.l		D0,FHandle
		bne.S		1$
		moveq		#1,D6
		lea		CONName(PC),A0
		move.l		A0,D1
		move.l		#MODE_OLDFILE,D2
		CallLib		Open
		move.l		D0,FHandle
		beq.S		2$
1$		moveq		#INFOMSG,D0
		Call		SendMsg
		move.l		D4,D0
		Call		SendMsg
		tst.l		D6
		beq.S		2$
		moveq		#127,D1
		CallLib		Delay
		move.l		FHandle,D1
		CallLib		Close
2$		Pop		D0-D7/A0-A6
		rts

*»»» Call:	D0 = Msg-number
SendMsg		neg.l		D0
		lsl.l		#1,D0
		lea		CONMsgTable(PC),A0
		add.w		0(A0,D0),A0
		move.l		A0,D2
		moveq		#-1,D3
1$		addq.l		#1,D3
		tst.b		(A0)+
		bne.S		1$
		move.l		FHandle,D1
		Prepare		Dos_Call
		CallLib		Write
		rts

INFOMSG		=0
INSTALLED	=-1
REMOVED		=-2
CANTINSTALL	=-3
CANTREMOVE	=-4

CONText		MACRO
		dc.w		\1-CONMsgTable
		ENDM
CONMsgTable	CONText		Msg
		CONText		Msg1
		CONText		Msg2
		CONText		Msg3
		CONText		Msg4

CONName		dc.b		'CON:100/60/330/63/MMBShift',0
Msg		dc.b		10,$9B,'0;33m MMBShift V1.0',10
		dc.b		$9B,'0;31m 1991 by ',$9B,'0;33mPreben Nielsen',$9B,'0;31m',10,' ',0
Msg1		dc.b		'has now been installed...',10,0
Msg2		dc.b		'has now been removed...',10,0
Msg3		dc.b		"Error: Can't install handler",10,0
Msg4		dc.b		"Error: Can't remove handler",10,0
		EVEN
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»» End of Console message routines »»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»

*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»» Inputhandler removal and installation routines »»»»»»»»»»»»»»»»»»»»»»»»
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
rtsValue	EQUR		D7
*»»» This is general-purpose inputhandler removal-routine
*»»» It only needs an ihs with a port-name to remove the handler
*»»» Call:	A0 = ihs
*»»»		A1 = first ihs-installation-routine or NULL
*»»»		A2 = second ihs-installation-routine or NULL
*»»» Return:	D0 = 0 means succes
RemoveHandler	Push		D1/rtsValue/A0-A3/A6
		moveq		#-1,rtsValue
		move.l		A2,A3
		move.l		A0,A2
		move.l		A1,D1
		beq.S		1$
		jsr		(A1)		; A0 = ihs
		beq.S		2$
		move.l		D0,A2
1$		move.l		A2,A0
		Prepare		Exec_Call
		moveq		#IND_REMHANDLER,D0
		Call		TellInputDevice
		move.l		D0,rtsValue
		bne.S		2$
		lea		ihs_Port(A2),A1
		CallLib		RemPort
		moveq		#0,D0
		bra.S		3$
2$		moveq		#-1,D0
3$		move.l		A3,D1
		beq.S		4$
		move.l		A2,A0
		jsr		(A3)		; A0 = ihs, D0 = 0 means succes
4$		move.l		rtsValue,D0
		Pop		D1/rtsValue/A0-A3/A6
		rts

*»»» This is general-purpose inputhandler installation-routine
*»»» It only needs an ihs with a port-name to install the handler
*»»» Call:	A0 = ihs
*»»»		A1 = first ihs-installation-routine or NULL
*»»»		A2 = second ihs-installation-routine or NULL
*»»» Return:	D0 = 0 means succes
InstallHandler	Push		D1/rtsValue/A0-A3/A6
		moveq		#-1,rtsValue
		move.l		A2,A3
		move.l		A0,A2
		move.l		A1,D1
		beq.S		1$
		jsr		(A1)		; A0 = ihs
		beq.S		2$
		move.l		D0,A2
1$		move.l		A2,A0
		moveq		#IND_ADDHANDLER,D0
		Call		TellInputDevice
		move.l		D0,rtsValue
		bne.S		2$
		lea		ihs_Port(A2),A1
		move.l		ihs_PortName(A2),MP+LN_NAME(A1)	;MsgPort->mp_Node.ln_Name=Name;
		clr.b		MP+LN_PRI(A1)			;MsgPort->mp_Node.ln_Pri =0;
		move.b		#NT_MSGPORT,MP+LN_TYPE(A1)	;MsgPort->mp_Node.ln_Type=NT_MSGPORT;
		move.b		#PA_IGNORE,MP_FLAGS(A1)		;MsgPort->mp_Flags	 =PA_IGNORE;
		Prepare		Exec_Call
		CallLib		AddPort
		moveq		#0,D0
		bra.S		3$
2$		moveq		#-1,D0
3$		move.l		A3,D1
		beq.S		4$
		move.l		A2,A0
		jsr		(A3)		; A0 = ihs, D0 = 0 means succes
4$		move.l		rtsValue,D0
		Pop		D1/rtsValue/A0-A3/A6
		rts

*»»» Open the input device. Set up the I/O block to add or remove the
*»»» input handler, and send the request to the input device. Finally,
*»»» close the device
*»»» Call:	A0 = ihs
*»»»		D0 = Function to perform (IND_ADDHANDLER/IND_REMHANDLER)
*»»» Return:	D0 = 0 means succes
 dcDeclare	A5
 dcArea		IReq,IOSTD_SIZE
 dcArea		IPort,MP_SIZE
 dcEnd
TellInputDevice	dcAlloc
		dcReset
		Push		D1-D2/rtsValue/A0-A3/A6
		Prepare		Exec_Call
		moveq		#-1,rtsValue
		move.l		D0,D2
		move.l		A0,A2
		lea		IPort(A5),A3
		move.b		#NT_MSGPORT,MP+LN_TYPE(A3)	; mp_Node.ln_Type=NT_MSGPORT;
		move.b		#PA_SIGNAL,MP_FLAGS(A3)		; mp_Flags	=PA_SIGNAL;
		moveq		#-1,D0
		CallLib		AllocSignal
		move.b		D0,MP_SIGBIT(A3)		; mp_SigBit	=MPSigBit;
		bmi.S		2$
		suba.l		A1,A1
		CallLib		FindTask
		move.l		D0,MP_SIGTASK(A3)		; mp_SigTask 	=FindTask(0);
		lea		MP_MSGLIST(A3),A0
		NEWLIST		A0
		lea		IReq(A5),A1
		move.l		A3,IO+MN_REPLYPORT(A1)		; ExtReq->io_Message.mn_ReplyPort   =taskReplyPort;
		move.b		#NT_MESSAGE,IO+MN+LN_TYPE(A1)	; ExtReq->io_Message.mn_Node.ln_Type=NT_MESSAGE;
		lea		InputName(PC),A0		; input.device
		moveq		#0,D0				; unit#
		moveq		#0,D1				; flags
		CallLib		OpenDevice
		tst.w		D0				; flag: error if > 0
		bne.S		1$
		lea		IReq(A5),A1
		move.w		D2,IO_COMMAND(A1)
		lea		ihs_Interrupt(A2),A0
		move.l		A0,IO_DATA(A1)
		CallLib		DoIO
		move.l		D0,rtsValue
		lea		IReq(A5),A1
		CallLib		CloseDevice
1$		move.b		MP_SIGBIT(A3),D0
		CallLib		FreeSignal
2$		move.l		rtsValue,D0
		Pop		D1-D2/rtsValue/A0-A3/A6
		dcFree
		rts

*»»» Each handler should have such a pair of installation-routines.
*»»» The first one is passed to InstallHandler in A1, and it
*»»» is called immediately when entering InstallHandler.
*»»» The second one is passed to InstallHandler in A2, and it
*»»» is called if installation of handler and message-port succeeds.
*»»» -----------------------------------------------------------------
*»»» Call:	A0 = ihs
*»»» Return:	D0 has to point to ihs to be used when installation proceeds.
*»»»		If D0 = 0 then installation is aborted.
PSPrepIHS1	Push		D1/A0-A1/A6
		Prepare		Exec_Call
		move.l		#HandlerSize,D0
		move.l		#MEMF_PUBLIC|MEMF_CLEAR,D1
		CallLib		AllocMem
		move.l		D0,HMem(DB)
		beq.S		2$
		move.l		D0,A1
		lea		IHS(PC),A0
		move.l		#HandlerSize,D0
		Push		D0/A0-A1
		CallLib		CopyMem
		Pop		D0/A0-A1
		move.l		D0,ihs_Length(A1)		; This will enable removal by other programs
		lea		HandlerCode-IHS(A1),A0
		move.l		A0,ihs_Interrupt+IS_CODE(A1)	; ihs_Interrupt.is_Code = Handler
		clr.l		ihs_Interrupt+IS_DATA(A1)	; ihs_Interrupt.is_Data = 0
		lea		IHSName-IHS(A1),A0
		move.l		A0,ihs_PortName(A1)		; ihs_PortName          = IHSName
		move.b		#HPRI,ihs_Interrupt+LN_PRI(A1)	; ihs_Interrupt.is_Node.ln_Pri = HPRI
		move.l		A1,D0
2$		Pop		D1/A0-A1/A6
		rts
*»»» Call:	A0 = ihs
*»»»		D0 = 0 means everything went perfect
*»»»		-1 means something went wrong during installation
PSPrepIHS2	Push		D0-D1/A0-A1/A6
		tst.l		D0
		beq.S		1$
		move.l		HMem(DB),D0
		beq.S		1$
		move.l		D0,A1
		move.l		#HandlerSize,D0
		Prepare		Exec_Call
		CallLib		FreeMem
1$		Pop		D0-D1/A0-A1/A6
		rts

*»»» Each handler should have such a pair of ending-routines.
*»»» The first one is passed to RemoveHandler in A1, and it
*»»» is called immediately when entering RemoveHandler.
*»»» The second one is passed to RemoveHandler in A2, and it
*»»» is called if removal of handler and message-port succeeds.
*»»» -----------------------------------------------------------------
*»»» Call:	A0 = ihs
*»»» Return:	D0 has to point to ihs to be used when removal proceeds
*»»»		If D0 = 0 then removal is aborted
PSEndIHS1	Push		A1
		lea		IHSName(PC),A1
		Call		FindThisPort
		Pop		A1
		rts
*»»» Call:	A0 = ihs
*»»»		D0 = 0 means everything went perfect
*»»»		-1 means something went wrong during removal
PSEndIHS2	Push		D0-D1/A0-A1/A6
		tst.l		D0
		bmi.S		1$
		Prepare		Exec_Call
		move.l		ihs_Length(A0),D0
		move.l		A0,A1
		CallLib		FreeMem
1$		Pop		D0-D1/A0-A1/A6
		rts

*»»» Call:	A1 = Portname
*»»» Return:	D0 = Port or NULL (cc reflects result also)
FindThisPort	Push		D1-D2/A0-A1/A6
		Prepare		Exec_Call
		move.l		A1,D2
		CallLib		Forbid
		move.l		D2,A1
		CallLib		FindPort
		move.l		D0,D2
		CallLib		Permit
		move.l		D2,D0
		Pop		D1-D2/A0-A1/A6
		rts

*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»» Input-handler start »»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
ihs_Port	=0
ihs_Interrupt	=MP_SIZE
ihs_ID		=MP_SIZE+IS_SIZE
ihs_Length	=MP_SIZE+IS_SIZE+4
ihs_Flags	=MP_SIZE+IS_SIZE+8
ihs_PortName	=MP_SIZE+IS_SIZE+10
ihs_LocalArea	=MP_SIZE+IS_SIZE+14
ihs_SIZE	=MP_SIZE+IS_SIZE+14

ihs_Start	MACRO
		dcb.b		MP_SIZE		; Message-Port structure
		dcb.b		IS_SIZE		; Interrupt structure
		dc.l		'P_IH'		; ID
		dc.l		0		; Length of handler 
		dc.w		0		; Flags
		dc.l		0
		ENDM

HPRI		=55
HDisabled	=0
HNoExtRemoval	=1

*»»» This is the handler-block
IHS		ihs_Start
*»»» Put local data here
Shifted		dc.w		0
*»»» IHS name
IHSName		dc.b		'MMBShift V1.0 Port',0
		EVEN
*»»» For each event in the event list:
*»»»  If we were waiting for this event then signal the task.
*»»» When all the events have been checked, return the event list so that
*»»» others can do their things.
EList		EQUR	A0
Event		EQUR	A1

Next		=ie_NextEvent
Class		=ie_Class
Code		=ie_Code
Qual		=ie_Qualifier

* Call:  A0 = List of InputEvents, A1 = HandlerData
HandlerCode	Push		Event/EList/A2
		move.l		EList,Event
ieLoop		move.l		Event,D0
		beq.S		NoMoreEvents
		cmpi.b		#IECLASS_RAWMOUSE,Class(Event)
		bne.S		DontRemove
		cmpi.w		#IECODE_MBUTTON,Code(Event)
		beq.S		PressMMB
		cmpi.w		#IECODE_UP_PREFIX|IECODE_MBUTTON,Code(Event)
		bne.S		DontRemove
ReleaseMMB	moveq		#0,D0
		bra.S		ChangeStatus
PressMMB	clr.w		Class(Event)			; Remove the event (make it an IECLASS_NULL event)
		moveq		#1,D0
ChangeStatus	lea		Shifted(PC),A2
		move.w		D0,(A2)
* Just move on to next Event
DontRemove	move.w		Shifted(PC),D0
		beq.S		1$
		ori.w		#IEQUALIFIER_LSHIFT,Qual(Event)	; Set a SHIFT qualifier
1$		move.l		Next(Event),Event
		bra.S		ieLoop
* Lets return
NoMoreEvents	move.l		EList,D0
		Pop		Event/EList/A2
		rts
HandlerSize	=		*-IHS
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»» Input-handler end »»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»

*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»» Data-definition start»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»
*»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»

DosName		dc.b		'dos.library',0
InputName	dc.b		'input.device',0
		END

