Get the new PortableApps.com Platform 10.0: Gorgeous themes, a full portable app store and advanced functionality
Announcing the World's Best Flash Drive: The PortableApps.com Companion | Did you see a malware warning on Friday?

Developers: Code Snippets

wraithdu
Developer
- December 3, 2009 - 11:54pm

Here's my sample AHK low level mouse hook. The comments should be pretty self-explanatory. This is the hook I'm hoping to implement in eXpresso.

#Persistent
#NoEnv
SetBatchLines -1
ListLines off

pMouseHook := RegisterCallback("MouseHookProc", "", 3) ; LowLevelMouseProc
hMouseHook := DllCall("SetWindowsHookEx", "int", 14, "uint", pMouseHook, "uint", DllCall("GetModuleHandle", "uint", 0), "uint", 0) ; set the WH_MOUSE_LL hook
DblClick := DllCall("GetDoubleClickTime") ; gets system dbl click time
DllCall("SetDoubleClickTime", "uint", 30) ; set a very low system dbl click time
; this will allow us to programmatically send our own dbl clicks without interference from the system
; effectively disabling human dbl clicks

; window classes for dbl click subroutine
; for this example we're handling dbl clicks on the Desktop and Explorer windows
aWinClass := "Progman,CabinetWClass,ExploreWClass"
fromEnter := False ; init variable

OnExit, ExitSub ; unregister the hook on exit
Return

; catch the Enter button as well
$Enter::
Critical
fromEnter := True ; cannot use A_ThisHotKey
SetTimer, DoubleClick, -10 ; run timed sub only once
Return

MouseHookProc(nCode, wParam, lParam)
{
	Critical ; prevent this thread from being interrupted
	Global DblClick, ClickTime, hMouseHook, MouseX, MouseY, fromEnter

	; only process messages withe nCode >= 0
	; incoming callback params are UINTs, so this is the test for >= 0
	If (nCode < 0x80000000)
	{
		If (wParam = 0x201) ; WM_LBUTTONDOWN
		{
			; get the mouse position from the MSLLHOOKSTRUCT at lParam (first two elements are a POINT struct)
			xpos := NumGet(lParam+0, 0, "int") ; must pass the actual number stored in lParam by using lParam+0
			ypos := NumGet(lParam+0, 4, "int")
			If (((NumGet(lParam+0, 16, "uint") - ClickTime) < DblClick)
					And (Abs(xpos - MouseX) < 8) And (Abs(ypos - MouseY) < 8))
			{
				; a double click has occurred within a 16x16 area, trigger subroutine
				; we'll use a timer so we can get out of this callback as fast as possible
				; Critical prevents the timer from interrupting this callback before it has a chance to return
				fromEnter := False
				SetTimer, DoubleClick, -10 ; run timed sub only once
				Return 1 ; suppress the double click, we'll handle it in the timer sub
			}
			; store last click time and mouse position
			ClickTime := NumGet(lParam+0, 16, "uint") ; time stamp from struct
			MouseX := xpos
			MouseY := ypos
		}
	}
	Return DllCall("CallNextHookEx", "uint", hMouseHook, "int", nCode, "uint", wParam, "uint", lParam)
}

DoubleClick:
; this example handles double clicks in specified windows
WinGet, HWndActiveWin, ID, A ; get active window handle
WinGetClass, classActiveWin, ahk_id %HWndActiveWin% ; get active window class
MouseGetPos,,,,mouseControlClass ; get control class under mouse cursor
Loop, Parse, aWinClass, `, ; parse the string of allowed classes
{
	If (classActiveWin = A_LoopField)
	{
		; only do stuff to a matching window class
		ControlGetFocus, activeControl, A
		; if we are here because of a double click and the mouse is not over the active control
		; (like a dbl click on the title bar), then get out and let the click through
		; this allows dbl clicks on other parts of a window that don't steal focus
		If (!fromEnter And (mouseControlClass <> activeControl))
			Goto EndDblClickSub
		; check for supported controls
		IfInString, activeControl, SysListView32
		{
			; we got a list view, use an optimized method to find the selection
			; find out if anything is selected
			ControlGet, selItems, List, Selected, %activeControl%, ahk_id %HWndActiveWin%
			If (selItems <> "")
			{
				; something is selected, get the result
				Loop, Parse, selItems, `n
				{
					; rows are delimited by `n, columns by A_Tab
					; we only want the first column which has the file name
					Loop, Parse, A_LoopField, %A_Tab%
					{
						MsgBox %A_LoopField%
						Break ; only interested in first column
					}
				}
				Return ; suppress the click / enter
			}
		}
		; else use generic Ctrl+c copy processing
		newClip := ""
		oldClip := ClipboardAll ; save old clipboard
		Clipboard := "" ; empty it
		Send ^c
		ClipWait 0.15 ; wait 150 ms
		If !ErrorLevel
			newClip := Clipboard
		If (newClip <> "")
		{
			Loop, Parse, newClip, `n, `r ; parse the clipboard contents
			{
				MsgBox %A_LoopField%
			}
		}
		Clipboard := oldClip ; restore clipboard contents

		; done processing click / enter
		Return ; break the loop and suppress the input
	}
}
EndDblClickSub:
; if no matches, send the click or Enter through
If fromEnter
	Send {Enter} ; send the Enter key
Else
{
	Click ; click primary mouse button
	ClickTime :=0 ; prevent hook from seeing dbl click
	Click ; trigger the system dbl click
}
Return

ExitSub:
DllCall("UnhookWindowsHookEx", "uint", hMouseHook) ; unregister the hook
DllCall("SetDoubleClickTime", "uint", DblClick) ; reset default dbl click time
ExitApp

( categories: )

Very Nice

Thanks for posting this wraithdu. What kind of increase in accuracy have you noted with this approach? I'll definitely be looking at how I can utilize this in some of my scripts.

Again, Thanks Smiling

"If knowledge can create problems, is it not through ignorance that we can solve them." -Isaac Asimov

Not sure yet. I'm waiting

Not sure yet. I'm waiting for the source from Oliver when he's done with it. It may or may not be better than the hotkey method that I think Cafe currently uses. The only way to tell will be when someone who is having problems with the hotkey method actually tests this method. I have some other ideas to get away from the Ctrl+C (copy) that Cafe currently uses to get file paths in certain controls, that I hope will be more accurate. But I'll have to wait and see how the implementation goes in Cafe to see if it will be a viable option.

New code posted above. This

New code posted above. This will be closer to what I hope to implement in eXpresso. Note that in Win7 an Explorer window is NO LONGER a 'SysListView32' control, it is a 'DirectUIHWND' control, so we are back to using the Ctrl+c copy technique right now. On my system 150ms to process Ctrl+s seems pretty good. In eXpresso this time will be configurable. I will look into working with this new control and see if it can be directly accessed (like a SysListView32) from AHK.

Also interesting...
I use XYplorer as my at home file explorer. It is a Delphi (I think) program and does not use standard windows controls (SysListView32, etc). In older versions Ctrl+c worked to copy a file path from the list view window. This no longer works in current versions. If a file is selected Ctrl+c yields blank, and if nothing is selected XYplorer pops an error message!

I don't see any way around this at this point, so eXpresso is not likely to work in conjunction with current versions of XYplorer.