每次目标窗口在 AutoHotkey 中激活时如何激活功能 [英] How to activate a function everytime the target window becomes active in AutoHotkey

查看:31
本文介绍了每次目标窗口在 AutoHotkey 中激活时如何激活功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在每次切换到特定程序时激活声音配置文件,并在每次离开时更改回默认配置文件.此操作通过单选按钮在 GUI 中打开.

I would like to activate a sound profile every time I switch to a specific program and change back to the default profile every time I leave it. This action is turned on in a GUI via a radio button.

我创建的解决方法是:

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

有没有更好的方法来做到这一点?我查看了论坛和教程,但没有成功.

Is there a better way to do this? I looked on forums and tutorials with no success.

推荐答案

OnWin.ahk 有点类似于你的方法;它使用 SetTimer 定期检查您注册的事件它,因此与您的方法不同,它在 AHK 线程方面是异步的.不要引用我的话,但我认为在内部 WinWaitActive 也很相似.

OnWin.ahk is somewhat similar to your method; it uses 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 is similar as well.

然而,还有另一种方法不涉及我们自己定期检查活动窗口,而是允许我们对 Windows 的活动窗口更改"事件做出反应——shell 钩子.通常 SetWindowsHookExWH_SHELL 将用于此,但我认为它甚至不可能单独与 AHK 一起使用(你必须制作一个 DLL),而且它有点复杂把一切都做好.幸运的是有 RegisterShellHookWindow,它允许我们将外壳事件作为 Windows 消息接收,而不是将 DLL 注入其他线程.然后我们可以使用 AHK 的 OnMessage 对这些消息做出反应,在您的情况下,这意味着有一个函数来测试 wParamHSHELL_WINDOWACTIVATEDHSHELL_RUDEAPPACTIVATED(即,位 3 已设置)并更改相应的声音配置文件.至于打开/关闭此功能,我们可以让单选按钮的 g-label 包含控制我们是否要通过 (De)RegisterShellHookWindow 接收 shell 消息的逻辑.

There is however another way that doesn't involve periodically checking the active window ourselves and instead allows us to react to Windows' "active window change" events - a shell hook. Typically 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 to react to these messages, which, in your case, means having a function that tests for to wParam being HSHELL_WINDOWACTIVATED or HSHELL_RUDEAPPACTIVATED (i.e., bit 3 is set) and changes the sound profile accordingly. As for turning this functionality on/off, we can have the radio buttons' g-label contain the logic for controlling whether we want to receive the shell messages via (De)RegisterShellHookWindow.

#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%
}

请注意,我以编辑控件的形式添加了一些反馈消息,以便此脚本可以用作小型独立演示.

Note that I added some feedback messages in the form of an edit control just so this script can serve as a small stand-alone demonstration.

这种方法的一个可能的缺点来自 RegisterShellHookWindow 文档的顶部:

A possible drawback of this approach comes right from the top of the RegisterShellHookWindow documentation:

此功能不适用于一般用途.它可能会在后续版本的 Windows 中更改或不可用.

This function is not intended for general use. It may be altered or unavailable in subsequent versions of Windows.

此外,我不知道什么是粗鲁的应用程序",也不知道为什么它们有自己的常量.这个问题说它与完整的-屏幕应用程序,但提问者和我收到了 HSHELL_RUDEAPPACTIVATED 几乎每个程序.

Additionally, I have no idea what a "rude app" is or why they have their own constant. This question says it has to do with full-screen applications, but the asker and I receive HSHELL_RUDEAPPACTIVATED for seemingly every program.

作为替代,还有 SetWinEventHook,可以用 EVENT_SYSTEM_FOREGROUNDWINEVENT_OUTOFCONTEXT 调用来安装 AHK 的回调,每次前台窗口都会调用它变化.请注意,与 RegisterShellHookWindow 方法不同,这将在子窗口进入前台时调用.

As an alternative, there's also SetWinEventHook, which can be called with EVENT_SYSTEM_FOREGROUND and WINEVENT_OUTOFCONTEXT to install a callback from AHK which is called every time the foreground window changes. Note that this will be called for child windows coming to the foreground, unlike the RegisterShellHookWindow method.

#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%
}

这篇关于每次目标窗口在 AutoHotkey 中激活时如何激活功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