Jump to content

Recommended Posts

Posted (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 by WildByDesign
Posted (edited)

:unsure:

#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 by ioa747

I know that I know nothing

Posted (edited)

I have done some work around this quite some times ago :

#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 by Nine
Posted
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. :)

Posted

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.

Posted

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%.

Posted

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?

 

#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
Posted

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...

Posted

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.

 

Posted
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.

Posted

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.

#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

 

Posted
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 :)

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted
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.

Posted
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:

#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

 

Posted
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.

"I think you are searching a bug where there is no bug... don't listen to bad advice."

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...