1 (изменено: teadrinker, 2017-04-27 01:21:55)

Тема: AHK: ToolTip/TrayTip с выбором цвета и шрифта

Class ToolTip

Представляю класс ToolTip, который можно использовать для создания ToolTip'ов и TrayTip'ов с возможностью выбора шрифта (и его размера и стиля), а также цвета фона и текста. Подобных разработок существует множество, моя более функциональна, чем, например, эта, и более компактна, чем эта.
Сам класс:

class ToolTip  {
/*
Версия: 1.05

При создании экземпляра объекта в конструктор передаётся ассоциативный массив с опциями.
Возможные ключи и их псевдонимы:

title
text
icon (1 — Info, 2 — Warning; 3 — Error; n > 3 — предполагается hIcon)
CloseButton (или close) — true или false
transparent (или trans) — true или false, указывает, будет ли ToolTip прозрачен для кликов мыши
ShowNow (или now) — true или false, показывать или не показывать ToolTip при создании экземпляра объекта
   Если параметр не указан, ToolTip будет показан сразу же.
x, y — координаты, если не указаны, ToolTip появится вблизи курсора
BalloonTip (или balloon, или ball) — true или false, BalloonTip — это ToolTip с хвостиком
TrayTip (или tray) — будет показан BalloonTip у иконки скрипта в трее, параметры x, y, и BalloonTip игнорируются
   Если указан ключ TrayTip, удалить экземпляр объекта можно либо методом Destroy(),
      либо указав Timeout с отрицателным значением.
   Если нет, тогда можно просто прировнять ссылку на объект пустому значению.
FontName (или font)
FontSize (или size)
FontStyle (или style) — bold, italic, underline, strikeout в любом сочетании через пробел
Timeout (или time) — время в милисекундах, через которое ToolTip будет скрыт, если число положительное,
   либо уничтожен, если отрицательное
BackColor (или back) — цвет фона
TextColor (или color) — цвет текста
   
Для указания цвета можно использовать литеральные названия, перечисленные здесь:
https://autohotkey.com/docs/commands/Progress.htm#colors
В этом случае название должно быть в кавычках.

Ключи можно задавать в любом порядке и в любой комбинации, как показано в примерах использования.
Если указан ключ TrayTip, удалить экземпляр объекта можно только методом Destroy(),
если нет, тогда можно просто прировнять ссылку на объект пустому значению.

Публичные методы объекта:

1. Show(x, y, timeout) — показ ранее скрытого ToolTip'а, указание координат и времени показа.
   x и y — координаты, если пустые значения — ToolTip будет показан возле курсора
   timeout — время в милисекундах, через которое ToolTip будет скрыт, если число положительное, либо уничтожен, если отрицательное
2. Hide() — скрытие ToolTip
3. SetText(text) — изменение текста
4. SetTitle(icon, title) — изменение иконки и заголовка
5. Destroy() — уничтожение экземпляра объекта

Описание и примеры использования (GitHub): https://goo.gl/51pZeA
Автор teadrinker (на GitHub jollycoder), email dfiveg@mail.ru, skype dmitry_fiveg
*/
   __New( options )  {
      this.ShowNow := true
      for k, v in options
         this[k] := v
      this._CreateToolTip()
      ( this.ShowNow && this.Show(this.x, this.y, this.Timeout) )
   }
   
   __Set(key, value)  {
      static PsevdoKeys := { close: "CloseButton", trans: "transparent", size: "FontSize"
                           , balloon: "BalloonTip", ball: "BalloonTip", tray: "TrayTip"
                           , style: "FontStyle", time: "Timeout", font: "FontName"
                           , back: "BackColor", color: "TextColor", now: "ShowNow" }
                           
      for k, v in PsevdoKeys
         if (key = k)
            this[v] := value
   }
   
