1 (изменено: leex, 2026-04-08 06:58:27)

Тема: AHK v2: Можно ли кнопкой X сворачивать окно в трей, а не kill процесс?

Здравствуйте.

Ищу способ реализовать поведение “сворачивание в трей при закрытии окна” для Snagit Editor 2021 (SnagitEditor.exe) на Windows 10.

Ситуация такая:

программа изначально не поддерживает трей
при закрытии (X / Alt+F4) окно всегда закрывается и завершает процесс
штатных настроек для “minimize/close to tray” нет

Также важно: нужно, чтобы при сворачивании в трей окно убиралось с панели задач, как у обычных tray-приложений.

Пробовал через AutoHotkey (v2):

WinHide / WinShow
перехват Alt+F4
попытки обработать закрытие окна

Но стабильного результата нет — поведение не работает, при закрытии окно всегда закрывается и завершает процесс.

Вопрос:

Можно ли вообще принудительно реализовать “tray behavior” для такого приложения?
Есть ли способ перехватить WM_CLOSE / системное закрытие окна через WinAPI или hooks, чтобы не давать приложению завершать процесс?
Или Snagit Editor в принципе нельзя заставить работать через трей из-за того, что он всегда завершает процесс при закрытии окна?

Интересуют рабочие методы (AHK / WinAPI / hooks), без сторонних tray-утилит.

#SingleInstance Force
SetTitleMatchMode 2

Snagit := "ahk_exe SnagitEditor.exe"

; Alt+F4 → скрыть окно (в "трей")
!F4:: {
    if WinExist(Snagit) {
        WinHide Snagit
        WinSetExStyle "-0x80", Snagit
    }
}

; восстановить окно
^!r:: {
    if WinExist(Snagit) {
        WinShow Snagit
        WinSetExStyle "+0x80", Snagit
        WinActivate Snagit
    }
}

; попытка перехвата закрытия
#HotIf WinActive(Snagit)
~!F4:: {
    WinHide Snagit
    WinSetExStyle "-0x80", Snagit
}
#HotIf

; если окно пропало — контроль видимости
SetTimer CheckSnagit, 1000

CheckSnagit() {
    Snagit := "ahk_exe SnagitEditor.exe"

    if WinExist(Snagit) {
        if !DllCall("IsWindowVisible", "ptr", WinExist(Snagit)) {
            WinShow Snagit
        }
    }
}

2

Re: AHK v2: Можно ли кнопкой X сворачивать окно в трей, а не kill процесс?

leex пишет:
Snagit := "ahk_exe SnagitEditor.exe"

А вы уверены, что у этого процесса только одно окно? Теоретически, их может быть несколько, и не все из них видимы. Лучше окно определять по заголовку, если всегда начинается одинаково или есть одинаковая часть, либо по классу (ahk_class).
Попробуйте запустить такой код и понажимать F11:

#Requires AutoHotkey v2

F11:: {
    DetectHiddenWindows true
    if !hWnd := WinExist('ahk_exe SnagitEditor.exe') {
        throw Error('Failed to get hwnd of SnagitEditor')
    }
    if DllCall('IsWindowVisible', 'Ptr', hWnd) {
        WinHide
    } else {
        WinShow
    }
}

Что происходит?

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

3 (изменено: leex, 2026-04-09 02:12:45)

Re: AHK v2: Можно ли кнопкой X сворачивать окно в трей, а не kill процесс?

Спасибо за наводку, @teadrinker!

Вы были правы — было не то окно.

Использовал ahk_exe SnagitEditor.exe, и вот что происходило:

При запуске скрипта (даже без активного процесса самого Snagit) в панели задач появлялась иконка SnagitEditor, но окно не открывалось по клику на этом значке.
Процесс SnagitEditor.exe стартовал по F11 — в диспетчере было видно, но управление не работало.
F11 переставал работать, когда запускал реальный Snagit штатным методом.


Проверил ваш код, а потом нашел нужное окно — ahk_class SnagIt9Editor.

И вот что удалось реализовать:

#Requires AutoHotkey v2
#SingleInstance Force

SnagitWin := "ahk_class SnagIt9Editor"
global hidden := false

; Чистка стандартного меню AHK в трее
A_TrayMenu.Delete()

; F11 - toggle
F11::Toggle()

