每次目标 window 在 AutoHotkey 中激活时如何激活函数
How to activate a function everytime the target window becomes active in AutoHotkey
我想在每次切换到特定程序时激活一个声音配置文件,并在每次离开时改回默认配置文件。
此操作通过单选按钮在 GUI 中打开。
我创建的解决方法是:
Auto_Ftsps:
gui, Submit, NoHide
While (Rad3==1)
{
Previous_window:= WinActive("A")
Sleep,1000
Current_window:= WinActive("A")
If (Previous_window =Current_window)
{}
Else If (Previous_window !=Current_window)
{
If(WinActive("Fortnite"))
Run_Peace_Profile("Ftsps")
Else
Run_Peace_Profile("Graphic EQ")
}
Sleep,2000
}
return
有更好的方法吗?我查看了论坛和教程,但没有成功。
OnWin.ahk和你的方法有些相似;它使用 SetTimer
to periodically check for the events you register with it, so unlike your method it's asynchronous in terms of AHK threads. Don't quote me on this, but I think internally WinWaitActive
也是类似的。
然而,还有另一种方法,它不涉及定期检查活动 window 我们自己而是允许我们对 Windows' "active window change" 事件做出反应 - shell 钩。通常 SetWindowsHookEx
with WH_SHELL
would be used for this, but I don't think it's even possible to use it with AHK alone (you have to make a DLL), and it's kind of complicated to get everything right. Luckily there's RegisterShellHookWindow
, which allows us to receive shell events as Windows messages rather than injecting a DLL into other threads. We can then use AHK's OnMessage
对这些消息做出反应,在您的情况下,这意味着具有测试 wParam
为 HSHELL_WINDOWACTIVATED
或 HSHELL_RUDEAPPACTIVATED
的功能(即设置位 3 ) 并相应地更改声音配置文件。至于启用此功能 on/off,我们可以让单选按钮的 g-label 包含控制我们是否要通过 (De)RegisterShellHookWindow
.[=33 接收 shell 消息的逻辑=]
#SingleInstance Force
Gui +AlwaysOnTop +HwndhWnd
Gui Add, Text,, Automatic sound profile change
Gui Add, Radio, gHookRadioHandler Checked, On
Gui Add, Radio, gHookRadioHandler X+, Off
Gui Font,, Consolas
Gui Add, Edit, HwndhLog xm w800 r30 ReadOnly -Wrap -WantReturn
ftspsActive := false
; Get the dynamic identifier for shell messages and assign our callback to handle these messages
SHELL_MSG := DllCall("RegisterWindowMessage", "Str", "SHELLHOOK", "UInt")
OnMessage(SHELL_MSG, Func("ShellCallback"))
if (!SetHook(true)) {
GuiControl,, Off, 1
}
Gui Show
GuiClose() {
ExitApp
}
; Dummy implementation that logs the changes to an edit control for demonstration purposes
Run_Peace_Profile(profile) {
Println("Switched to " profile)
}
; Sets whether the shell hook is registered
SetHook(state) {
global hWnd
static shellHookInstalled := false
if (!shellHookInstalled and state) {
if (!DllCall("RegisterShellHookWindow", "Ptr", hWnd)) {
Println("Failed to register shell hook")
return false
}
Println("Registered shell hook")
shellHookInstalled := true
}
else if (shellHookInstalled and !state) {
if (!DllCall("DeregisterShellHookWindow", "Ptr", hWnd)) {
Println("Failed to deregister shell hook")
return false
}
Println("Deregistered shell hook")
shellHookInstalled := false
}
return true
}
; Radio button handler that controls registration of the sound profile hook
HookRadioHandler() {
state := A_GuiControl == "On"
if (!SetHook(state)) {
GuiControl,, % (state ? "Off" : "On"), 1
}
}
; Shell messages callback
ShellCallback(wParam, lParam) {
; HSHELL_WINDOWACTIVATED = 4, HSHELL_RUDEAPPACTIVATED = 0x8004
if (wParam & 4) {
; lParam = hWnd of activated window
global ftspsActive
WinGet fnHWnd, ID, Fortnite
WinGetTitle t, ahk_id %lParam%
Println("active window: " t)
if (!ftspsActive and fnHWnd = lParam) {
Run_Peace_Profile("Ftsps")
ftspsActive := true
}
else if (ftspsActive and fnHWnd != lParam) {
Run_Peace_Profile("Graphic EQ")
ftspsActive := false
}
}
}
; Prints a line to the logging edit box
Println(s) {
global hLog
static MAX_LINES := 1000, LINE_ADJUST := 200, nLines := 0
; EM_SETSEL = 0xB1, EM_REPLACESEL = 0xC2, EM_LINEINDEX = 0xBB
if (nLines = MAX_LINES) {
; Delete the oldest LINE_ADJUST lines
SendMessage 0xBB, LINE_ADJUST,,, ahk_id %hLog%
SendMessage 0xB1, 0, ErrorLevel,, ahk_id %hLog%
SendMessage 0xC2, 0, 0,, ahk_id %hLog%
nLines -= LINE_ADJUST
}
++nLines
; Move to the end by selecting all and deselecting
SendMessage 0xB1, 0, -1,, ahk_id %hLog%
SendMessage 0xB1, -1, -1,, ahk_id %hLog%
; Add the line
str := "[" A_Hour ":" A_Min "] " s "`r`n"
SendMessage 0xC2, 0, &str,, ahk_id %hLog%
}
请注意,我以编辑控件的形式添加了一些反馈消息,因此此脚本可以用作小型 stand-alone 演示。
这种方法的一个可能缺点来自 RegisterShellHookWindow
文档的顶部:
This function is not intended for general use. It may be altered or unavailable in subsequent versions of Windows.
此外,我不知道 "rude app" 是什么,也不知道为什么它们有自己的常量。 This question 说它与 full-screen 个应用程序有关,但提问者和我似乎每个程序都收到 HSHELL_RUDEAPPACTIVATED
。
作为替代方案,还有 SetWinEventHook
,可以用 EVENT_SYSTEM_FOREGROUND
和 WINEVENT_OUTOFCONTEXT
调用,以安装每次前台调用的 AHK 回调 window 变化。请注意,与 RegisterShellHookWindow
方法不同,这将在子 windows 进入前台时调用。
#SingleInstance Force
Gui +AlwaysOnTop
Gui Add, Text,, Automatic sound profile change
Gui Add, Radio, gHookRadioHandler Checked, On
Gui Add, Radio, gHookRadioHandler X+, Off
Gui Font,, Consolas
Gui Add, Edit, HwndhLog xm w800 r30 ReadOnly -Wrap -WantReturn
ftspsActive := false
fcAddr := RegisterCallback(Func("FgCallback"))
if (!SetHook(true)) {
GuiControl,, Off, 1
}
Gui Show
GuiClose() {
ExitApp
}
; Dummy implementation that logs the changes to an edit control for demonstration purposes
Run_Peace_Profile(profile) {
Println("Switched to " profile)
}
; Sets whether the foreground hook is installed
SetHook(state) {
global fcAddr
static hook, fgHookInstalled := false
if (!fgHookInstalled and state) {
; EVENT_SYSTEM_FOREGROUND = 3, WINEVENT_OUTOFCONTEXT = 0
hook := DllCall("SetWinEventHook", "UInt", 3, "UInt", 3, "Ptr", 0, "Ptr", fcAddr, "Int", 0, "Int", 0, "UInt", 0, "Ptr")
if (!hook) {
Println("Failed to set foreground hook")
return false
}
Println("Set foreground hook")
fgHookInstalled := true
}
else if (fgHookInstalled and !state) {
if (!DllCall("UnhookWinEvent", "Ptr", hook)) {
Println("Failed to unset foreground hook")
return false
}
Println("Unset foreground hook")
fgHookInstalled := false
}
return true
}
; Radio button handler that controls installation of the sound profile hook
HookRadioHandler() {
state := A_GuiControl == "On"
if (!SetHook(state)) {
GuiControl,, % (state ? "Off" : "On"), 1
}
}
; Foreground window change callback
FgCallback(hWinEventHook, event, hWnd, idObject, idChild, dwEventThread, dwmsEventTime) {
global ftspsActive
WinGet fnHWnd, ID, Fortnite
WinGetTitle t, ahk_id %hWnd%
Println("fg window: " t)
if (!ftspsActive and fnHWnd = hWnd) {
Run_Peace_Profile("Ftsps")
ftspsActive := true
}
else if (ftspsActive and fnHWnd != hWnd) {
Run_Peace_Profile("Graphic EQ")
ftspsActive := false
}
}
; Prints a line to the logging edit box
Println(s) {
global hLog
static MAX_LINES := 1000, LINE_ADJUST := 200, nLines := 0
; EM_SETSEL = 0xB1, EM_REPLACESEL = 0xC2, EM_LINEINDEX = 0xBB
if (nLines = MAX_LINES) {
; Delete the oldest LINE_ADJUST lines
SendMessage 0xBB, LINE_ADJUST,,, ahk_id %hLog%
SendMessage 0xB1, 0, ErrorLevel,, ahk_id %hLog%
SendMessage 0xC2, 0, 0,, ahk_id %hLog%
nLines -= LINE_ADJUST
}
++nLines
; Move to the end by selecting all and deselecting
SendMessage 0xB1, 0, -1,, ahk_id %hLog%
SendMessage 0xB1, -1, -1,, ahk_id %hLog%
; Add the line
str := "[" A_Hour ":" A_Min "] " s "`r`n"
SendMessage 0xC2, 0, &str,, ahk_id %hLog%
}
我想在每次切换到特定程序时激活一个声音配置文件,并在每次离开时改回默认配置文件。 此操作通过单选按钮在 GUI 中打开。
我创建的解决方法是:
Auto_Ftsps:
gui, Submit, NoHide
While (Rad3==1)
{
Previous_window:= WinActive("A")
Sleep,1000
Current_window:= WinActive("A")
If (Previous_window =Current_window)
{}
Else If (Previous_window !=Current_window)
{
If(WinActive("Fortnite"))
Run_Peace_Profile("Ftsps")
Else
Run_Peace_Profile("Graphic EQ")
}
Sleep,2000
}
return
有更好的方法吗?我查看了论坛和教程,但没有成功。
OnWin.ahk和你的方法有些相似;它使用 SetTimer
to periodically check for the events you register with it, so unlike your method it's asynchronous in terms of AHK threads. Don't quote me on this, but I think internally WinWaitActive
也是类似的。
然而,还有另一种方法,它不涉及定期检查活动 window 我们自己而是允许我们对 Windows' "active window change" 事件做出反应 - shell 钩。通常 SetWindowsHookEx
with WH_SHELL
would be used for this, but I don't think it's even possible to use it with AHK alone (you have to make a DLL), and it's kind of complicated to get everything right. Luckily there's RegisterShellHookWindow
, which allows us to receive shell events as Windows messages rather than injecting a DLL into other threads. We can then use AHK's OnMessage
对这些消息做出反应,在您的情况下,这意味着具有测试 wParam
为 HSHELL_WINDOWACTIVATED
或 HSHELL_RUDEAPPACTIVATED
的功能(即设置位 3 ) 并相应地更改声音配置文件。至于启用此功能 on/off,我们可以让单选按钮的 g-label 包含控制我们是否要通过 (De)RegisterShellHookWindow
.[=33 接收 shell 消息的逻辑=]
#SingleInstance Force
Gui +AlwaysOnTop +HwndhWnd
Gui Add, Text,, Automatic sound profile change
Gui Add, Radio, gHookRadioHandler Checked, On
Gui Add, Radio, gHookRadioHandler X+, Off
Gui Font,, Consolas
Gui Add, Edit, HwndhLog xm w800 r30 ReadOnly -Wrap -WantReturn
ftspsActive := false
; Get the dynamic identifier for shell messages and assign our callback to handle these messages
SHELL_MSG := DllCall("RegisterWindowMessage", "Str", "SHELLHOOK", "UInt")
OnMessage(SHELL_MSG, Func("ShellCallback"))
if (!SetHook(true)) {
GuiControl,, Off, 1
}
Gui Show
GuiClose() {
ExitApp
}
; Dummy implementation that logs the changes to an edit control for demonstration purposes
Run_Peace_Profile(profile) {
Println("Switched to " profile)
}
; Sets whether the shell hook is registered
SetHook(state) {
global hWnd
static shellHookInstalled := false
if (!shellHookInstalled and state) {
if (!DllCall("RegisterShellHookWindow", "Ptr", hWnd)) {
Println("Failed to register shell hook")
return false
}
Println("Registered shell hook")
shellHookInstalled := true
}
else if (shellHookInstalled and !state) {
if (!DllCall("DeregisterShellHookWindow", "Ptr", hWnd)) {
Println("Failed to deregister shell hook")
return false
}
Println("Deregistered shell hook")
shellHookInstalled := false
}
return true
}
; Radio button handler that controls registration of the sound profile hook
HookRadioHandler() {
state := A_GuiControl == "On"
if (!SetHook(state)) {
GuiControl,, % (state ? "Off" : "On"), 1
}
}
; Shell messages callback
ShellCallback(wParam, lParam) {
; HSHELL_WINDOWACTIVATED = 4, HSHELL_RUDEAPPACTIVATED = 0x8004
if (wParam & 4) {
; lParam = hWnd of activated window
global ftspsActive
WinGet fnHWnd, ID, Fortnite
WinGetTitle t, ahk_id %lParam%
Println("active window: " t)
if (!ftspsActive and fnHWnd = lParam) {
Run_Peace_Profile("Ftsps")
ftspsActive := true
}
else if (ftspsActive and fnHWnd != lParam) {
Run_Peace_Profile("Graphic EQ")
ftspsActive := false
}
}
}
; Prints a line to the logging edit box
Println(s) {
global hLog
static MAX_LINES := 1000, LINE_ADJUST := 200, nLines := 0
; EM_SETSEL = 0xB1, EM_REPLACESEL = 0xC2, EM_LINEINDEX = 0xBB
if (nLines = MAX_LINES) {
; Delete the oldest LINE_ADJUST lines
SendMessage 0xBB, LINE_ADJUST,,, ahk_id %hLog%
SendMessage 0xB1, 0, ErrorLevel,, ahk_id %hLog%
SendMessage 0xC2, 0, 0,, ahk_id %hLog%
nLines -= LINE_ADJUST
}
++nLines
; Move to the end by selecting all and deselecting
SendMessage 0xB1, 0, -1,, ahk_id %hLog%
SendMessage 0xB1, -1, -1,, ahk_id %hLog%
; Add the line
str := "[" A_Hour ":" A_Min "] " s "`r`n"
SendMessage 0xC2, 0, &str,, ahk_id %hLog%
}
请注意,我以编辑控件的形式添加了一些反馈消息,因此此脚本可以用作小型 stand-alone 演示。
这种方法的一个可能缺点来自 RegisterShellHookWindow
文档的顶部:
This function is not intended for general use. It may be altered or unavailable in subsequent versions of Windows.
此外,我不知道 "rude app" 是什么,也不知道为什么它们有自己的常量。 This question 说它与 full-screen 个应用程序有关,但提问者和我似乎每个程序都收到 HSHELL_RUDEAPPACTIVATED
。
作为替代方案,还有 SetWinEventHook
,可以用 EVENT_SYSTEM_FOREGROUND
和 WINEVENT_OUTOFCONTEXT
调用,以安装每次前台调用的 AHK 回调 window 变化。请注意,与 RegisterShellHookWindow
方法不同,这将在子 windows 进入前台时调用。
#SingleInstance Force
Gui +AlwaysOnTop
Gui Add, Text,, Automatic sound profile change
Gui Add, Radio, gHookRadioHandler Checked, On
Gui Add, Radio, gHookRadioHandler X+, Off
Gui Font,, Consolas
Gui Add, Edit, HwndhLog xm w800 r30 ReadOnly -Wrap -WantReturn
ftspsActive := false
fcAddr := RegisterCallback(Func("FgCallback"))
if (!SetHook(true)) {
GuiControl,, Off, 1
}
Gui Show
GuiClose() {
ExitApp
}
; Dummy implementation that logs the changes to an edit control for demonstration purposes
Run_Peace_Profile(profile) {
Println("Switched to " profile)
}
; Sets whether the foreground hook is installed
SetHook(state) {
global fcAddr
static hook, fgHookInstalled := false
if (!fgHookInstalled and state) {
; EVENT_SYSTEM_FOREGROUND = 3, WINEVENT_OUTOFCONTEXT = 0
hook := DllCall("SetWinEventHook", "UInt", 3, "UInt", 3, "Ptr", 0, "Ptr", fcAddr, "Int", 0, "Int", 0, "UInt", 0, "Ptr")
if (!hook) {
Println("Failed to set foreground hook")
return false
}
Println("Set foreground hook")
fgHookInstalled := true
}
else if (fgHookInstalled and !state) {
if (!DllCall("UnhookWinEvent", "Ptr", hook)) {
Println("Failed to unset foreground hook")
return false
}
Println("Unset foreground hook")
fgHookInstalled := false
}
return true
}
; Radio button handler that controls installation of the sound profile hook
HookRadioHandler() {
state := A_GuiControl == "On"
if (!SetHook(state)) {
GuiControl,, % (state ? "Off" : "On"), 1
}
}
; Foreground window change callback
FgCallback(hWinEventHook, event, hWnd, idObject, idChild, dwEventThread, dwmsEventTime) {
global ftspsActive
WinGet fnHWnd, ID, Fortnite
WinGetTitle t, ahk_id %hWnd%
Println("fg window: " t)
if (!ftspsActive and fnHWnd = hWnd) {
Run_Peace_Profile("Ftsps")
ftspsActive := true
}
else if (ftspsActive and fnHWnd != hWnd) {
Run_Peace_Profile("Graphic EQ")
ftspsActive := false
}
}
; Prints a line to the logging edit box
Println(s) {
global hLog
static MAX_LINES := 1000, LINE_ADJUST := 200, nLines := 0
; EM_SETSEL = 0xB1, EM_REPLACESEL = 0xC2, EM_LINEINDEX = 0xBB
if (nLines = MAX_LINES) {
; Delete the oldest LINE_ADJUST lines
SendMessage 0xBB, LINE_ADJUST,,, ahk_id %hLog%
SendMessage 0xB1, 0, ErrorLevel,, ahk_id %hLog%
SendMessage 0xC2, 0, 0,, ahk_id %hLog%
nLines -= LINE_ADJUST
}
++nLines
; Move to the end by selecting all and deselecting
SendMessage 0xB1, 0, -1,, ahk_id %hLog%
SendMessage 0xB1, -1, -1,, ahk_id %hLog%
; Add the line
str := "[" A_Hour ":" A_Min "] " s "`r`n"
SendMessage 0xC2, 0, &str,, ahk_id %hLog%
}