OnWin() - call function on window event (WinWaitXXX async)

Post your working scripts, libraries and tools
User avatar
hoppfrosch
Posts: 406
Joined: 07 Oct 2013, 04:05
GitHub: hoppfrosch
Location: Rhine-Maine-Area, Hesse, Germany
Contact:

Re: OnWin() - call function on window event (WinWaitXXX asyn

23 Feb 2015, 01:01

Coco wrote:
hoppfrosch wrote:I've tested with all available current variants of Autohotkey (Test/NonTest, AHK1/AHK2) - all with the same results: the callback NP_EXist() is never called ...
Please try the updated demo.
amun wrote:By the demo in German you have to change "notepad" to "editor"
I've changed the demo to use Calculator, I' not sure if the window title is the same in German but it should be easier to replace.
Yupp - that was it. :headwall: Thx for your effort - really nice work ...
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: OnWin() - call function on window event (WinWaitXXX asyn

23 Feb 2015, 07:30

Updated to v1.0.03.00 - mostly internal changes
Skrell
Posts: 166
Joined: 23 Jan 2014, 12:05

Re: OnWin() - call function on window event (WinWaitXXX asyn

25 Feb 2015, 20:54

This is probably a stupid question but can i put multiple OnWin("Exist", "Calculator", "C") events in a single script?
So for example:

OnWin("Exist", "Calculator", "C")
OnWin("Exist", "Word", "W")
OnWin("Exist", "Notepad", "N")
etc ?
User avatar
Relayer
Posts: 134
Joined: 30 Sep 2013, 13:09
Location: Delaware, USA

Re: OnWin() - call function on window event (WinWaitXXX asyn

25 Feb 2015, 21:41

Coco,

I have noticed that a new instance of AutoHotKey appears in task manager for each "OnWin" that is pending. They each have a CPU usage of 13 and the fan on my machine kicks into high gear.

Just an observation.

Relayer
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: OnWin() - call function on window event (WinWaitXXX asyn

01 Mar 2015, 12:43

@Pinkfloydd, the child process(es) should terminate once the parent script exits. Are you using the latest version from the main post?

Perhaps you're terminating the main script from within task manager? I tried this and indeed, the subprocess(es) are not terminated. For now, you should exit the main script via ExitApp or Tray menu->Exit. I'll come up with a fix for cases wherein the user tries to exit from the task manager or similar(e.g.: Process, Close). Although ideally, one should allow the script to quit properly(ExitApp or Tray menu) instead of prematurely terminating the process.
User avatar
evilC
Posts: 4780
Joined: 27 Feb 2014, 12:30

Re: OnWin() - call function on window event (WinWaitXXX async)

28 Aug 2015, 07:45

I have been trying to adapt OnWin to use function binding:

Library:

Code: Select all

OnWin(event, WinTitle, CbProc, reserved:=0)
{
	static host
	if !IsObject(host)
		host := new OnWinHost()
	host.AddClient(client := new OnWinClient(event, WinTitle, CbProc))

	code := Format("
	(LTrim Join`n
	{5}
	ListLines Off
	OnWin_Main({1}{2}{1}, {1}{3}{1})
	ExitApp
	#Include {4}
	#NoTrayIcon
	#KeyHistory 0
	)", Chr(34), host.Id, client.Id, A_LineFile, A_AhkVersion<"2" ? "SetBatchLines -1" : "")

	cmd := Format("{1}{2}{1} /ErrorStdOut *", Chr(34), A_AhkPath)
	exec := ComObjCreate("WScript.Shell").Exec(cmd)
	exec.StdIn.Write(code), exec.StdIn.Close()
	while !client.__Handle && (exec.Status == 0)
		Sleep 10

	; taken from Lexikos' LoadFile() [http://goo.gl/y6ctxp], make script #Persistent
	Hotkey IfWinActive, % host.Id
	Hotkey vk07, _onwin_persistent, Off
_onwin_persistent:
}

class OnWinHost
{
	__New()
	{
		this.Clients := {}

		proxy := ObjClone(this)
		VarSetCapacity(CLSID, 16, 0)
		if DllCall("ole32\CoCreateGuid", "Ptr", &CLSID) != 0
			throw Exception("Failed to generate CLSID", -1)

		HR := DllCall("oleaut32\RegisterActiveObject"
		      , "Ptr", &proxy, "Ptr", &CLSID, "UInt", 0, "UInt*", hReg, "UInt")
		if (HR < 0)
			throw Exception(Format("HRESULT: 0x{:x}", HR), -1)
		this.__Handle := hReg, proxy.__Handle := 0 ; avoid calling RevokeActiveObject twice

		VarSetCapacity(sGUID, 38 * 2 + 1)
		DllCall("ole32\StringFromGUID2", "Ptr", &CLSID, "Ptr", &sGUID, "Int", 38 + 1)
		this.Id := StrGet(&sGUID, "UTF-16")
	}

	__Delete() ; called on script's exit
	{
		if hReg := this.__Handle ; 0 if proxy(active object)
		{
			DllCall("oleaut32\RevokeActiveObject", "UInt", hReg, "Ptr", 0)
			for i, client in ObjRemove(this, "Clients") ; terminate any running listener(s)
				client.Terminate()
		}
	}

	AddClient(client)
	{
		this.Clients[ client.Id ] := client
	}

	FreeClient(client)
	{
		return ObjRemove(this.Clients, client.Id)
	}
}

class OnWinClient
{
	__New(event, WinTitle, CbProc)
	{
		if (WinTitle ~= "i)^ahk_group .*$")
			throw Exception("Invalid argument. To specify a window group, pass an array of WinTitle(s).", -1, WinTitle)
		
		this.Event          := event
		this.Window         := WinTitle
		;this.Callback       := IsObject(CbProc) ? CbProc : Func(CbProc)
		this.Callback       := CbProc
		this.MatchMode      := A_TitleMatchMode
		this.MatchModeSpeed := A_TitleMatchModeSpeed
		this.Id             := "#" . &this
		this.__Handle       := 0
	}

	__Call(callee, args*)
	{
		if (callee == "") || (callee = "Call") || IsObject(callee)
		{
			/*
			if CbProc := this.Callback
				return %CbProc%(this)
			*/
			return this.Callback.(this.Event, this.Window)
		}
	}

	Terminate()
	{
		if hWnd := this.__Handle
			return DllCall("PostMessage", "Ptr", hWnd, "UInt", 0x10, "Ptr", 0, "Ptr", 0) ; WM_CLOSE
	}
}

OnWin_Main(HostId, ClientId)
{
	host := ComObjActive(HostId)
	client := host.Clients[ClientId], client.__Handle := A_ScriptHwnd + 0

	event := client.Event
	if !(event ~= "i)^(Exist|(Not|!)?Active|(Close|(Not|!)Exist)(All)?|Show|Hide|M(in|ax)imize|Move)$")
		return

	prev_DHW := A_DetectHiddenWindows
	DetectHiddenWindows On
	SetWinDelay -1
	SetTitleMatchMode % client.MatchMode
	SetTitleMatchMode % client.MatchModeSpeed

	if IsObject(WinTitle := client.Window) ; ahk_group GroupName workaround
	{
		Loop % WinTitle[A_AhkVersion<"2" ? "MaxIndex" : "Length"]() ; can't use for-loop :(
			GroupAdd WinGroup, % WinTitle[A_Index]
		WinTitle := "ahk_group WinGroup"
	}

	if InStr(" Exist Show Minimize Maximize Move ", Format(" {} ", event))
		WinWait %WinTitle%

	if (event = "Active")
		WinWaitActive %WinTitle%

	else if (event = "NotActive" || event = "!Active")
		WinWaitNotActive %WinTitle%

	else if (event ~= "i)^(Close|(Not|!)Exist)(All)?$") && WinExist(WinTitle)
		WinWaitClose % InStr(event, "All") ? WinTitle : ""

	else if (event = "Show") || (event = "Hide" && WinExist(WinTitle))
	{
		DetectHiddenWindows Off
		if (event = "Show")
			WinWait %WinTitle%
		else
			WinWaitClose
	}

	else if (event = "Minimize" || event = "Maximize")
	{
		hWnd := WinExist() ; get handle of "Last Found" Window
		showCmd := event="Minimize" ? 2 : 3
		VarSetCapacity(WINDOWPLACEMENT, 44, 0)
		NumPut(44, WINDOWPLACEMENT, 0, "UInt") ; sizeof(WINDOWPLACEMENT)
		Loop
			DllCall("GetWindowPlacement", "Ptr", hWnd, "Ptr", &WINDOWPLACEMENT)
		until NumGet(WINDOWPLACEMENT, 8, "UInt") == showCmd
	}

	else if (event = "Move")
	{
		WinGetPos prevX, prevY, prevW, prevH ; use last found (for ahk_group WinGroup)
		Loop
			WinGetPos x, y, w, h
		until (x != prevX || y != prevY || w != prevW || h != prevH)
	}

	DetectHiddenWindows %prev_DHW%
	
	try %client%() ; suppress error
	return host.FreeClient(client)
}
Test Script:

Code: Select all

#SingleInstance force
#Include <OnWin>

mc := new MyClass()
return

GuiClose:
	ExitApp
	return

class myclass {
	__New(){
		fn := this.C.Bind(this)
		OnWin("Exist", "Calculator", fn)
		Sleep 1000
		Run calc.exe
	}
 
	C(event, window)
	{
		;static window
		;event := this.Event, window := this.Window
		MsgBox CEvent: %event%`nWindow: %window%
		;OnWin("Close", window, "X")
		fn := this.X.Bind(this)
		OnWin("Close", window, fn)
		;SetTimer close, -1000
		return
	}
	
	X(event, window)
	{
		;event := this.Event, window := this.Window
		MsgBox XEvent: %event%`nWindow: %window%
		;SetTimer exit, -1 ; allow function to return
		return
	}
}
However, the #Singleinstance force directive in the test script breaks it ("Could not close previous instance of script. Keep Waiting?" error).
Does anyone know how to fix this?
Coco-guest

Re: OnWin() - call function on window event (WinWaitXXX async)

28 Aug 2015, 10:55

I've been wanting to update this script for a while but my focus has been on other things. Perhaps due to not having the need for this now. I'll see what I can do to fix the bugs and improve the script.
User avatar
RobertL
Posts: 540
Joined: 18 Jan 2014, 01:14
Location: China

Re: OnWin() - call function on window event (WinWaitXXX async)

06 Sep 2015, 09:33

Thanks for the work~

And , how to stop watching, does it necessary? I didn't found infomation in Parameter.
Using empty callback? OnWin( event, WinTitle)
我为人人,人人为己?
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: OnWin() - call function on window event (WinWaitXXX async)

06 Sep 2015, 10:00

RobertL wrote:Thanks for the work~

And , how to stop watching, does it necessary? I didn't found infomation in Parameter.
Using empty callback? OnWin( event, WinTitle)
Oops, I even forgot how to use this script, lol. Okay, so after reviewing the source, I believe that stopping window monitoring is possible via the OnWinClient class Terminate() method. However, the client object is not exposed to the caller as it is stored in an array kept by the host which itself is stored in a static variable. Thanks for pointing this out, I'll implement this in the next release.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: OnWin() - call function on window event (WinWaitXXX async)

13 Sep 2015, 06:21

I've pushed a new rewritten version(for testing) with the following changes:
  • Monitoring can be performed in-process(same process as the main script) - this is the default when calling OnWin(). To have a window event monitor run in a separate child process, the caller must now use OnWin_Ex()
  • Previously, each window event monitor runs it in its own process(w/c is kinda lame). With the new version, the caller may specify on whether to run an event monitor in its own dedicated process or push it into an existing running process(hosting previously set event monitor(s))
  • Added OnWin_Stop() to allow disabling of specific active window event monitor(s) via a user-defined event monitor ItemId
  • Improved termination of child process(es) that are sometimes left running even when the main script has exited.
Inline documentation is provided to aid in usage. I'm still testing this version, I will be updating the main post once I've merged this with the main branch. Feedback(s) are highly appreciated.

Edit:
Apparently, this line(#77): static level := A_AhkVersion<"2" ? -2 : -1 is bug in v2 and should be static level := A_AhkVersion<"2" ? -2 : -3. I haven't really tested this for v2 but I have written it to be compatible(majority of the code, I hope).
User avatar
evilC
Posts: 4780
Joined: 27 Feb 2014, 12:30

Re: OnWin() - call function on window event (WinWaitXXX async)

16 Sep 2015, 10:09

Thanks for the update!

I am not sure as to the reason why your example code uses this as the passed param.
If you altered the sample code to be like this...

Code: Select all

#Include <OnWin>
 
OnWin("Exist", "Calculator", "C")
Sleep 1000
Run calc.exe
return
 
C(e)
{
	static window
	event := e.Event, window := e.Window
	MsgBox Event: %event%`nWindow: %window%
	OnWin("Close", window, "X")
	SetTimer close, -1000
	return
close:
	WinClose %window%
	return
}
 
X(e)
{
	event := e.Event, window := e.Window
	MsgBox Event: %event%`nWindow: %window%
	SetTimer exit, -1 ; allow function to return
	return
exit:
	ExitApp
}
... then it would also work in a class-based, function binding scenario, and would not override the "this" property of the class method.

Code: Select all

#Include <OnWin>
 
mc := new MyClass()
return

Class MyClass {
	__New(){
		this.test := "Hello World"
		
		OnWin("Exist", "Calculator", this.C.Bind(this))
		Sleep 1000
		Run calc.exe
	}
	
	C(e)
	{
		static window
		event := e.Event, window := e.Window
		msgbox % this.test	; "this" is preserved
		MsgBox Event: %event%`nWindow: %window%
		OnWin("Close", window, this.X.Bind(this))
		SetTimer close, -1000
		return
	close:
		WinClose %window%
		return
	}
 
	X(e)
	{
		event := e.Event, window := e.Window
		MsgBox Event: %event%`nWindow: %window%
		SetTimer exit, -1 ; allow function to return
		return
	exit:
		ExitApp
	}
}
Skrell
Posts: 166
Joined: 23 Jan 2014, 12:05

Re: OnWin() - call function on window event (WinWaitXXX async)

16 Sep 2015, 19:21

What are the advantages to using this script over just a bunch of SetTimer functions ? Ease of use? Performance?
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: OnWin() - call function on window event (WinWaitXXX async)

17 Sep 2015, 05:57

@evilC: The sample code is not updated. Regardless, it's just an example to demonstrate OnWin(). ;)

@Skrell: Ease of use, reusable, additional functionality esp. the ability to have the monitoring done in a separate process. You can group certain window event monitors, etc. We occasionally see help requests like: "Do this when certain window appears", "Do that when this window is closed" so this lib will really come in handy for those users. :)
Skrell
Posts: 166
Joined: 23 Jan 2014, 12:05

Re: OnWin() - call function on window event (WinWaitXXX async)

17 Sep 2015, 12:06

Coco wrote:@evilC: The sample code is not updated. Regardless, it's just an example to demonstrate OnWin(). ;)

@Skrell: Ease of use, reusable, additional functionality esp. the ability to have the monitoring done in a separate process. You can group certain window event monitors, etc. We occasionally see help requests like: "Do this when certain window appears", "Do that when this window is closed" so this lib will really come in handy for those users. :)
but performance wise it will typically be the same right?
lexikos
Posts: 7057
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: OnWin() - call function on window event (WinWaitXXX async)

22 Sep 2015, 00:50

While this script is a good demonstration of inter-process communication for multi-tasking, if the purpose is to monitor for windows appearing, activating or closing, a shell hook would be more suitable.
User avatar
RobertL
Posts: 540
Joined: 18 Jan 2014, 01:14
Location: China

Re: OnWin() - call function on window event (WinWaitXXX async)

22 Sep 2015, 06:27

lexikos wrote:While this script is a good demonstration of inter-process communication for multi-tasking, if the purpose is to monitor for windows appearing, activating or closing, a shell hook would be more suitable.
shell hook can only monitor top windows belong to Shell, not contain its child window, so..
inter-process communication for multi-tasking, if the sub tasks could be organizated as threads (not processes), that would be more nice in Windows Task Manager.
我为人人,人人为己?
Skrell
Posts: 166
Joined: 23 Jan 2014, 12:05

Re: OnWin() - call function on window event (WinWaitXXX async)

22 Sep 2015, 10:20

RobertL wrote:if the sub tasks could be organizated as threads (not processes), that would be more nice in Windows Task Manager.
I thought that this is what the SetTimer routine did which is why I originally questioned the advantage of this script over one that simply uses a bunch of SetTimers. Am I incorrect in this understanding ?
Coco-guest

Re: OnWin() - call function on window event (WinWaitXXX async)

22 Sep 2015, 12:05

The dev version I posted here, by default, does not use a separate process to perform the monitoring, but instead uses a timer. Only in cases wherein you think that the timer is conflicting with your main script that you can opt in for the alternate method of using a separate child process by calling the newly added function OnWin_Ex(). So in answer to Skrell, performance(in comparison with a script that uses a bunch of SetTimer(s)) should be the same since after all, the method(SetTimer) used is the same. For optimum reliability, shell hook is the best choice.

@RobertL: With the dev version, if you choose to have the monitoring performed in a separate process(by calling OnWin_Ex()), by default, only one child process is used so it's somehow more "friendly" to Windows Task Manager compared to the previous version. Subsequently added event monitors are just pushed into the queue/list. I will be merging this into the main branch.
lexikos
Posts: 7057
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: OnWin() - call function on window event (WinWaitXXX async)

22 Sep 2015, 16:55

RobertL wrote:shell hook can only monitor top windows belong to Shell, not contain its child window, so..
What's your point? This script doesn't handle controls, does it? If you want to monitor for controls, you can use SetWinEventHook, but it fires far more frequently than a shell hook.
inter-process communication for multi-tasking, if the sub tasks could be organizated as threads (not processes),
In that case it wouldn't be inter-process communication. Is not the whole point of the technique to work around the fact that AutoHotkey doesn't support multi-threading?

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 23 guests