; Alt+F4 - скрыть вместо закрытия
!F4:: {
    global SnagitWin
    DetectHiddenWindows true

    if WinActive(SnagitWin) {
        if hwnd := WinExist(SnagitWin) {
            WinHide "ahk_id " hwnd
        }
        return
    }
}

Toggle() {
    global SnagitWin, hidden
    DetectHiddenWindows true

    hwnd := WinExist(SnagitWin)
    if !hwnd
        return

    if DllCall("IsWindowVisible", "ptr", hwnd) {
        WinHide "ahk_id " hwnd
        hidden := true
    } else {
        WinShow "ahk_id " hwnd
        WinActivate "ahk_id " hwnd
        hidden := false
    }
}

F11 — toggle видимости
Клик по крестику (Alt+F4) — и окно летит в трей.


Нужен совет как реализовать обработку ЛКМ по иконке в трее — чтобы разворачивалось окно программы из трея.
Можно ли заменить стандартную зелёную иконку AHK в трее на свою?
Или есть ли другой нормальный способ сделать кнопку в трее для этого?


Буду признателен за любые идеи и дельные советы.

4

Re: AHK v2: Можно ли кнопкой X сворачивать окно в трей, а не kill процесс?

Я использую такой скрипт для сворачивания любых окон в трей, может вам подойдёт, как есть, или приспособите под свои нужды:

#Requires AutoHotkey v2.0
#NoTrayIcon

hider := HideToTray()

!6:: hider.Hide(WinExist('A'))
!7:: hider.RestoreLast()
!8:: hider.RestoreAll()

class HideToTray
{
    ; add classes here to exclude from hiding:
    excludeClasses := ['Progman', 'WorkerW', 'Shell_TrayWnd', 'ApplicationFrameWindow']
    
    static __New() => SingletonDecorator.ApplyTo(this)
    
    __New() {
        this.test := ''
        this.exclude := Map()
        for winClass in this.excludeClasses {
            this.exclude[winClass] := ''
        }
        this.hiddenWindows := []
        this.knownWindows := Map()
        this.knownWindows.DefineProp('DeleteIfExists', {call: (s, p) => s.Has(p) && s.Delete(p)})
        this.SetHooks()
        Loop 2 {
            ObjRelease(ObjPtr(this))
        }
    }

    ; public methods
    Hide(hWnd) {
        if !this.exclude.Has(WinGetClass(hWnd)) {
            this.stopHook := true
            this.hiddenWindows.Push(HideToTray.HiddenWindow(hWnd))
            this.knownWindows[hWnd] := ''
            this.stopHook := false
        }
    }
    RestoreAll() => (this.stopHook := true, this.hiddenWindows := [], this.stopHook := false)
    RestoreLast() => this.hiddenWindows.Has(1) && this.hiddenWindows.Pop()

    ; private methods
    SetHooks() {
        static EVENT_SYSTEM_MINIMIZESTART := 0x0016
             , EVENT_SYSTEM_MINIMIZEEND   := 0x0017
             , EVENT_OBJECT_DESTROY       := 0x8001
             , OBJID_WINDOW := 0

        this.HasOwnProp('stopHook') || (
            this.stopHook := false,
            OnMessage(0x404, this.OnNotify := ObjBindMethod(this, 'AHK_NOTIFYICON')),
            this.destroyHook := WinEventHook(EVENT_OBJECT_DESTROY      , EVENT_OBJECT_DESTROY    , HookProc),
            this.minHook     := WinEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND, HookProc)
        )