   _CreateToolTip()  {
      static WS_POPUP := 0x80000000, WS_EX_TOPMOST := 8, WS_EX_TRANSPARENT := 0x20
           , TTS_NOPREFIX := 2, TTS_ALWAYSTIP := 1, TTS_BALLOON := 0x40, TTS_CLOSE := 0x80
           , TTF_TRACK := 0x20, TTF_ABSOLUTE := 0x80, szTI := A_PtrSize = 4 ? 48 : 72
     
      VarSetCapacity(TOOLINFO, szTI, 0)
      this.pTI := &TOOLINFO
      NumPut(szTI, TOOLINFO)
      (this.TrayTip && this.BalloonTip := true)
      NumPut(TTF_TRACK|(this.BalloonTip ? 0 : TTF_ABSOLUTE), &TOOLINFO + 4)
      
      this.hwnd := DllCall("CreateWindowEx", UInt, WS_EX_TOPMOST|(this.transparent ? WS_EX_TRANSPARENT : 0)
        , Str, "tooltips_class32", Str, ""
        , UInt, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | (this.CloseButton ? TTS_CLOSE : 0) | (this.BalloonTip ? TTS_BALLOON : 0)
        , Int, 0, Int, 0, Int, 0, Int, 0, Ptr, 0, Ptr, 0, Ptr, 0, Ptr, 0)
        
      if (this.FontName || this.FontSize || this.FontStyle)
         this._SetFont()
      
      if (this.TextColor != "" || this.BackColor != "")
         this._SetColor()
      
      this._SetInfo()
   }
   
   _SetFont()  {
      static WM_GETFONT := 0x31, mult := A_IsUnicode ? 2 : 1, szLF := 28 + 32 * mult
           , LOGPIXELSY := 90, ANTIALIASED_QUALITY := 4
           , styles := { bold: {value: 700, offset: 16, size: "Int"}
                       , italic: {value: 1, offset: 20, size: "Char"}
                       , underline: {value: 1, offset: 21, size: "Char"}
                       , strikeout: {value: 1, offset: 22, size: "Char"} }
                       
      hPrevFont := this._SendMessage(WM_GETFONT)
      VarSetCapacity(LOGFONT, szLF, 0)
      DllCall("GetObject", Ptr, hPrevFont, Int, szLF, Ptr, &LOGFONT)
      DllCall("DeleteObject", Ptr, hPrevFont)
      
      if this.FontSize  {
         hDC := DllCall("GetDC", Ptr, this.hwnd, Ptr)
         height := -DllCall("MulDiv", Int, this.FontSize, Int, DllCall("GetDeviceCaps", Ptr, hDC, Int, LOGPIXELSY), Int, 72)
         DllCall("ReleaseDC", Ptr, this.hwnd, Ptr, hDC)
         NumPut(height, LOGFONT, "Int")
      }
      FontStyle := this.FontStyle
      Loop, parse, FontStyle, %A_Space%
         if obj := styles[A_LoopField]
            NumPut(obj.value, &LOGFONT + obj.offset, obj.size)

      (this.FontSize > 24 && NumPut(ANTIALIASED_QUALITY, &LOGFONT + 26, "Char"))
      this.FontName && StrPut(this.FontName, &LOGFONT + 28, StrLen(this.FontName) * mult, A_IsUnicode ? "UTF-16" : "CP0")
      this.hFont := DllCall("CreateFontIndirect", Ptr, &LOGFONT, Ptr)
   }
   
   _SetColor()  {
      static WM_USER := 0x400, TTM_SETTIPBKCOLOR := WM_USER + 19, TTM_SETTIPTEXTCOLOR := WM_USER + 20
      
      VarSetCapacity(empty, 2, 0)
      DllCall("UxTheme\SetWindowTheme", Ptr, this.hwnd, Ptr, 0, Ptr, &empty)   
      ( this.TextColor != "" && this._SendMessage(TTM_SETTIPTEXTCOLOR, this._GetColor(this.TextColor)) )
      ( this.BackColor != "" && this._SendMessage(TTM_SETTIPBKCOLOR, this._GetColor(this.BackColor)) )
   }
   
   _GetColor(color)  {
      static WS_CHILD := 0x40000000, WM_CTLCOLORSTATIC := 0x138
      
      Gui, New, +hwndhGui +%WS_CHILD%
      Gui, Color, % color + 0 = "" ? color : Format("{:x}", color)
      Gui, Add, Text, hwndhText
      hdc := DllCall("GetDC", Ptr, hText, Ptr)
      SendMessage, WM_CTLCOLORSTATIC, hdc, hText,, ahk_id %hGui%
      clr := DllCall("GetBkColor", Ptr, hdc)
      DllCall("ReleaseDC", Ptr, hText, Ptr, hdc)
      Gui, Destroy
      Return clr
   }
   
   _SetInfo()  {
      static WM_USER := 0x400, TTM_SETMAXTIPWIDTH := WM_USER + 24
           , TTM_ADDTOOL := WM_USER + (A_IsUnicode ? 50 : 4), WM_SETFONT := 0x30
      
      this._SendMessage(WM_SETFONT, this.hFont, 1)
      this._SendMessage(TTM_ADDTOOL, 0, this.pTI)
      this.SetTitle(this.icon, this.title)
      this._SendMessage(TTM_SETMAXTIPWIDTH, 0, A_ScreenWidth)
      this.SetText(this.text)
   }
   
