WildByDesign Posted October 24 Posted October 24 (edited) I'm just wondering if there is a way to get the window classname of anything that I click on with the mouse. I have something going that mostly does what I need but it's using a hook. I'd rather not use a hook if there is a simpler way to achieve this particular task. Although obtaining the window classname on every single mouse click might end up being heavier than a hook. I'm just not sure and have zero experience in mouse click related functions in AutoIt. Thank you. Updated Description: Basically, anytime the user clicks on the desktop (therefore Progman classname), a function is run. Currently, I am using a WinEventHook and actions are being taken based on EVENT_OBJECT_FOCUS events (EVENT_SYSTEM_FOREGROUND works just as well). Anytime Progman becomes the Active window, a function is run. This is good. However, the problem is that in order to be able to run that function again, I have to click on another window (eg. Taskbar) and then click back on the desktop. I would like to be able to click on the desktop several times in a row to run the function without having to click back and forth to another window each time. Therefore, in that regard, the WinEventHook method is not doing what I need 100%. Edited October 24 by WildByDesign
ioa747 Posted October 24 Posted October 24 (edited) #include <WinAPISysWin.au3> Global $g_hActWnd While Sleep(50) _GetActiveWindow() WEnd Func _GetActiveWindow() Local $AnyWindow, $sWindowTitle, $sClassName $AnyWindow = WinGetHandle("[ACTIVE]") If $g_hActWnd <> $AnyWindow Then $g_hActWnd = $AnyWindow ConsoleWrite("$g_hActWnd=" & $g_hActWnd & @CRLF) $sWindowTitle = WinGetTitle($g_hActWnd) ConsoleWrite("$sWindowTitle=" & $sWindowTitle & @CRLF) $sClassName = _WinAPI_GetClassName($g_hActWnd) ConsoleWrite("$sClassName=" & $sClassName & @CRLF) ConsoleWrite("" & @CRLF) EndIf EndFunc ;==>_GetActiveWindow Edited October 24 by ioa747 WildByDesign 1 I know that I know nothing
Nine Posted October 24 Posted October 24 (edited) I have done some work around this quite some times ago : expandcollapse popup#include <WinAPI.au3> Opt("MustDeclareVars", True) HotKeySet("{ESC}", Terminate) Example() Func Example() Local $hWnd, $hRoot, $hWnd2, $hWnd_Old = -1, $tPoint While Sleep(50) $tPoint = _WinAPI_GetMousePos() $hWnd = _WinAPI_WindowFromPoint($tPoint) $hWnd2 = GetRealChild($hWnd) If $hWnd2 And $hWnd <> $hWnd2 Then $hWnd = $hWnd2 If $hWnd <> $hWnd_Old Then ConsoleWrite(_WinAPI_GetClassName($hWnd) & @CRLF) $hWnd_Old = $hWnd EndIf WEnd EndFunc ;==>Example Func Terminate() Exit EndFunc ;==>Terminate Func _WinAPI_RealChildWindowFromPoint($hWnd, $tPoint) Local $aRet = DllCall('user32.dll', 'hwnd', 'RealChildWindowFromPoint', 'hwnd', $hWnd, 'struct', $tPoint) If @error Then Return SetError(@error, @extended, 0) Return $aRet[0] EndFunc ;==>_WinAPI_RealChildWindowFromPoint Func GetRealChild($hWnd) Local $tPoint, $hRoot = _WinAPI_GetAncestor($hWnd, $GA_ROOT) If $hWnd = $hRoot Then $tPoint = _WinAPI_GetMousePos(True, $hWnd) Return _WinAPI_ChildWindowFromPointEx($hWnd, $tPoint) EndIf Local $hParent = _WinAPI_GetAncestor($hWnd, $GA_PARENT) Local $aChild = _WinAPI_EnumChildWindows($hParent) If @error Then Return 0 Local $hFound For $i = 1 To $aChild[0][0] $hParent = _WinAPI_GetParent($aChild[$i][0]) $tPoint = _WinAPI_GetMousePos(True, $hParent) $hFound = _WinAPI_RealChildWindowFromPoint($hParent, $tPoint) If $hFound = $aChild[$i][0] Then Return $hFound Next Return 0 EndFunc ;==>GetRealChild ps. It works on mouse hover, you will need to adapt it for mouse click... Edited October 24 by Nine WildByDesign 1 “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
WildByDesign Posted October 24 Author Posted October 24 12 minutes ago, Nine said: ps. It works on mouse hover, you will need to adapt it for mouse click... Thank you. This is quite beautiful and works incredibly well. I think that the only thing that I really need to do for my needs is combine yours with checking the active window (similar to @ioa747's example) because that active window would have been clicked at some point anyway to become active. Well, not necessarily I suppose. But active window combined with your example should do what I need. I'll try to integrate it into my project and will post back here on how it goes.
WildByDesign Posted October 24 Author Posted October 24 I'm kind of brainstorming right now. I assume that I cannot use WM_MOUSEACTIVATE since it's not my GUI window. It's any other running and visible windows, so I don't think that would work.
WildByDesign Posted October 24 Author Posted October 24 I realize that my first post doesn't explain very well what the goal is. I will update the OP as well. Basically, anytime the user clicks on the desktop (therefore Progman classname), a function is run. Currently, I am using a WinEventHook and actions are being taken based on EVENT_OBJECT_FOCUS events (EVENT_SYSTEM_FOREGROUND works just as well). Anytime Progman becomes the Active window, a function is run. This is good. However, the problem is that in order to be able to run that function again, I have to click on another window (eg. Taskbar) and then click back on the desktop. I would like to be able to click on the desktop several times in a row to run the function without having to click back and forth to another window each time. Therefore, in that regard, the WinEventHook method is not doing what I need 100%.
WildByDesign Posted October 24 Author Posted October 24 OK so I have something that is working right now for the most part. It's only capturing left mouse button click at the moment. I will add more later. This is really quite incredible and I can see this functionality benefiting me in a few projects. I have one problem. For this particular project, I only need the top-level window. For example, when I click on most areas of Notepad, it gives me the Edit control handle which makes sense since that is what covers most of it. If I click on the titlebar of Notepad, it gives me Notepad class handle. How can I get it to show the top-level window only? expandcollapse popup#include <Misc.au3> #include <MsgBoxConstants.au3> #include <WinAPIvkeysConstants.au3> #include <WinAPI.au3> HotKeySet("{ESC}", Terminate) Example() Func Example() Local $hWnd, $hRoot, $hWnd2, $hWnd_Old = -1, $tPoint Local $asModifierKeys[6] = [0, "VK_SHIFT", "VK_CONTROL", "VK_MENU", "VK_LWIN", "VK_RWIN"] Local $aKeys[1] = [$VK_LBUTTON] While 1 Local $iRet = _IsPressed($aKeys, Default, True) ; Check modifier If Not $iRet And @extended Then ConsoleWrite("The modifier key " & $asModifierKeys[@extended] & " has been pressed. @extended = " & @extended & @CRLF) Local $sKey Switch $iRet Case 1 ; MouseClick Left $sKey = "{LBUTTON}" $tPoint = _WinAPI_GetMousePos() $hWnd = _WinAPI_WindowFromPoint($tPoint) $hWnd2 = GetRealChild($hWnd) If $hWnd2 And $hWnd <> $hWnd2 Then $hWnd = $hWnd2 If $hWnd <> $hWnd_Old Then ConsoleWrite(_WinAPI_GetClassName($hWnd) & @CRLF) $hWnd_Old = $hWnd EndIf EndSwitch Sleep(100) WEnd EndFunc Func Terminate() Exit EndFunc ;==>Terminate Func _WinAPI_RealChildWindowFromPoint($hWnd, $tPoint) Local $aRet = DllCall('user32.dll', 'hwnd', 'RealChildWindowFromPoint', 'hwnd', $hWnd, 'struct', $tPoint) If @error Then Return SetError(@error, @extended, 0) Return $aRet[0] EndFunc ;==>_WinAPI_RealChildWindowFromPoint Func GetRealChild($hWnd) Local $tPoint, $hRoot = _WinAPI_GetAncestor($hWnd, $GA_ROOT) If $hWnd = $hRoot Then $tPoint = _WinAPI_GetMousePos(True, $hWnd) Return _WinAPI_ChildWindowFromPointEx($hWnd, $tPoint) EndIf Local $hParent = _WinAPI_GetAncestor($hWnd, $GA_PARENT) Local $aChild = _WinAPI_EnumChildWindows($hParent) If @error Then Return 0 Local $hFound For $i = 1 To $aChild[0][0] $hParent = _WinAPI_GetParent($aChild[$i][0]) $tPoint = _WinAPI_GetMousePos(True, $hParent) $hFound = _WinAPI_RealChildWindowFromPoint($hParent, $tPoint) If $hFound = $aChild[$i][0] Then Return $hFound Next Return 0 EndFunc ;==>GetRealChild
Solution Nine Posted October 24 Solution Posted October 24 Then you do not need all of it, just get ancestor : #include <WinAPI.au3> Opt("MustDeclareVars", True) HotKeySet("{ESC}", Terminate) Example() Func Example() Local $hWnd, $hWnd_Old = -1, $tPoint While Sleep(50) $tPoint = _WinAPI_GetMousePos() $hWnd = _WinAPI_GetAncestor(_WinAPI_WindowFromPoint($tPoint), $GA_ROOT) If $hWnd <> $hWnd_Old Then ConsoleWrite(_WinAPI_GetClassName($hWnd) & @CRLF) $hWnd_Old = $hWnd EndIf WEnd EndFunc ;==>Example That should do it, I think... “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
WildByDesign Posted October 24 Author Posted October 24 3 hours ago, Nine said: That should do it, I think... Well this is absolutely phenomenal! Short, sweet and fast piece of code. Thank you for your time.
junkew Posted October 25 Posted October 25 Some other topics you could look into all with their own pro and cons. With IUIAutomation you can "subscribe" to these events a little bit higher then diving into the system hooks which are relatively hard without a separate dll doing the work. There are many examples on the MS IUIAutomation on determining windows and catching events. WildByDesign 1 FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets
WildByDesign Posted October 26 Author Posted October 26 23 hours ago, junkew said: With IUIAutomation you can "subscribe" to these events a little bit higher then diving into the system hooks which are relatively hard without a separate dll doing the work. This sounds really interesting. I will spend some time today learning some more about IUIAutomation in general. And the great thing about this forum is the fact that there is a gold mine of examples throughout the forum and so many users willing to help. Thank you. argumentum 1
WildByDesign Posted October 27 Author Posted October 27 I was having some inaccuracies with IsPressed() which, of course, entirely depended on the amount of time in the Sleep() function in the While loop. Setting it too low could end up getting multiple assumed clicks on a single click and setting it too high could miss clicks. So along with the inaccuracies, I also decided that I wanted to capture double-clicks. It's easier if you own the GUI, but in this case it's windows that are not part of my process. I ended up using a WinEventHook that can capture single and double clicks accurately on any other windows. In my example, I am specifically only capturing clicks on the desktop (Progman class). It uses the system tick count to measure time. expandcollapse popup#include <GuiMenu.au3> #include <SendMessage.au3> #include <WinAPIGdi.au3> #include <WinAPIMisc.au3> #include <WinAPIProc.au3> #include <WinAPISys.au3> #include <WindowsNotifsConstants.au3> #include <WinAPISysWin.au3> Local $hEventProc = DllCallbackRegister('_EventProc', 'none', 'ptr;dword;hwnd;long;long;dword;dword') Global $g_tRECT, $g_iIndex, $g_hMenu = 0 OnAutoItExitRegister('OnAutoItExit') Local $hEventHook = _WinAPI_SetWinEventHook($EVENT_SYSTEM_CAPTURESTART, $EVENT_SYSTEM_CAPTURESTART, DllCallbackGetPtr($hEventProc)) While 1 Sleep(1000) WEnd Func OnAutoItExit() _WinAPI_UnhookWinEvent($hEventHook) DllCallbackFree($hEventProc) EndFunc ;==>OnAutoItExit Func _EventProc($hEventHook, $iEvent, $hWnd, $iObjectID, $iChildID, $iThreadId, $iEventTime) #forceref $hEventHook, $iObjectID, $iChildID, $iThreadId, $iEventTime Local $iTime, $hParentWnd, $sParent Local Static $iTimeLast Switch $iEvent Case $EVENT_SYSTEM_CAPTURESTART $hParentWnd = _WinAPI_GetAncestor($hWnd, $GA_ROOT) $sParent = _WinAPI_GetClassName($hParentWnd) If $sParent = "Progman" Then $iTime = Round((_WinAPI_GetTickCount64() / 1000), 2) $iTimeDiff = $iTime - $iTimeLast If $iTime <> $iTimeLast Then ConsoleWrite("Desktop hit: " & Round((_WinAPI_GetTickCount64() / 1000), 2) & " " & "Diff: " & $iTimeDiff & @CRLF) If $iTimeDiff < 0.4 Then ; likely double-click ConsoleWrite("assume double-click" & @CRLF) EndIf $iTimeLast = $iTime EndIf EndIf EndSwitch EndFunc ;==>_EventProc
pixelsearch Posted October 27 Posted October 27 4 hours ago, WildByDesign said: I was having some inaccuracies with IsPressed() which, of course, entirely depended on the amount of time in the Sleep() function in the While loop. Setting it too low could end up getting multiple assumed clicks on a single click [...] Just curious to know if you used the important 2nd inner While...WEnd loop, to make sure _IsPressed() returns only 1 click, no matter the time you keep the left mouse button pressed : #include <Misc.au3> HotKeySet("{ESC}", Terminate) Local $iCount = 0, $hDLL = DllOpen("user32.dll") While 1 If _IsPressed("01", $hDLL) Then ; left mouse button = "01" While _IsPressed("01", $hDLL) Sleep(10) WEnd $iCount += 1 ConsoleWrite("LMB pressed #" & $iCount & @CRLF) EndIf Sleep(10) WEnd Func Terminate() Exit EndFunc ;==>Terminate Maybe it helps and makes you change your opinion concerning _IsPressed() ... for your many scripts to come WildByDesign 1 "I think you are searching a bug where there is no bug... don't listen to bad advice."
WildByDesign Posted October 27 Author Posted October 27 42 minutes ago, pixelsearch said: Just curious to know if you used the important 2nd inner While...WEnd loop, to make sure _IsPressed() returns only 1 click, no matter the time you keep the left mouse button pressed : Thank you so much for letting me know about this. I wasn't familiar with IsPressed before, so I ended up basing all of my testing on Example 2. It looks like Example 1 and Example 3 have that 2nd inner loop. I ended up resorting to other ways to avoid the extra clicks. So I had no idea about it since I only followed Example 2. I will definitely have to revisit IsPressed again tonight, for sure.
WildByDesign Posted October 28 Author Posted October 28 6 hours ago, pixelsearch said: Maybe it helps and makes you change your opinion concerning _IsPressed() ... for your many scripts to come Indeed, my opinion has changed. You were right. With the WinEventHook, I was able to detect single-click and double-click with a relatively decent level of accuracy. However, there was a problem that I didn't realize until later. Drag/selection actions (eg. dragging rectangle on desktop) were still counting as a click and I had no way to detect that with WinEventHook. Then you came along (at the perfect time, by the way) with your 2nd inner While...WEnd loop magic for the IsPressed() function. And that is exactly what I needed. I got rid of the WinEventHook and switched back to only using the IsPressed() function. Now, thanks to you, I am able to detect single-click, double-click and avoid drag/selection actions falsely triggering it. I may still tweak the code a bit, but the follow example is doing exactly what I need now: expandcollapse popup#include <Misc.au3> #include <WinAPIvkeysConstants.au3> #include <WinAPIMisc.au3> #include <WinAPISysWin.au3> #include <WinAPISys.au3> DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext" , "HWND", "DPI_AWARENESS_CONTEXT" -4) Global $hUser32 = DllOpen('user32.dll') HotKeySet("{ESC}", Terminate) Example() Func Example() Local $tPoint, $iTimePress, $sParent, $hWndCur, $iTimeRelease Local Static $bSingleLast, $bDouble Local Static $iTimeLast While 1 If _IsPressed($VK_LBUTTON, $hUser32, False) Then $tPoint = _WinAPI_GetMousePos() $hWndCur = _WinAPI_GetAncestor(_WinAPI_WindowFromPoint($tPoint), $GA_ROOT) $sParent = _WinAPI_GetClassName($hWndCur) If $sParent = "Progman" Then $iTimePress = Round((_WinAPI_GetTickCount64() / 1000), 2) $iTimeDiff = $iTimePress - $iTimeLast ;ConsoleWrite("Time: " & $iTimePress & " Diff: " & $iTimeDiff & @CRLF) ; wait until key is released While _IsPressed($VK_LBUTTON, $hUser32, False) Sleep(5) WEnd ; left click has been released $iTimeRelease = Round((_WinAPI_GetTickCount64() / 1000), 2) $iDiffSingle = $iTimeRelease - $iTimePress ;ConsoleWrite("Single-click time: " & $iDiffSingle & @CRLF) If $iDiffSingle < 0.4 Then ; single-click detected If $bSingleLast And $iTimeDiff < 0.4 Then $bDouble = True $bSingleLast = True If $bSingleLast And $bDouble Then ; double-click detected ConsoleWrite("Double-click detected at: " & HourAmPm(@HOUR & ":" & @MIN & ":" & @SEC) & @CRLF) ; reset values $bSingleLast = False $bDouble = False Else If Not $bDouble Then ; single-click detected ConsoleWrite("Single-click detected at: " & HourAmPm(@HOUR & ":" & @MIN & ":" & @SEC) & @CRLF) EndIf EndIf ElseIf $iDiffSingle > 0.4 Then ; possible drag/selection detected $bSingleLast = False $bDouble = False EndIf $iTimeLast = $iTimePress EndIf EndIf Sleep(100) WEnd EndFunc Func Terminate() _IsPressed() Exit EndFunc ;==>Terminate ; #FUNCTION# ==================================================================================================================== ; Name...........: HourAmPm ; Description....: Converts a time in 24-hour format to AM/PM format. ; Syntax.........: HourAmPm( $sDateTime [, $sAmPm = "AM|PM" [, $iTrimRight = 0]] ) ; Parameters.....: $sDateTime - The time (with date or not) string with "HH:" in it. ; $sAmPm - [optional] The AM/PM representation (default is "AM|PM"). ; $iTrimRight - [optional] The number of characters to trim from the right of the result (default is 0). ; $iNoDate - [optional] Whether to omit the date from the output. Defaults to False. ; Return values .: Success: Returns the formatted date and time in AM/PM format. ; Failure: None. ; Author ........: argumentum ; Modified ......: ; Remarks .......: This function takes a 24-hour time string, converts it to AM or PM format, and returns the result with optional trimming. ; Related .......: ; Link ..........: https://www.autoitscript.com/forum/index.php?showtopic=213061 ; Example .......: MsgBox(64, "Converted Time", HourAmPm("12/31/1999 18:59:59")) ; =============================================================================================================================== Func HourAmPm($sDateTime, $sAmPm = Default, $iTrimRight = Default, $iNoDate = Default) Local $aAmPm = StringSplit((StringInStr($sAmPm, "|") ? $sAmPm : "AM|PM"), "|"), $sFormat = $aAmPm[2] Local $iHourPos = StringInStr($sDateTime, ":"), $sHour = StringMid($sDateTime, $iHourPos - 2, 2) Local $sDate = StringLeft($sDateTime, $iHourPos - 3), $sTime = StringTrimLeft($sDateTime, $iHourPos - 1) If $sHour < 12 Then $sFormat = $aAmPm[1] ; https://www.autoitscript.com/forum/index.php?showtopic=213061 $sHour = Mod($sHour, 12) If Not $sHour Then $sHour = 12 Return StringTrimRight((Int($iNoDate) ? "" : $sDate) & StringRight('0' & $sHour, 2) & $sTime, Int($iTrimRight)) & " " & $sFormat EndFunc ;==>HourAmPm pixelsearch 1
pixelsearch Posted October 28 Posted October 28 56 minutes ago, WildByDesign said: Then you came along (at the perfect time, by the way) with your 2nd inner While...WEnd loop magic for the IsPressed() function. And that is exactly what I needed [...] It's always rewarding to help you (if we have something to afford in the thread) because you're a person that tries all suggestions made to you and in the end, it increases your knowledge of AutoIt. In other words, I feel that suggestions made to you are never "lost" as you always do the effort to test them, and even if you don't include the suggestion in your present script, I hope that it could be useful to you in a further script. WildByDesign 1 "I think you are searching a bug where there is no bug... don't listen to bad advice."
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now