OnWin.ahk 有点类似于你的方法;它使用SetTimer 定期检查您向其注册的事件,因此与您的方法不同,它在AHK 线程方面是异步的。不要引用我的话,但我认为内部WinWaitActive 也是类似的。
但是,还有另一种方法,它不涉及定期检查活动窗口,而是允许我们对 Windows 的“活动窗口更改”事件做出反应 - 外壳挂钩。通常SetWindowsHookEx 和WH_SHELL 将用于此目的,但我认为它甚至不可能单独与AHK 一起使用(你必须制作一个DLL),而且让一切都正确有点复杂。幸运的是,有RegisterShellHookWindow,它允许我们以 Windows 消息的形式接收 shell 事件,而不是将 DLL 注入其他线程。然后,我们可以使用 AHK 的 OnMessage 对这些消息做出反应,在您的情况下,这意味着有一个函数可以测试 wParam 是否为 HSHELL_WINDOWACTIVATED 或 HSHELL_RUDEAPPACTIVATED(即设置了第 3 位)并更改相应的声音配置文件。至于打开/关闭此功能,我们可以让单选按钮的 g-label 包含控制是否要通过(De)RegisterShellHookWindow 接收 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%
}
请注意,我以编辑控件的形式添加了一些反馈消息,以便此脚本可以用作小型独立演示。
这种方法的一个可能缺点来自RegisterShellHookWindow 文档的顶部:
此功能不适用于一般用途。它可能会在 Windows 的后续版本中被更改或不可用。
此外,我不知道什么是“粗鲁的应用程序”,也不知道为什么它们有自己的常数。 This question 说它与全屏应用程序有关,但我和提问者收到了HSHELL_RUDEAPPACTIVATED 似乎每个程序。
作为替代方案,还有SetWinEventHook,可以使用EVENT_SYSTEM_FOREGROUND 和WINEVENT_OUTOFCONTEXT 调用以安装来自AHK 的回调,每次前台窗口更改时都会调用该回调。请注意,与RegisterShellHookWindow 方法不同,这将为进入前台的子窗口调用。
#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%
}