   SetText(text)  {
      static WM_USER := 0x400, TTM_UPDATETIPTEXT := WM_USER + (A_IsUnicode ? 57 : 12)
      NumPut(&text, this.pTI + (A_PtrSize = 4 ? 36 : 48))
      this._SendMessage(TTM_UPDATETIPTEXT, 0, this.pTI)
   }
   
   SetTitle(icon := "", title := "")  {
      static WM_USER := 0x400, TTM_SETTITLE := WM_USER + (A_IsUnicode ? 33 : 32), TTM_UPDATE := WM_USER + 29
      
      ((icon || this.CloseButton) && title = "") && title := " "
      this._SendMessage(TTM_SETTITLE, icon, &title)
      (icon > 3 && DllCall("DestroyIcon", Ptr, icon))
      this._SendMessage(TTM_UPDATE)
   }
   
   Show(x := "", y := "", timeout := "")  {
      static WM_USER := 0x400, TTM_TRACKACTIVATE := WM_USER + 17, TTM_TRACKPOSITION := WM_USER + 18
      
      if (x = "TrayTimer")
         Critical
      
      if this.TrayTip  {
         this._GetTrayIconCoords(xTT, yTT)
         if !this.SetTrayTimer  {
            this.TrayTimer := timer := ObjBindMethod(this, "Show", "TrayTimer")
            SetTimer, % timer, 1000
            this.SetTrayTimer := true
         }
         else  {
            this._CheckPosAboveTaskBar()
            if (this.xTT = xTT && this.yTT = yTT)
               Return
            else
               this.xTT := xTT, this.yTT := yTT
         }
      }
      else  {
         xTT := x, yTT := y
      
         if (xTT = "" || yTT = "") {
            CoordMode, Mouse
            MouseGetPos, xm, ym
            (xTT = "" && xTT := xm + 10)
            (yTT = "" && yTT := ym + 10)
         }
      }
      this._SendMessage(TTM_TRACKPOSITION, 0, xTT|(yTT<<16))
      this._SendMessage(TTM_TRACKACTIVATE, 1, this.pTI)
      
      if this.BalloonTip
         xMax := A_ScreenWidth, yMax := A_ScreenHeight
      else  {
         WinGetPos,,, W, H, % "ahk_id" this.hwnd
         xMax := A_ScreenWidth - W - 10
         yMax := A_ScreenHeight - H - 10
      }
      
      if (xTT > xMax || yTT > yMax)  {
         (xTT > xMax && xTT := xMax)
         (yTT > yMax && yTT := yMax)
         this._SendMessage(TTM_TRACKPOSITION, 0, xTT|(yTT<<16))
      }

      if timeout  {
         timer := this.timer
         try SetTimer, % timer, Delete
         this.timer := timer := ObjBindMethod(this, timeout > 0 ? "Hide" : "Destroy")
         SetTimer % timer, % "-" . Abs(timeout)
      }
   }
   
   Hide()  {
      static WM_USER := 0x400, TTM_TRACKACTIVATE := WM_USER + 17
      this._SendMessage(TTM_TRACKACTIVATE, 0, this.pTI)
      if this.SetTrayTimer  {
         this.SetTrayTimer := false
         timer := this.TrayTimer
         SetTimer, % timer, Delete
      }
   }
   
   Destroy()  {
      this.__Delete()
      this.SetCapacity(0)
      this.base := ""
   }
   
   _CheckPosAboveTaskBar()  {
      static GW_HWNDNEXT := 2, SWP_NOSIZE := 1, SWP_NOMOVE := 2
      
      hTaskBar := WinExist("ahk_class Shell_TrayWnd")
      Loop
         hWnd := A_Index = 1 ? DllCall("GetTopWindow", Ptr, 0, Ptr) : DllCall("GetWindow", Ptr, hWnd, UInt, GW_HWNDNEXT, Ptr)
      until (hWnd = hTaskBar && TaskBarAbove := true) || hWnd = this.hwnd
      
      if TaskBarAbove
         DllCall("SetWindowPos", Ptr, this.hwnd, Ptr, 0
                               , Int, 0, Int, 0, Int, 0, Int, 0
                               , UInt, SWP_NOSIZE | SWP_NOMOVE )
   }
   
