Jump to content

Recommended Posts

Hi 

I wonder if it's possible to interrupt my script when a specific window closes. This is similar to GUISetOnEvent() but the window I'm using is not created by GUICreate(), it's just a window app. 

My script checks with some frecuency whether the window still exists. However, this is racy since this event is asynchronic w.r.t my script. When it happens, my script end up clicking any other window behind. 

Options to consider are

- Always use ControlClick to prevent clicks outside this window.

- With a timer, poll the existence of the window. Still racy but less probable to happen. (efficiency is not required) 

- Get interrupted whenever the window dissapears. (What I'm looking for) 

Actually, I use MouseClick with client coords. 

Thanks in advance 

 

Link to post
Share on other sites

What you want is something along the lines of DllCallbackRegister + _WinAPI_SetWinEventHook. If set up correctly this will interrupt your script. I have a small example from a script that I've used it in previously, but it's not something that I use very often so I don't know a lot about it. I didn't change the code that was in my script to fit your needs, I'll leave that up to you. You will need to look up a couple resources to understand it more:

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook

https://learn.microsoft.com/en-us/windows/win32/winauto/event-constants

 

#include <WinAPI.au3>
#include <WinAPISys.au3>

Local $hEventProc = DllCallbackRegister('_EventProc', "none", "ptr;dword;hwnd;long;long;dword;dword")
Local $hEventHook = _WinAPI_SetWinEventHook($EVENT_MIN, $EVENT_MAX, DllCallbackGetPtr($hEventProc))

OnAutoItExitRegister('__Exit')

While 1
    Sleep(100)
WEnd

Func _EventProc($hEventHook, $iEvent, $hWnd, $iObjectID, $iChildID, $iThreadId, $iEventTime)
    #forceref $hEventHook, $iObjectID, $iChildID, $iThreadId, $iEventTime

    ; https://learn.microsoft.com/en-us/windows/win32/winauto/event-constants

    Local $hCurrHnd
    Local $hTimer = TimerInit()
;~  ConsoleWrite('Event: ' & Hex($iEvent, 8) & @CRLF) ; Don't recommend that you enable this, you'll be spammed with logs

    Switch $iEvent
        Case $EVENT_SYSTEM_MOVESIZEEND
            $hCurrHnd = WinGetHandle('[ACTIVE]')
            While _WinAPI_GetParent($hWnd) <> 0
                $hWnd = _WinAPI_GetParent($hWnd)
            WEnd
            If $hCurrHnd <> $hWnd Then Return $iEvent
            ConsoleWrite(WinGetTitle($hWnd) & " - MoveEventEnd, " & Round(TimerDiff($hTimer), 2) & 'ms' & @CRLF)

;~      Case $EVENT_SYSTEM_MOVESIZESTART
;~          $hCurrHnd = __WinHandle()
;~          While _WinAPI_GetParent($hWnd) <> 0
;~              $hWnd = _WinAPI_GetParent($hWnd)
;~          WEnd
;~          If $hCurrHnd <> $hWnd Then Return False
;~          ConsoleWrite(WinGetTitle($hWnd) & " - Start" & @CRLF)

        Case $EVENT_OBJECT_LOCATIONCHANGE
            $hCurrHnd = WinGetHandle('[ACTIVE]')
            While _WinAPI_GetParent($hWnd) <> 0
                $hWnd = _WinAPI_GetParent($hWnd)
            WEnd
            If $hCurrHnd <> $hWnd Then Return $iEvent
            ConsoleWrite("$EVENT_OBJECT_LOCATIONCHANGE - " & Round(TimerDiff($hTimer), 2) & 'ms' & @CRLF)

        Case $EVENT_OBJECT_NAMECHANGE
            $hCurrHnd = WinGetHandle('[ACTIVE]')
            While _WinAPI_GetParent($hWnd) <> 0
                $hWnd = _WinAPI_GetParent($hWnd)
            WEnd
            If $hCurrHnd <> $hWnd Then Return $iEvent
            ConsoleWrite("$EVENT_OBJECT_NAMECHANGE - " & Round(TimerDiff($hTimer), 2) & 'ms' & @CRLF)
    EndSwitch
EndFunc   ;==>_EventProc

Func __Exit()
    _WinAPI_UnhookWinEvent($hEventHook)
    DllCallbackFree($hEventProc)
    Exit
EndFunc   ;==>__Exit

 

What you would want to do is likely have a function that wraps your MouseClick, and have a variable that you can toggle on/off if the window exists, or not. That way you can toggle the variable to false if the window is destroyed, and that can either exit your loop/function that's doing the clicks. Even if it's not at the point of checking that then having your MouseClick wrapped in a function that checks if the variable is true before clicking should keep you from clicking when the window doesn't exist. Something like 
 

Local $bClick = False
Func _MouseClick($sButton = 'main', $iX = Default, $iY = Default, $iClicks = 1, $iSpeed = 10)
    If $bClick = True Then Return MouseClick($sButton, $iX, $iY, $iClicks, $iSpeed)
EndFunc   ;==>_MouseClick

 

The other much more simple option is to use the _MouseClick function like above and replace the If check with If WinExists('YourWindow'), not very efficient but should take care of clicking when the window doesn't exist, for the most part (since it could still happen in a tiny time between the check and click function). ControlClick if it works for your window will be safer.

Edited by mistersquirrle

We ought not to misbehave, but we should look as though we could.

Link to post
Share on other sites

If you prefer using a hook then I would recommend using _WinAPI_RegisterShellHookWindow as it is more appropriate when a window closes.

Here an example :

#include <APISysConstants.au3>
#include <WinAPISysWin.au3>
#include <Constants.au3>

If WinExists("[CLASS:Notepad]") Then Exit MsgBox($MB_SYSTEMMODAL, "Error", "Close all instance of Notepad")
Run("Notepad")
Global $hTarget = WinWait("[CLASS:Notepad]")

OnAutoItExitRegister(OnAutoItExit)

Global $hForm = GUICreate('')
GUIRegisterMsg(_WinAPI_RegisterWindowMessage('SHELLHOOK'), WM_SHELLHOOK)
_WinAPI_RegisterShellHookWindow($hForm)

While 1
    Sleep(100)
WEnd

Func WM_SHELLHOOK($hWnd, $iMsg, $wParam, $lParam)
  If $lParam <> $hTarget Then Return
  Switch $wParam
    Case $HSHELL_WINDOWDESTROYED
      ConsoleWrite("Notepad has been closed" & @CRLF)
      Exit
  EndSwitch
EndFunc   ;==>WM_SHELLHOOK

Func OnAutoItExit()
    _WinAPI_DeregisterShellHookWindow($hForm)
EndFunc   ;==>OnAutoItExit

The script will stop when you close Notepad...

Link to post
Share on other sites

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.

  • Similar Content

    • By Jefrey
      Hi guys/girls!
      I'm gonna share this UDF I made today.
      It allows you to easily create TCP servers and set actions depending on three events: OnConnect, OnDisconnect and OnReceive.
      It is also multi client (you can set the clients limit) and you can also bind a Console-based executable to the socket (similar to -e parameter in NetCat). This feature is useful if you want to use some Console UDF to create your TCP server and don't want to mix it with the TCP functions. Also, as it runs on background just firing events, it won't pause your script while listening/receiving, so you can do anything else (stop and restart the server, allow the user to click buttons or just wait on an infinite loop) that your callbacks will be called once the event is fired.
      It's also very easy to use. See this examples:
      Example #1: A basic server
      By running this (then connecting to the server using Netcat), you will receive a message box telling you when some user connects or disconnects (the socket ID and his IP address is passed as parameter to your callback function) and also when the user sends something over the TCP socket (the data sent is passed as parameter).
      #cs Download netcat at https://eternallybored.org/misc/netcat/ Execute this script Run in CMD: nc -vv 127.0.0.1 8081 #ce #include "TCPServer.au3" ; First we set the callback functions for the three events (none of them is mandatory) _TCPServer_OnConnect("connected") _TCPServer_OnDisconnect("disconnect") _TCPServer_OnReceive("received") ; And some parameters _TCPServer_DebugMode(True) _TCPServer_SetMaxClients(10) ; Finally we start the server at port 8081 at any interface _TCPServer_Start(8081) Func connected($iSocket, $sIP) MsgBox(0, "Client connected", "Client " & $sIP & " connected!") _TCPServer_Broadcast('new client connected guys', $iSocket) _TCPServer_Send($iSocket, "Hey! Write something ;)" & @CRLF) _TCPServer_SetParam($iSocket, "will write") EndFunc ;==>connected Func disconnect($iSocket, $sIP) MsgBox(0, "Client disconnected", "Client " & $sIP & " disconnected from socket " & $iSocket) EndFunc ;==>disconnect Func received($iSocket, $sIP, $sData, $sPar) MsgBox(0, "Data received from " & $sIP, $sData & @CRLF & "Parameter: " & $sPar) _TCPServer_Send($iSocket, "You wrote: " & $sData) _TCPServer_SetParam($iSocket, 'will write again') EndFunc ;==>received While 1 Sleep(100) WEnd Example #2: A basic HTTP server (just one page, as it is just an example)
      In this example, we run this code and point our browser to the address mentioned on the comments. A basic "It works!" page is show.
      #cs Run this script Point your browser to http://localhost:8081/ #ce #include "TCPServer.au3" _TCPServer_OnReceive("received") _TCPServer_DebugMode(True) _TCPServer_SetMaxClients(10) _TCPServer_Start(8081) Func received($iSocket, $sIP, $sData, $sParam) _TCPServer_Send($iSocket, "HTTP/1.0 200 OK" & @CRLF & _ "Content-Type: text/html" & @CRLF & @CRLF & _ "<h1>It works!</h1>" & @CRLF & _ "<p>This is the default web page for this server.</p>" & @CRLF & _ "<p>However this server is just a 26-lines example.</p>") _TCPServer_Close($iSocket) EndFunc ;==>received While 1 Sleep(100) WEnd Example #3: A telnet-like server (Command Prompt bound to the socket after password requesting)
      By running this example and connecting with Netcat, we will be asked for a password, which is 12345 as we set on the script. If the password is correct, we will see the Command Prompt live-updated (try running a ping to some server, for example).
      #cs Download netcat at https://eternallybored.org/misc/netcat/ Execute this script Run in CMD: nc -vv 127.0.0.1 8081 #ce #include "TCPServer.au3" Global $sPassword = "12345" ; input server password here _TCPServer_OnConnect("connected") _TCPServer_OnDisconnect("disconnect") _TCPServer_OnReceive("received") _TCPServer_DebugMode(True) _TCPServer_SetMaxClients(10) _TCPServer_Start(8081) Func connected($iSocket, $sIP) _TCPServer_Send($iSocket, "Welcome! Please input password: ") _TCPServer_SetParam($iSocket, 'login') EndFunc ;==>connected Func disconnect($iSocket, $sIP) MsgBox(0, "Client disconnected", "Client " & $sIP & " disconnected from socket " & $iSocket) EndFunc ;==>disconnect Func received($iSocket, $sIP, $sData, $sParam) If $sParam = "login" Then If $sData <> $sPassword Then _TCPServer_Send($iSocket, "Wrong password. Try again: ") Return Else _TCPServer_SetParam($iSocket, 'command') _TCPServer_BindAppToSocket($iSocket, 'cmd.exe') EndIf ElseIf $sParam = "command" Then _TCPServer_SendToBound($iSocket, $sData) EndIf EndFunc ;==>received While 1 Sleep(100) WEnd The limit is your imagination? Well, no sure. We have this limit:
      You can't create more than one server with this UDF in the same script. However, you can pause and resume (read 'stop and start again') your server at any time in your script, without having to reset the server settings. And of course you can have many clients (or just one, it's your choice!) in the same server. Or run multiple instances. Functions list:
      _TCPServer_Start
      _TCPServer_Stop
      _TCPServer_Close
      _TCPServer_Send
      _TCPServer_Broadcast
      _TCPServer_SetParam
      _TCPServer_BindAppToSocket
      _TCPServer_SendToBound
      _TCPServer_UnBindAppToSocket
      _TCPServer_GetMaxClients
      _TCPServer_IsServerActive
      _TCPServer_ListClients
      _TCPServer_OnConnect
      _TCPServer_OnDisconnect
      _TCPServer_OnReceive
      _TCPServer_SetMaxClients
      _TCPServer_DebugMode
      _TCPServer_AutoTrim
      _TCPServer_SocketToIP
      _TCPServer_SocketToConnID
      _TCPServer_ConnIDToSocket
       
      Help file and more examples included!
      Latest version: 1.0.0.1
      Download: TCPServer UDF.rar
      Changelog
      1.0 - First release - 18/04/2015 1.0.0.1 - Bug fix __TCPServer_Accept internal function / help file recompiled - 26/04/2015 Perhaps you will need to uncompress the file first, so the help file will work.
      Fork this on Github: http://github.com/jesobreira/TCPServerUDF
      TCPServer UDF.rar
       
    • By revonatu
      Hi,
      I have a quite complex script and now I would like to define the initial parameters and directories via a GUI.
      The first question is: Which GUI mode is best for my purposes? I don't understand in detail what the difference is. I tend to OnEvent mode as I only need the GUI at the beginning.
      Edit: Ok, only in loop mode the file selection works. One question solved. But how do I pause my script and start it after I am done with the GUI?
      This is my code so far:
      #include <MsgBoxConstants.au3> #include <WindowsConstants.au3> #include <File.au3> #include <FileConstants.au3> #include <StringConstants.au3> #include <Date.au3> #include <Array.au3> #include <GUIConstantsEx.au3> Local $AutoIt_GUI = GUICreate("ATCOR4 - Initial Settings", 500, 400) ; title, width and height Local $idLabel0 = GUICtrlCreateLabel("Wellcome to this ATCOR4 automation. Please choose your preferences.", 30, 10) ; text and position (left, top) Local $idButton1 = GUICtrlCreateButton("*.meta file", 30, 50, 60) Local $idLabel1 = GUICtrlCreateLabel("select the meta file with start and end time", 120, 55, 300, 60) Local $idButton2 = GUICtrlCreateButton("*.pos file", 30, 80, 60); name, position and width Local $idLabel2 = GUICtrlCreateLabel("select the pos file with flight geometry", 120, 85, 300, 60) Local $idCombo1 = GUICtrlCreateCombo("Operation Type", 30, 150, 100) GUICtrlSetData($idCombo1, "GUI|.inn file", "Operation Type") Global $OperationMode = '"' & GUICtrlRead($idCombo1) & '"' GUISetState(@SW_SHOW) ; display this GUI Local $idMsg = 0 ; In this message loop we use variables to keep track of changes to the GUI controls While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop Exit ;Case $GUI_EVENT_MINIMIZE ;MsgBox($MB_SYSTEMMODAL, "", "Dialog minimized", 2) ;Case $GUI_EVENT_MAXIMIZE ;MsgBox($MB_SYSTEMMODAL, "", "Dialog restored", 2) Case $idButton1 Local $MetaOpenDialog = FileOpenDialog("Select the meta file...", @ScriptDir & "\", "Meta (*.meta)") $metaPath = $MetaOpenDialog GUICtrlSetData($idLabel1, $MetaOpenDialog) Case $idButton2 Local $PosOpenDialog = FileOpenDialog("Select the pos file...", @ScriptDir & "\", "Pos (*.pos)") $posPath = $PosOpenDialog GUICtrlSetData($idLabel2, $PosOpenDialog) EndSwitch WEnd Thanks in advance for any hints and suggestions (the shorter the code the better).
    • By GtaSpider
      Hey everyone,
      I searched some hours for a way to use the function WaitForSingleObject not as a function the script has to wait for, but a function which calls a callback function when the event is signaled. I found a way with RegisterWaitForSingleObject  but no AutoIt implemention, so I made one on my own, and now I want to share it with you guys!
      The only limitation by this implemention is that you are not able to use more than one callback at once. to avoid this, you could write a static array in the function which fills with the selfmade callback structs. If you find another way, let me know!
      Example:



      Hope you like it and find use for it!
      Greetz,
      Spider
      RegisterWaitForSingleObject.zip
×
×
  • Create New...