Search code examples
autohotkey

New Window Detection


The following script for AHK 2.0 is meant to do something to certain windows once, then ignore them while checking for new windows:

#SingleInstance Force
Persistent
SetTitleMatchMode "RegEx"

ids := []
Loop {
 global ids
 all := "ahk_exe ((msedge)|(chrome)|(Notepad)|(olk)|(explorer))\.exe"
 beside := ""
 for i, id in ids {
  beside .= (i == 1 ? " ahk_id ^((?!" : "|") "(" id ")" (i == ids.Length ? ").)+$" : "")
 }
 MsgBox all beside
 WinWait all beside
 ids.push(WinGetID())



 ; Do something with the window here.
 WinSetTransparent 217



 for i, id in ids {
  if(!WinExist("ahk_id " id)) {
   ids.RemoveAt(i)
  } else {
   for n, nid in ids {
    if(n > i && nid == id)
     ids.RemoveAt(n)
   }
  }
 }
}

Right now, it finds the foremost window (usually the Taskbar), then it finds no other window. The regular expression I'm using for the ahk_id values matches anything not within the capture groups; the full statement would look something like this if it finds multiple windows: ahk_exe ((msedge)|(chrome)|(Notepad)|(olk)|(explorer))\.exe ahk_id ^((?!(66328)|(14420486)).)+$.

Is there a better way to exclude a changing list of windows from WinWait? I tried to use window groups and the ExcludeTitle parameter, but I believe that parameter may really only be for titles rather than ahk_criteria. Window groups also cannot be changed without reloading the script.

I just want to find new windows of certain executables, do something, then wait for new windows (not detecting the old windows). How might I accomplish that?


Solution

  • In this question, I needed to affect newly created windows only. Instead of using WinWait, I found a few references to an old example of a Shell Hook. This allows for the creation of a trigger which detects creation, destruction of, and changes to windows.

    I have adapted the example for AutoHotkey 2.0:

    #SingleInstance Force
    Persistent
    
    detector := Gui()
    DllCall("RegisterShellHookWindow", "UInt",detector.Hwnd)
    messenger := DllCall("RegisterWindowMessage", "Str","SHELLHOOK")
    OnMessage(messenger, recipient)
    
    recipient(message, id, *) {
     if(message == 1) {
      if(WinGetProcessName("ahk_id " id) == "YourExecutable.exe") {
    
       ; Do something with the window here.
       WinSetTransparent(229, "ahk_id " id)
    
      }
     }
    }
    

    Concerning the nested if statements:

    if(message == 1) {
     if(WinGetProcessName("ahk_id " id) == "YourExecutable.exe") {
    
    • When detecting if a window has been created (when message == 1), id will contain the id of the window that was created. When message != 1, id may not contain a valid window id, so putting the checks in the same if statement may throw an error.
    • message == 1 checks to see if the hook detected the creation of a window. For a list of the messages the hook can detect, see the link mentioned previously for Shell Hooks.
    • WinGetProcessName("ahk_id " id) == "YourExecutable.exe", can be changed to anything you want to check concerning the window that was created. The title of the window that was created is ("ahk_id " id). Note that this may occasionally throw an error and not find a window in certain circumstances; I'm not sure why it does this, but, to solve this in my personal script, I put this if statement in a try block.

    For this example, I once again set the transparency of the window. I did so using the title of the window detected by the hook, as I mentioned just above: WinSetTransparent(229, "ahk_id " id).

    I hope that this answer is as useful to you as it was for me!