   _SendMessage(msg, wp := 0, lp := 0)  {
      Return DllCall("SendMessage", Ptr, this.hwnd, UInt, msg, Ptr, wp, Ptr, lp, Ptr)
   }
   
   _GetTrayIconCoords(ByRef x, ByRef y)  {
      static WM_USER := 0x400, TB_BUTTONCOUNT := WM_USER + 24, TB_GETBUTTON := WM_USER + 23, TB_GETITEMRECT := WM_USER + 29
           , PtrSize := A_Is64bitOS ? 8 : 4, szTBBUTTON := 8 + PtrSize*3, szHWND := PtrSize
         
      for k, v in ["TrayNotifyWnd", "SysPager", "ToolbarWindow32"]
         hTray := DllCall("FindWindowEx", Ptr, k = 1 ? WinExist("ahk_class Shell_TrayWnd") : hTray, Ptr, 0, Str, v, UInt, 0, Ptr)
      WinWait, ahk_id %hTray%
      WinGet, PID, PID
      WinGetPos, xTB, yTB
      
      if !IsObject(RemoteBuff := new this.RemoteBuffer(PID, szTBBUTTON))  {
         x := xTB, y := yTB
         MsgBox, % "Не удалось создать удалённый буфер`nОшибка " A_LastError
         Return
      }
      SendMessage, TB_BUTTONCOUNT
      Loop % ErrorLevel  {
         SendMessage, TB_GETBUTTON, A_Index - 1, RemoteBuff.ptr
         pTBBUTTON := RemoteBuff.Read(szTBBUTTON)
         pHWND := RemoteBuff.Read(szHWND, NumGet(pTBBUTTON + 8 + PtrSize) - RemoteBuff.ptr)
         hWnd := NumGet(pHWND + 0, PtrSize = 4 ? "UInt" : "UInt64")
         if (hWnd = A_ScriptHwnd)  {
            SendMessage, TB_GETITEMRECT, A_Index - 1, RemoteBuff.ptr
            pRECT := RemoteBuff.Read(16)
            x := xTB + ( NumGet(pRECT + 0, "Int") + NumGet(pRECT + 8, "Int") )//2
            y := yTB + ( NumGet(pRECT + 4, "Int") + NumGet(pRECT + 12, "Int") )//2 - 5
            break
         }
      }
      RemoteBuff := "", ((x = "" || y = "") && (x := xTB, y := yTB))
   }
   
   __Delete()  {
      (this.hFont && DllCall("DeleteObject", Ptr, this.hFont))
      (this.Icon > 3 && DllCall("DestroyIcon", Ptr, this.Icon))
      DllCall("DestroyWindow", Ptr, this.hwnd)
   }

   class RemoteBuffer
   {
      __New(PID, size)  {
         static PROCESS_VM_OPERATION := 0x8, PROCESS_VM_WRITE := 0x20
              , PROCESS_VM_READ := 0x10, MEM_COMMIT := 0x1000, PAGE_READWRITE := 0x4
            
         if !(this.hProc := DllCall("OpenProcess", UInt, PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE, Int, 0, UInt, PID, Ptr))
            Return
         
         if !(this.ptr := DllCall("VirtualAllocEx", Ptr, this.hProc, Ptr, 0, Ptr, size, UInt, MEM_COMMIT, UInt, PAGE_READWRITE, Ptr))
            Return, "", DllCall("CloseHandle", Ptr, this.hProc)
         
         this.hHeap := DllCall("GetProcessHeap", Ptr)
      }
      
      __Delete()  {
         DllCall("VirtualFreeEx", Ptr, this.hProc, Ptr, this.ptr, UInt, 0, UInt, MEM_RELEASE := 0x8000)
         DllCall("CloseHandle", Ptr, this.hProc)
         DllCall("HeapFree", Ptr, this.hHeap, UInt, 0, Ptr, this.pHeap)
      }
      
      Read(size, offset = 0)  {
         (this.pHeap && DllCall("HeapFree", Ptr, this.hHeap, UInt, 0, Ptr, this.pHeap))
         this.pHeap := DllCall("HeapAlloc", Ptr, this.hHeap, UInt, HEAP_ZERO_MEMORY := 0x8, Ptr, size, Ptr)
         if !DllCall("ReadProcessMemory", Ptr, this.hProc, Ptr, this.ptr + offset, Ptr, this.pHeap, Ptr, size, Int, 0)
            Return, 0, DllCall("MessageBox", Ptr, 0, Str, "Не удалось прочитать данные`nОшибка " A_LastError, Str, "", UInt, 0)
         Return this.pHeap
      }
      