        HookProc(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) {
            if idObject = OBJID_WINDOW && !this.stopHook {
                switch event {
                    case EVENT_OBJECT_DESTROY       : this.knownWindows.DeleteIfExists(hwnd), this.UnHide(hwnd)
                    case EVENT_SYSTEM_MINIMIZESTART : this.knownWindows.Has(hwnd) && this.Hide(hwnd)
                    case EVENT_SYSTEM_MINIMIZEEND   : this.UnHide(hwnd)
                }
            }
        }
    }

    UnHide(hWnd) {
        try for i, wnd in this.hiddenWindows {
            continue
        } until wnd.hwnd = hwnd && this.hiddenWindows.RemoveAt(i)
    }

    AHK_NOTIFYICON(wp, lp, msg, hwnd) {
        static WM_LBUTTONDOWN := 0x201, WM_RBUTTONUP := 0x205
             , iconID := 0x404, iconMsg := 0x404, maxLenMenuStr := 40
        if !(lp = WM_LBUTTONDOWN || lp = WM_RBUTTONUP) {
            return
        }
        for i, wnd in this.hiddenWindows {
            if wnd.icon.wnd.hwnd = hwnd {
                switch lp {
                    case WM_RBUTTONUP: ShowMenu(i)
                    case WM_LBUTTONDOWN: this.hiddenWindows.RemoveAt(i)
                }
                break
            }
        }
        ShowMenu(idx) {
            title := this.hiddenWindows[idx].title
            b := StrLen(title) > maxLenMenuStr
            menuText := 'Restore «' . SubStr(title, 1, maxLenMenuStr) . (b ? '...' : '') . '»'
            iconMenu := Menu()
            iconMenu.Add(menuText, (*) => this.AHK_NOTIFYICON(iconID, WM_LBUTTONDOWN, iconMsg, hwnd))
            iconMenu.SetIcon(menuText, 'HICON:*' . this.hiddenWindows[idx].icon.hIcon)
            iconMenu.Add('Restore all windows', (*) => this.RestoreAll())
            iconMenu.Show()
            iconMenu.Delete()
        }
    }

    __Delete() {
        Loop 2 {
            ObjAddRef(ObjPtr(this))
        }
        this.DeleteProp('minHook')
        this.DeleteProp('destroyHook')
        OnMessage(0x404, this.OnNotify, 0)
        this.RestoreAll()
    }

    class HiddenWindow {
        __New(hWnd) {
            this.title := WinGetTitle(hWnd)
            this.icon := TrayIcon(this.GetWindowIcon(hWnd), this.title)
            WinMinimize(hWnd), WinHide(hWnd)
            this.hwnd := hWnd
        }

        __Delete() {
            try WinExist(this.hwnd) && (WinShow(), WinRestore(), WinActivate())
        }

        GetWindowIcon(hWnd) {
            static WM_GETICON := 0x007F, ICON_SMALL := 0, GCLP_HICONSM := -34
                 , GetClassLong := 'GetClassLong' . (A_PtrSize = 4 ? '' : 'Ptr')
            ((hIcon := SendMessage(WM_GETICON, ICON_SMALL, A_ScreenDPI, , hWnd))
            || (hIcon := DllCall(GetClassLong, 'Ptr', hWnd, 'Int', GCLP_HICONSM, 'Ptr'))
            || (hIcon := LoadPicture('Shell32', 'Icon3', &IMAGE_ICON := 1)))
            return hIcon
        }
    }
}

class TrayIcon
{
    NIF_MESSAGE := 1, NIF_ICON := 2, NIF_TIP := 4
    NIM_ADD := 0, NIM_DELETE := 2
    iconID := 0x404, iconMsg := 0x404

    __New(hIcon, tip?) {
        flags := this.NIF_MESSAGE | this.NIF_ICON | (IsSet(tip) ? this.NIF_TIP : 0)
        this.wnd := Gui(), this.hIcon := hIcon
        this.NOTIFYICONDATA := Buffer(396, 0)
        NumPut(
            'Ptr' , this.NOTIFYICONDATA.size,
            'Ptr' , this.wnd.hwnd,
            'UInt', this.iconID,
            'UInt', flags,
            'Ptr' , this.iconMsg,
            'Ptr' , hIcon, this.NOTIFYICONDATA
        )
        if IsSet(tip) {
            StrPut(tip, this.NOTIFYICONDATA.ptr + 4 * A_PtrSize + 8, 'CP0')
        }
        DllCall('Shell32\Shell_NotifyIcon', 'UInt', this.NIM_ADD, 'Ptr', this.NOTIFYICONDATA)
    }

    __Delete() {
        DllCall('Shell32\Shell_NotifyIcon', 'UInt', this.NIM_DELETE, 'Ptr', this.NOTIFYICONDATA)
        this.wnd.Destroy()
    }
}

