#include-once
#include <WindowsConstants.au3>
#include <WinAPI.au3>
#include <Timers.au3>
;

#CS
Name: _MouseSetOnEvent UDF, set an events handler (a hook) for Mouse device.
Usage: _MouseSetOnEvent($iEvent, $sFuncName="", $sParam1="", $sParam2="", $hTargetWnd=0, $iBlockDefProc=1)
Author: G.Sandler ((Mr)CreatoR, CreatoR's Lab - http://creator-lab.ucoz.ru, http://autoit-script.ru)
AutoIt: 3.3.0.0
Version: 1.7

ChangLog:
v1.7 [14.10.2009]
* Stability fixes. Thanks again to wraithdu.

v1.6 [13.10.2009]
* Fixed an issue with releasing the resources of mouse hook. Thanks to wraithdu.

v1.5 [09.10.2009]
+ Added wheel button up/down *scrolling* event recognition.
	Thanks to JRowe (http://www.autoitscript.com/forum/index.php?showtopic=103362).
* Fixed an issue with returning value from __MouseSetOnEvent_MainHandler - should call _WinAPI_CallNextHookEx before return.
* Constants starting with MOUSE_EXTRABUTTON* renamed to MOUSE_XBUTTON*, as it should be in the first place.
* Few examples updated.

v1.4 [30.09.2009]
+ Added UDF header to the function.
+ Now the original events-messages (such as $WM_MOUSEMOVE) can be used as well.
+ Added missing events (althought i am not sure if they are still supported)
	$MOUSE_PRIMARYDBLCLK_EVENT - Primary mouse button double click.
	$MOUSE_SECONDARYDBLCLK_EVENT - Secondary mouse button double click.
	$MOUSE_WHEELDBLCLK_EVENT - Wheel mouse button double click.
	$MOUSE_EXTRABUTTONDBLCLK_EVENT - Side mouse button double click.

* Changed global vars and internal functions names to be more unique.
* Fixed variables declaration and misspelling.

v1.3 [27.10.2008]
* Added optional parameter $iBlockDefProc - Define wether the Mouse events handler will block the default processing or not (Default is 1, block). If this is -1, then user can Return from the event function to set processing operation (see the attached example «AutoDrag Window.au3»).

v1.2 [05.04.2008]
* Added: [Optional] parameter $hTargetWnd, if set, the OnEvent function will be called only on $hTargetWnd window, otherwise will be standard Event processing.
Note: Can be a little(?) buggy when you mix different events 

v1.1 [22.03.2008]
* Fixed: Incorrect ReDim when remove event from the array, it was causing UDF to crash script with error.
* Spell/Grammar corrections 
* Added: An example of _BlockMouseClicksInput().
#CE

#Region Global Constants & Variables
If Not IsDeclared("WH_MOUSE_LL") 		Then Assign("WH_MOUSE_LL", 			14, 2)

If Not IsDeclared("WM_MOUSEMOVE") 		Then Assign("WM_MOUSEMOVE", 		0x200, 2) ;512
If Not IsDeclared("WM_LBUTTONDOWN") 	Then Assign("WM_LBUTTONDOWN", 		0x201, 2) ;513
If Not IsDeclared("WM_LBUTTONUP") 		Then Assign("WM_LBUTTONUP", 		0x202, 2) ;514
If Not IsDeclared("WM_LBUTTONDBLCLK") 	Then Assign("WM_LBUTTONDBLCLK", 	0x203, 2) ;515
If Not IsDeclared("WM_RBUTTONDOWN") 	Then Assign("WM_RBUTTONDOWN", 		0x204, 2) ;516
If Not IsDeclared("WM_RBUTTONUP") 		Then Assign("WM_RBUTTONUP", 		0x205, 2) ;517
If Not IsDeclared("WM_RBUTTONDBLCLK") 	Then Assign("WM_RBUTTONDBLCLK", 	0x206, 2) ;518
If Not IsDeclared("WM_MBUTTONDOWN") 	Then Assign("WM_MBUTTONDOWN", 		0x207, 2) ;519
If Not IsDeclared("WM_MBUTTONUP") 		Then Assign("WM_MBUTTONUP", 		0x208, 2) ;520
If Not IsDeclared("WM_MBUTTONDBLCLK") 	Then Assign("WM_MBUTTONDBLCLK", 	0x209, 2) ;521
If Not IsDeclared("WM_MOUSEWHEEL") 		Then Assign("WM_MOUSEWHEEL", 		0x20A, 2) ;522
If Not IsDeclared("WM_XBUTTONDOWN") 	Then Assign("WM_XBUTTONDOWN", 		0x20B, 2) ;523
If Not IsDeclared("WM_XBUTTONUP") 		Then Assign("WM_XBUTTONUP", 		0x20C, 2) ;524
If Not IsDeclared("WM_XBUTTONDBLCLK") 	Then Assign("WM_XBUTTONDBLCLK", 	0x20D, 2) ;525

Global Const $MOUSE_MOVE_EVENT				= 0x200 ;512
Global Const $MOUSE_PRIMARYDOWN_EVENT		= 0x201 ;513
Global Const $MOUSE_PRIMARYUP_EVENT			= 0x202 ;514
Global Const $MOUSE_PRIMARYDBLCLK_EVENT		= 0x203 ;515
Global Const $MOUSE_SECONDARYDOWN_EVENT		= 0x204 ;516
Global Const $MOUSE_SECONDARYUP_EVENT		= 0x205 ;517
Global Const $MOUSE_SECONDARYDBLCLK_EVENT	= 0x206 ;518
Global Const $MOUSE_WHEELDOWN_EVENT			= 0x207 ;519
Global Const $MOUSE_WHEELUP_EVENT			= 0x208 ;520
Global Const $MOUSE_WHEELDBLCLK_EVENT		= 0x209 ;521
Global Const $MOUSE_WHEELSCROLL_EVENT		= 0x20A ;522
Global Const $MOUSE_XBUTTONDOWN_EVENT		= 0x20B ;523
Global Const $MOUSE_XBUTTONUP_EVENT			= 0x20C ;524
Global Const $MOUSE_XBUTTONDBLCLK_EVENT		= 0x20D ;525

Global Const $MOUSE_WHEELSCROLLDOWN_EVENT	= 0x20A + 8 ;530
Global Const $MOUSE_WHEELSCROLLUP_EVENT		= 0x20A + 16 ;538

Global $__MouseSetOnEvent_aEvents[1][1]
Global $__MouseSetOnEvent_hMouseProc 		= -1
Global $__MouseSetOnEvent_hMouseHook 		= -1
Global $__MouseSetOnEvent_iTimer			= -1
Global $__MouseSetOnEvent_iEventReturn		= 1
#EndRegion Global Constants & Variables
;

; #FUNCTION# ====================================================================================================
; Name...........:	_MouseSetOnEvent
; Description....:	Set an events handler (a hook) for Mouse device.
; Syntax.........:	_MouseSetOnEvent($iEvent, $sFuncName="", $sParam1="", $sParam2="", $hTargetWnd=0, $iBlockDefProc=1)
; Parameters.....:	$iEvent 		- The event to set, here is the list of allowed events:
;										$MOUSE_MOVE_EVENT - Mouse moving.
;										$MOUSE_PRIMARYDOWN_EVENT - Primary mouse button down.
;										$MOUSE_PRIMARYUP_EVENT - Primary mouse button up.
;										$MOUSE_PRIMARYDBLCLK_EVENT - Primary mouse button double click.
;										$MOUSE_SECONDARYDOWN_EVENT - Secondary mouse button down.
;										$MOUSE_SECONDARYUP_EVENT - Secondary mouse button up.
;										$MOUSE_SECONDARYDBLCLK_EVENT - Secondary mouse button double click.
;										$MOUSE_WHEELDOWN_EVENT - Wheel mouse button pressed down.
;										$MOUSE_WHEELUP_EVENT - Wheel mouse button up.
;										$MOUSE_WHEELDBLCLK_EVENT - Wheel mouse button double click.
;										$MOUSE_WHEELSCROLL_EVENT - Wheel mouse scroll.
;										$MOUSE_WHEELSCROLLDOWN_EVENT - Wheel mouse scroll *Down*.
;										$MOUSE_WHEELSCROLLUP_EVENT - Wheel mouse scroll *Up*.
;										$MOUSE_XBUTTONDOWN_EVENT - Side mouse button down (usualy navigating next/back buttons).
;										$MOUSE_XBUTTONUP_EVENT - Side mouse button up.
;										$MOUSE_XBUTTONDBLCLK_EVENT - Side mouse button double click.
;
;					$sFuncName 		- [Optional] Function name to call when the event is triggered.
;										If this parameter is empty string (""), the function will *unset* the $iEvent.
;					$sParam1 		- [Optional] First parameter to pass to the event function ($sFuncName).
;					$sParam2 		- [Optional] Second parameter to pass to the event function ($sFuncName).
;					$hTargetWnd 	- [Optional] Window handle to set the event for, e.g the event is set only for this window.
;					$iBlockDefProc 	- [Optional] Defines if the event should be blocked (actualy block the mouse action).
;					
; Return values..:	Success 		- Returns 1.
;					Failure 		- Returns 0 on UnSet event mode and when there is no set events yet.
; Author.........:	G.Sandler ((Mr)CreatoR, CreatoR's Lab - http://creator-lab.ucoz.ru, http://autoit-script.ru)
; Modified.......:	
; Remarks........:	1) The original events-messages (such as $WM_MOUSEMOVE) can be used as well.
;					2) Blocking of $sFuncName function by window messages with commands
;                     such as "Msgbox()" can lead to unexpected behavior, the return to the system should be as fast as possible!
; Related........:	
; Link...........:	http://www.autoitscript.com/forum/index.php?showtopic=64738
; Example........:	Yes.
; ===============================================================================================================
Func _MouseSetOnEvent($iEvent, $sFuncName="", $sParam1="", $sParam2="", $hTargetWnd=0, $iBlockDefProc=1)
	If $sFuncName = "" Then ;Unset Event
		If $__MouseSetOnEvent_aEvents[0][0] < 1 Then Return 0
		Local $aTmp_Mouse_Events[1][1]
		
		For $i = 1 To $__MouseSetOnEvent_aEvents[0][0]
			If $__MouseSetOnEvent_aEvents[$i][0] <> $iEvent Then
				$aTmp_Mouse_Events[0][0] += 1
				ReDim $aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]+1][6]
				
				$aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]][0] = $__MouseSetOnEvent_aEvents[$i][0]
				$aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]][1] = $__MouseSetOnEvent_aEvents[$i][1]
				$aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]][2] = $__MouseSetOnEvent_aEvents[$i][2]
				$aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]][3] = $__MouseSetOnEvent_aEvents[$i][3]
				$aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]][4] = $__MouseSetOnEvent_aEvents[$i][4]
				$aTmp_Mouse_Events[$aTmp_Mouse_Events[0][0]][5] = $__MouseSetOnEvent_aEvents[$i][5]
			EndIf
		Next
		
		$__MouseSetOnEvent_aEvents = $aTmp_Mouse_Events
		
		If $__MouseSetOnEvent_aEvents[0][0] < 1 Then
			If $__MouseSetOnEvent_iEventReturn = 1 Then
				OnAutoItExit()
			ElseIf $__MouseSetOnEvent_iEventReturn = 0 Then
				$__MouseSetOnEvent_iTimer = _Timer_SetTimer(0, 10, "__MouseSetOnEvent_WaitHookReturn")
			EndIf
		EndIf
		
		Return 1
	EndIf
	
	If $__MouseSetOnEvent_aEvents[0][0] < 1 Then
		$__MouseSetOnEvent_hMouseProc = DllCallbackRegister("__MouseSetOnEvent_MainHandler", "int", "int;ptr;ptr")
		
		Local $hMHook_Module = _WinAPI_GetModuleHandle(0)
		$__MouseSetOnEvent_hMouseHook = _WinAPI_SetWindowsHookEx(Eval("WH_MOUSE_LL"), _
			DllCallbackGetPtr($__MouseSetOnEvent_hMouseProc), $hMHook_Module, 0)
	EndIf
	
	$__MouseSetOnEvent_aEvents[0][0] += 1
	ReDim $__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]+1][6]
	
	$__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]][0] = $iEvent
	$__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]][1] = $sFuncName
	$__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]][2] = $sParam1
	$__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]][3] = $sParam2
	$__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]][4] = $hTargetWnd
	$__MouseSetOnEvent_aEvents[$__MouseSetOnEvent_aEvents[0][0]][5] = $iBlockDefProc
	
	Return 1