      Write(pLocalBuff, size, offset = 0)  {
         if !res := DllCall("WriteProcessMemory", Ptr, this.hProc, Ptr, this.ptr + offset, Ptr, pLocalBuff, Ptr, size, PtrP, writtenBytes)
            DllCall("MessageBox", Ptr, 0, Str, "Не удалось записать данные`nОшибка " A_LastError, Str, "", UInt, 0)
         Return writtenBytes
      }
   }
}
Примеры использования
myToolTip := new ToolTip({ title: "Title"
                         , text: "I'm the colored TrayTip!"
                         , icon: 1  ; icon Info
                         , TrayTip: true
                         , FontName: "Comic Sans MS"
                         , FontSize: 20
                         , TextColor: "Navy"
                         , BackColor: 0xFFA500 })
#Include %A_ScriptDir%\ToolTip.ahk

Результат:

http://i.imgur.com/txDfd6v.jpg


Для работы примеров необходимо сохранить класс ToolTip в файл ToolTip.ahk и положить его в папку скрипта, либо удалить из кода строку #Include %A_ScriptDir%\ToolTip.ahk и дописать класс непосредственно в код примера.
Ещё пример:

myTrayTip := new ToolTip({ title: " Look!"
                         , text: " It's a blue TrayTip! "
                         , FontSize: 18
                         , icon: 2            ; icon Warning
                         , TrayTip: true      ; будет отображаться возле иконки скрипта в трее
                         , Timeout: -4000     ; TrayTip будет удалён через 4 секунды
                         , BackColor: 0x0055aa
                         , TextColor: "white" })
Sleep, 2500
myTrayTip.SetTitle()  ; удаляем иконку и текст заголовка
myTrayTip.SetText("Goodbye!")
Sleep, 2000
ExitApp

#Include %A_ScriptDir%\ToolTip.ahk

Более сложный пример:

myTrayTip := new ToolTip({ text: "Модифицированный TrayTip"
                         , title: "Стандартная иконка «Info»"
                         , CloseButton: true
                         , Icon: 1              ; 1 — Info, 2 — Warning, 3 — Error, n > 3 — предполагается hIcon
                         , TrayTip: true
                         , ShowNow: false       ; изначально показан не будет
                         , FontSize: 16
                         , FontName: "Verdana" })

myToolTip := new ToolTip({ text: "Это модифицированный ToolTip.`nОн прозрачен для кликов мыши."
                         , title: "Кастомная иконка"
                         , Timeout: 2500
                         , Icon: hIcon := CreateIconFromBase64(GetIcon(), 32)
                         , transparent: true    ; если true — окно прозрачно для мыши
                         , BalloonTip: true
                         , FontSize: 22
                         , FontName: "Times New Roman"
                         , FontStyle: "italic" })
                         
ColoredTip:= new ToolTip({ text: "Я цветной ToolTip!`nЯ исчезну через 7 сек"
                         , x: A_ScreenWidth
                         , y: 0
                         , BalloonTip: true
                         , TextColor: "Navy"
                         , BackColor: 0xFFA500
                         , FontName: "Comic Sans MS"
                         , FontSize: 20
                         , Timeout: -7000 })  ; будет автоматически удалён через 7000 мс
                         
oTimer := Func("Timer").Bind(ColoredTip)
SetTimer, % oTimer, 1000  ; этот таймер только для изменения текста
Sleep, 1000
myTrayTip.Show()  ; показываем изначально скрытый TrayTip, созданный с параметром ShowNow: false
Sleep, 1500
myToolTip.SetText("Можно менять`nтекст и расположение")  ; меняем текст
Sleep, 500

myToolTip.Show(50, 50, 2000)  ; показываем в координатах x = 50, y = 50 на две секунды
Sleep, 2000

myToolTip.SetText("I'm near the mouse cursor!")
Sleep, 500

myToolTip.Show("", "", -2500)  ; без координат — в районе курсора, таймаут отрицательный, значит объект через 
                               ; две с половиной секунды удалится без возможности дальнейшего использования
Sleep, 2500

myTrayTip.SetTItle(2, "Изменённый заголовок")  ; меняем иконку и заголовок
myTrayTip.SetText("Goodbye!")
Sleep, 1000
myTrayTip.Destroy()
Sleep, 1000
ExitApp

Timer(oTip)  {
   static i := 0
   oTip.SetText("Я цветной ToolTip!`r`nЯ исчезну через " (7 - ++i) " сек")
   if (i = 7)
      SetTimer,, Delete
}