class WinEventHook
{
    ; Event Constants: https://is.gd/tRT5Wr
    __New(eventMin, eventMax, hookProc, options := '', idProcess := 0, idThread := 0, dwFlags := 0) {
        this.callback := CallbackCreate(hookProc, options, 7)
        this.hHook := DllCall('SetWinEventHook', 'UInt', eventMin, 'UInt', eventMax, 'Ptr', 0, 'Ptr', this.callback
                                               , 'UInt', idProcess, 'UInt', idThread, 'UInt', dwFlags, 'Ptr')
    }
    __Delete() {
        DllCall('UnhookWinEvent', 'Ptr', this.hHook)
        CallbackFree(this.callback)
    }
}

class SingletonDecorator
{
    static ApplyTo(targetClass) {
        proto    := targetClass.Prototype
        origCall := targetClass.GetMethod('Call')
        origNew  := proto.HasMethod('__New')    ? proto.GetMethod('__New')    : ''
        origDel  := proto.HasMethod('__Delete') ? proto.GetMethod('__Delete') : ''
        
        targetClass.DefineProp('Call', {Call: (cls, p*) => cls.HasProp('singleton') ? cls.singleton : origCall(cls, p*)})
        proto.DefineProp('__New', {Call: (inst, p*) => (
            (origNew && origNew(inst, p*)),
            targetClass.singleton := inst,
            ObjRelease(ObjPtr(inst))
        )})
        proto.DefineProp('__Delete', {Call: inst => (
            ObjAddRef(ObjPtr(inst)),
            (origDel && origDel(inst)),
            targetClass.DeleteProp('singleton')
        )})
    } 
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

5 (изменено: leex, 2026-04-10 11:36:39)

Re: AHK v2: Можно ли кнопкой X сворачивать окно в трей, а не kill процесс?

Большое спасибо за пример, очень помог разобраться.

В итоге взял у вас идею с перехватом клика по трею через OnMessage(0x404) и логику работы с WinExist + IsWindowVisible + ShowWindow. На этой основе собрал свой вариант.

Сейчас всё работает стабильно:

ЛКМ по иконке в трее => сворачивает/разворачивает окно.
Alt+F4 => скрывает окно вместо закрытия и корректно возвращается в фокус через SetForegroundWindow.

Вопрос с иконкой решил так:
Получаю hwnd окна Snagit, дальше через GetWindowThreadProcessId + QueryFullProcessImageName вытягиваю путь к SnagitEditor.exe и уже его передаю в TraySetIcon(). В итоге в трее отображается родная иконка программы, а не стандартная AHK.

В целом всё получилось. Поведение теперь как у нормального tray-приложения.


Ещё раз спасибо за ваши советы и идеи из предоставленного примера — очень помогло решить эту задачу.


#Requires AutoHotkey v2
#SingleInstance Force

SnagitWin := "ahk_class SnagIt9Editor"
DetectHiddenWindows true

ListLines(false)
A_AllowMainWindow := false

hwnd := WinExist(SnagitWin)
if hwnd {
    path := GetWindowProcessPath(hwnd)
    if path != ""
        TraySetIcon(path)
}

GetWindowProcessPath(hwnd) {
    DllCall("GetWindowThreadProcessId", "ptr", hwnd, "uint*", &pid := 0)
    hProcess := DllCall("OpenProcess", "uint", 0x1000, "int", 0, "uint", pid)
    if !hProcess
        return ""

    buf := Buffer(260 * 2)
    size := 260
    ok := DllCall("QueryFullProcessImageName", "ptr", hProcess, "uint", 0, "ptr", buf, "uint*", &size)
    DllCall("CloseHandle", "ptr", hProcess)

    return ok ? StrGet(buf, size) : ""
}

OnMessage(0x404, TrayClick)

TrayClick(wParam, lParam, msg, hwnd) {
    if (lParam = 0x201) {
        hwnd := WinExist(SnagitWin)
        if hwnd {
            visible := DllCall("IsWindowVisible", "ptr", hwnd)
            DllCall("ShowWindow", "ptr", hwnd, "int", visible ? 0 : 9)
            if !visible
                DllCall("SetForegroundWindow", "ptr", hwnd)
        }
        return 0
    }
}

!F4:: {
    hwnd := WinExist(SnagitWin)
    if WinActive(SnagitWin) && hwnd
        DllCall("ShowWindow", "ptr", hwnd, "int", 0)
}

6

Re: AHK v2: Можно ли кнопкой X сворачивать окно в трей, а не kill процесс?

WinGetProcessPath

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