EndFunc

Func __MouseSetOnEvent_MainHandler($nCode, $wParam, $lParam)
	If $nCode < 0 Then Return _WinAPI_CallNextHookEx($__MouseSetOnEvent_hMouseHook, $nCode, $wParam, $lParam) ;Continue processing
	
	Local Const $stMSLLHOOKSTRUCT = $tagPOINT & ";dword mouseData;dword flags;dword time;ulong_ptr dwExtraInfo"
	Local $iEvent = _WinAPI_LoWord($wParam)
	Local $iRet, $iBlockDefProc_Ret
	
	If $__MouseSetOnEvent_aEvents[0][0] < 1 Then Return 0
	
	For $i = 1 To $__MouseSetOnEvent_aEvents[0][0]
		;Compare with standard event, or with wheel *Scroll* Down and wheel *Scroll* Up events
		If $__MouseSetOnEvent_aEvents[$i][0] = $iEvent Or _
			$__MouseSetOnEvent_aEvents[$i][0] = $iEvent+8 Or $__MouseSetOnEvent_aEvents[$i][0] = $iEvent+16 Then
			
			If $iEvent <> $__MouseSetOnEvent_aEvents[$i][0] Then
				Local $tWheel_Struct = DllStructCreate($stMSLLHOOKSTRUCT, $lParam)
				Local $sWheelScroll_Data = DllStructGetData($tWheel_Struct, 3)
				
				If _WinAPI_HiWord($sWheelScroll_Data) > 0 Then
					If $iEvent+16 <> $__MouseSetOnEvent_aEvents[$i][0] Then ContinueLoop ;Scroll Up event, but not matching
				Else
					If $iEvent+8 <> $__MouseSetOnEvent_aEvents[$i][0] Then ContinueLoop ;Scroll Down event, but not matching
				EndIf
			EndIf
			
			If $__MouseSetOnEvent_aEvents[$i][4] <> 0 And _
				Not __MouseSetOnEvent_IsHoveredWnd($__MouseSetOnEvent_aEvents[$i][4]) Then Return 0 ;Allow default processing
			
			$__MouseSetOnEvent_iEventReturn = 0
			$iBlockDefProc_Ret = $__MouseSetOnEvent_aEvents[$i][5]
			
			$iRet = Call($__MouseSetOnEvent_aEvents[$i][1], $__MouseSetOnEvent_aEvents[$i][2], $__MouseSetOnEvent_aEvents[$i][3])
			If @error Then $iRet = Call($__MouseSetOnEvent_aEvents[$i][1], $__MouseSetOnEvent_aEvents[$i][2])
			If @error Then $iRet = Call($__MouseSetOnEvent_aEvents[$i][1])
			
			$__MouseSetOnEvent_iEventReturn = 1
			
			If $iBlockDefProc_Ret = -1 Then Return $iRet
			Return $iBlockDefProc_Ret ;Block default processing (or not :))
		EndIf
	Next
	
	Return _WinAPI_CallNextHookEx($__MouseSetOnEvent_hMouseHook, $nCode, $wParam, $lParam) ;Continue processing
EndFunc

Func __MouseSetOnEvent_WaitHookReturn($hWnd, $Msg, $iIDTimer, $dwTime)
	If $__MouseSetOnEvent_iEventReturn = 1 Then
		_Timer_KillTimer(0, $__MouseSetOnEvent_iTimer)
		OnAutoItExit()
	EndIf
EndFunc

Func __MouseSetOnEvent_IsHoveredWnd($hWnd)
	Local $iRet = False
	
	Local $aWin_Pos = WinGetPos($hWnd)
	Local $aMouse_Pos = MouseGetPos()
	
	If $aMouse_Pos[0] >= $aWin_Pos[0] And $aMouse_Pos[0] <= ($aWin_Pos[0] + $aWin_Pos[2]) And _
		$aMouse_Pos[1] >= $aWin_Pos[1] And $aMouse_Pos[1] <= ($aWin_Pos[1] + $aWin_Pos[3]) Then $iRet = True
	
	Local $aRet = DllCall("User32.dll", "int", "WindowFromPoint", _
		"long", $aMouse_Pos[0], _
		"long", $aMouse_Pos[1])
	
	If HWnd($aRet[0]) <> $hWnd And Not $iRet Then $iRet = False
	
	Return $iRet
EndFunc

Func OnAutoItExit()
	If $__MouseSetOnEvent_hMouseHook <> -1 Then
		_WinAPI_UnhookWindowsHookEx($__MouseSetOnEvent_hMouseHook)
		$__MouseSetOnEvent_hMouseHook = -1
	EndIf
	
	If $__MouseSetOnEvent_hMouseProc <> -1 Then
		DllCallbackFree($__MouseSetOnEvent_hMouseProc)
		$__MouseSetOnEvent_hMouseProc = -1
	EndIf
EndFunc