CreateIconFromBase64(StringBASE64, Size)
{
   StringBase64ToData(StringBASE64, IconData)
   Return DllCall("CreateIconFromResourceEx", Ptr, &IconData + 4
      , UInt, NumGet(&IconData, "UInt"), UInt, true, UInt, 0x30000, Int, Size, Int, Size, UInt, 0)
}
   
StringBase64ToData(StringBase64, ByRef OutData)
{
   DllCall("Crypt32.dll\CryptStringToBinary", Ptr, &StringBase64 
      , UInt, StrLen(StringBase64), UInt, CRYPT_STRING_BASE64 := 1, UInt, 0, UIntP, Bytes, UIntP, 0, UIntP, 0)

   VarSetCapacity(OutData, Bytes) 
   DllCall("Crypt32.dll\CryptStringToBinary", Ptr, &StringBase64 
      , UInt, StrLen(StringBase64), UInt, CRYPT_STRING_BASE64, Str, OutData, UIntP, Bytes, UIntP, 0, UIntP, 0)
   Return Bytes
}

GetIcon()  {
   Icon32 = 
   (RTrim Join
      qBAAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
      FRgjABMXIQATFyEAFBchABMXIQAVGCMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
      AAAAABUYIwAAAAAAAAAAAAAAAAIAAAAAAAAAABUYIwAVGCMAExchABMXIRQUFyETExchABUYIwAVGCMAAAAAAAAAAAAAAAACAAAAAAAAAAAVGCMAAAAAAAAAAAAAAAAA
      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjABQXIgAUFyIAFBciABMXIQATFyEAFRgjABUYIwIUFyIAFBciqxQXIqoUFyIAFRgjAhUYIwAUFyIA
      FBgiABQXIgATFyIAExciABUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVGCMAFBciABQXIhAUFiFCExgiABMWIQAAAAAA
      FRgjAxUYIwAVFiO4ExghuBUYIwAVGCMDAAAAABQXIgATGiIAExchQhMYIhATGCIAFRgjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
      AAAAABUYIwEUFyIAFBchMhQXIv8TFyFDFBciABUXIwIVGCMCFRgjABUXI7sUGCK7FRgjABUYIwIUFyMCFBciABQXIkMUFyL/FBchMhQXIgAVGCMBAAAAAAAAAAAAAAAA
      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjABUXIgEVFyIAFBciuRQXItoVGSICFRgiARUYIwMUFyIBFBcinRMXIp0UFyIBFRgjAxUYIgEVGCIC
      FBci2hQXIroVFyIAFRciARUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjAAAAAAAAAAAAAAAAAgAAAAEVGCMAFBgjARQXIgAUFyIkFBci/xQXImEUFyIC
      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
      FBci2xQXIrcUGCMAFBgjARUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVGCMBFRciABUXIjIUFyL/FBciQxQXIgAUFyMC
      FRgjAxUYIwAVFyO7FBgiuxUYIwAVGCMDFRgiAhQXIgAUFiFFFBci/xQXIjEUFyIAFRgjAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
      AAAAABUYIwAUFyIAFBciEBQXIkITGCYAFRQiAAAAAAAVGCMDFRgjABUWI7gTGCG4FRgjABUYIwMAAAAAExYgABQVHgATFiFDFBYiEBQWIgAVGCMAAAAAAAAAAAAAAAAA
      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjABQXIgAUFyEAFBciABQXJAAVFSIAFRgjABUYIwIVFyIAFBciqxQXIqoVFyIAFRgjAhUYIwATFiEA
      FBYgABMXIgAUFyIAFBciABUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVGCMAAAAAAAAAAAAAAAACAAAAAAAAAAAVGCMA
      FRgjABUWIQAUFyEVFRYiFBUWIQAVGCMAFRgjAAAAAAAAAAAAAAAAAgAAAAAAAAAAFRgjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVGCMAFRciABQXIgAVFiIAFRYiABUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
      AAAAAAAAAAAAAAAA/////////////n////5///++ff//nnn//8/z///f+//38A/v8eAHj/jAAx//gAH//wAB//8AAP//AAD/wwAAw8MAAMP/AAD//wAA//+AAf//gAH/
      +MADH/HgB4/38A/v///////P8///nnn//759///+f////n////////////8=
   )
   Return Icon32
}

#Include %A_ScriptDir%\ToolTip.ahk

Вопросы можно задать в этой теме.
Пост на GitHub: описание, примеры и changelog.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder