Descolada / UIA-v2

UIAutomation library for AHK v2, based on thqby's UIA library
MIT License
168 stars 25 forks source link

TrayIcon Clicker #41

Closed cycle4passion closed 3 months ago

cycle4passion commented 3 months ago

I am trying to create function to click trayicons based on their tooltip name. It works for the ones showing, but I am unable to make it also work for those that have relegated to the popup. For UIA to see that area, it has to be clicked, but it seems to immediately disappear when I try to get ahold of it (see commented out line)

#Requires Autohotkey v2.0
Persistent
#SingleInstance

#Include "%A_ScriptDir%\Includes\UIA\UIA.ahk"

; Example usage
ClickTrayIcon("1Password") ; UPDATE HERE and make sure the reference tooltip is in main tray area (not popup)

; Function to click a tray icon by its tooltip text
ClickTrayIcon(iconTooltip) {
  trayIcons := []

  ; Get the system tray window
  hwndTaskbar := WinGetID("ahk_class Shell_TrayWnd")
  hwndTray := ControlGetHwnd("TrayNotifyWnd1", "ahk_id " hwndTaskbar)
  trayElement := UIA.ElementFromHandle(hwndTray)
  promotedNotifyArea := trayElement.FindElement([{ Name: "User Promoted Notification Area" }])
  chevron := TrayElement.FindElement([{ Name: "Notification Chevron" }]).Click()
  ;  overflowNotifyArea := trayElement.FindElement([{ Name: "Overflow Notification Area" }]) ; this causes popup to close, so can't be UIA'ed????
  promotedandoverflow := promotedNotifyArea
  ;promotedandoverflow.push(overflowNotifyArea*) ; like [...promotedNotifyArea, ...overflowNotifyArea]

  icons := promotedandoverflow.FindElements({ Type: "Button" })
  for icon in icons {
    iconName := icon.GetPropertyValue("Name")
    if (RegExMatch(icon.name, iconTooltip))
      icon.Click()
  }
}
Descolada commented 3 months ago

It disappears because trayElement.FindElement([{ Name: "Overflow Notification Area" }]) throws an "Element not found" error and the error message window causes the popup to close. If you inspect the overflow area then you might notice that the UIA tree is smaller than the one for the taskbar. This is because the "popup" is actually a separate window, which you can verify by inspecting it with AHK Window Spy. With Window Spy we find out that its ahk_class is NotifyIconOverflowWindow, but WinExist("ahk_class NotifyIconOverflowWindow") won't return any window handle. This is because by default Windows has decided to filter some windows from the common methods that are used to find windows (see my PR about it), and AHK uses those methods for WinExist etc. We can work around it using FindWindowEx like this:

  ; Waits a maximum of 1 second for the popup to show
  timeout := A_TickCount+1000
  while !(hWnd := DllCall("FindWindowExW", "Ptr", 0, "Ptr", 0, "Str", "NotifyIconOverflowWindow", "Str", "", "UPtr")) && A_TickCount < timeout
    Sleep 1

  overflowNotifyArea := UIA.ElementFromHandle(hWnd)
  overflowNotifyArea.Highlight()

The next part of your code throws an error because promotedNotifyArea isn't an array and thus promotedandoverflow.push(overflowNotifyArea*) can't work.

cycle4passion commented 3 months ago

Thank you for the great libraries, and the help you provided above. If anyone else finds this, Here is the working code.

ClickTrayIcon(iconTooltip, WhichButton := "", ClickCount := 1, DownOrUp := "", Relative := "", NoActivate := False) {
  hwndTaskbar := WinGetID("ahk_class Shell_TrayWnd")
  hwndTray := ControlGetHwnd("TrayNotifyWnd1", "ahk_id " hwndTaskbar)
  trayElement := UIA.ElementFromHandle(hwndTray)
  promotedNotifyArea := trayElement.FindElement([{ Name: "User Promoted Notification Area" }])
  icons := promotedNotifyArea.FindElements({ Type: "Button" })
  chevron := TrayElement.FindElement([{ Name: "Notification Chevron" }])
  chevron.Click() ; open overflow temporarily
  timeout := A_TickCount + 1000 ; See AHK2 Issue; https://github.com/AutoHotkey/AutoHotkey/pull/338
  while !(hWnd := DllCall("FindWindowExW", "Ptr", 0, "Ptr", 0, "Str", "NotifyIconOverflowWindow", "Str", "", "UPtr")) && A_TickCount < timeout
    Sleep 1
  overflowNotifyArea := UIA.ElementFromHandle(hWnd)
  icons2 := overflowNotifyArea.FindElements({ Type: "Button" })
  chevron.Click() ; close overflow
  icons.push(icons2*)
  for icon in icons {
    if (RegExMatch(icon.name, iconTooltip)) {
      icon.Click(WhichButton, ClickCount, DownOrUp, Relative, NoActivate)
      return
    }
  }
}