#NoEnv
SetBatchLines, -1
Menu, Tray, NoStandard
DetectHiddenWindows, On
scriptDirectory := "D:\AHK\My Scripts"
recurse := true
enumerateEXE := true
enumerateLNK := true
Scripts := EnumScriptsInDirectory(scriptDirectory, recurse, enumerateEXE, enumerateLNK)
OnMessage( 0x404, Func("AHK_NOTIFYICON").Bind(Scripts, scriptDirectory) )
Return
EnumScriptsInDirectory(scriptDirectory, recurse, exe, lnk) {
if !InStr( FileExist(scriptDirectory), "D" ) {
MsgBox, Folder "%scriptDirectory%" not found
ExitApp
}
Scripts := [], Names := {}
Loop, Files, % scriptDirectory . "\*.*", % (recurse ? "R" : "")
{
if (A_LoopFileExt ~= "i)ahk" . (exe ? "|exe" : "") . (lnk ? "|lnk" : "")) {
if (A_LoopFileExt = "lnk") {
FileGetShortcut, % A_LoopFilePath, scriptPath
if !(scriptPath ~= "i)(ahk" . (exe ? "|exe" : "") . ")$")
continue
SplitPath, scriptPath, name
}
else {
name := A_LoopFileName
scriptPath := A_LoopFilePath
}
if Names.HasKey(name) {
if (scriptPath = Names[name].scriptPath)
continue
i := 0
Loop
newName := name . " (" . ++i . ")"
until !Names.HasKey(newName)
name := newName
}
Names[name] := {name: name, scriptPath: scriptPath}
}
}
for k, v in Names
Scripts.Push(v)
Return Scripts
}
AHK_NOTIFYICON(Scripts, scriptDirectory, wp, lp) {
static WM_MOUSEMOVE := 0x200, WM_RBUTTONUP := 0x205, info
, IsData := [], timer := Func("IsDataClear").Bind(IsData)
if (lp = WM_MOUSEMOVE) {
if !IsData[1] {
Critical
Loop 3 {
try info := GetAhkScriptsInfo()
catch {
Sleep, 100
continue
}
break
}
IsData[1] := true
SetTimer, % timer, -1000
Critical Off
}
}
if (lp = WM_RBUTTONUP) {
handler := Func("RunFile").Bind("taskmgr")
Menu, MyMenu, Add, Running Scripts, % handler
Menu, MyMenu, Default, Running Scripts
Menu, MyMenu, Add
Names := {}
for k, v in info {
if Names.HasKey(v.name) {
i := 0
Loop
name := v.name . " (" . ++i . ")"
until !Names.HasKey(name)
v.name := name
}
Names[v.name] := ""
}
for name in Names {
for k, v in info {
if (v.name = name) {
CreateActionsSubMenu(k, v)
item := v.name
if v.suspended
item .= A_Tab . "Suspended"
else if v.paused
item .= A_Tab . "Paused"
Menu, MyMenu, Add, % item, :Actions%k%
break
}
}
}
Menu, MyMenu, Add
CreateScriptsSubmenu(Scripts, scriptDirectory, info)
Menu, MyMenu, Add, My Scripts, :Scripts
Menu, MyMenu, Add
Menu, MyMenu, Add, Close this Script, ExitScript
Menu, MyMenu, Show
Loop % info.MaxIndex()
Menu, Actions%A_Index%, DeleteAll
Loop % Scripts.MaxIndex()
Menu, ScriptFile%A_Index%, DeleteAll
Menu, Scripts, DeleteAll
Menu, MyMenu, DeleteAll
IsData[1] := false
}
}
IsDataClear(IsData) {
IsData[1] := false
}
GetAhkScriptsInfo() {
static Waiting := 5, Suspended := 5
, MyPID := DllCall("GetCurrentProcessId")
info := [], PIDs := {}
WinGet, List, List, ahk_class AutoHotkey
Loop % List {
WinExist("ahk_id" List%A_Index%)
threadID := DllCall("GetWindowThreadProcessId", "Ptr", List%A_Index%, "UIntP", PID)
if (PID = MyPID)
continue
PIDs[PID] := threadID
WinGetTitle, title
scriptPath := RegExReplace(title, " - AutoHotkey v[\d\.]+")
SplitPath, scriptPath, fileName
info.Push({ scriptPath: scriptPath, name: fileName, PID: PID
, hwnd: List%A_Index%, suspended: false, bitness: GetProcessBitness(PID) })
}
ThreadsInfo(PIDs)
for k, v in info {
v.threads := PIDs[ v.PID ]
for k, j in PIDs[ v.PID ] {
if !(j.ThreadState = Waiting && j.WaitReason = Suspended)
continue 2
v.suspended := true
}
}
for k, v in info
if !v.suspended
v.paused := IsPaused(v.hwnd)
Return info
}
GetProcessBitness(PID) {
if !A_Is64bitOS
Return 32
hProc := DllCall("OpenProcess", "UInt", PROCESS_QUERY_INFORMATION := 0x400, "UInt", 0, "UInt", PID, "Ptr")
DllCall("IsWow64Process", "Ptr", hProc, "IntP", is32)
DllCall("CloseHandle", "Ptr", hProc)
Return 32 << !is32
}
ThreadsInfo(PIDs) {
static SPI := SystemProcessInformation := 0x5, STATUS_SUCCESS := 0
, szSPI := A_PtrSize = 4 ? 0xB8 : 0x100 ; size of SYSTEM_PROCESS_INFORMATION
, szSTI := A_PtrSize = 4 ? 0x40 : 0x050 ; size of SYSTEM_THREAD_INFORMATION
DllCall("Ntdll\NtQuerySystemInformation", "Int", SPI, "Ptr", 0, "UInt", 0, "UIntP", size, "UInt")
VarSetCapacity(buff, size, 0)
addr := &buff
res := DllCall("Ntdll\NtQuerySystemInformation", "Int", SPI, "Ptr", addr, "UInt", size, "UIntP", 0, "UInt")
if (res != STATUS_SUCCESS)
throw Exception( "Could not get data from NtQuerySystemInformation. Error: " . Format("{:#x}", res) )
offset := 0
Loop {
addr += offset
ProcessID := NumGet(addr + 4*14 + A_PtrSize*3)
if !PIDs.HasKey(ProcessID)
continue
threadsCount := NumGet(addr + 4, "UInt")
mainThreadID := PIDs[ProcessID]
PIDs[ProcessID] := []
Loop % threadsCount {
ThreadID := NumGet(addr + szSPI + 24 + A_PtrSize*3 + (A_Index - 1)*szSTI)
if (ThreadID != mainThreadID)
continue
ThreadState := NumGet(addr + szSPI + 36 + A_PtrSize*4 + (A_Index - 1)*szSTI, "UInt")
WaitReason := NumGet(addr + szSPI + 40 + A_PtrSize*4 + (A_Index - 1)*szSTI, "UInt")
PIDs[ProcessID].Push( {ThreadID: ThreadID, ThreadState: ThreadState, WaitReason: WaitReason} )
}
} until !offset := NumGet(addr + 0, "UInt")
}
CreateScriptsSubmenu(Scripts, folder, info) {
handler := Func("RunFile").Bind(folder)
Menu, Scripts, Add, Files, % handler
Menu, Scripts, Default, Files
Menu, Scripts, Add
for k, v in Scripts {
isRunning := false
for i, j in info {
if (j.scriptPath = v.scriptPath && isRunning := true)
break
}
CreateScriptFileSubmenu(v.scriptPath, isRunning, k)
Menu, Scripts, Add, % v.name, :ScriptFile%k%
if isRunning
Menu, Scripts, Check, % v.name
}
}
CreateScriptFileSubmenu(scriptPath, isRunning, k) {
if !isRunning {
handler := Func("RunScript").Bind(scriptPath, k + 2)
Menu, ScriptFile%k%, Add, Run, % handler
}
if !(scriptPath ~= "i)\.exe$") {
handler := Func("RunFileContextMenuItem").Bind(scriptPath, "Edit Script")
Menu, ScriptFile%k%, Add, Edit Script, % handler
}
handler := Func("AddToStartup").Bind(scriptPath, k)
Menu, ScriptFile%k%, Add, Add to Startup, % handler
SplitPath, scriptPath, fileName
if FileExist(A_Startup . "\" . fileName . ".lnk")
Menu, ScriptFile%k%, Check, Add to Startup
handler := Func("OpenScriptFolder").Bind(scriptPath)
Menu, ScriptFile%k%, Add, Open File Location, % handler
}
RunFileContextMenuItem(filePath, contextMenuItemName) {
SplitPath, filePath, fileName, dir
try Folder := ComObjCreate("Shell.Application").Namespace(dir)
catch
Return
for Item in Folder.Items {
if (Item.Name = fileName) {
Loop % Item.Verbs.Count {
Verb := Item.Verbs.Item(A_Index - 1)
if (Verb.Name = contextMenuItemName) {
Verb.DoIt()
break 2
}
}
}
}
}
RunFile(filePath) {
Run, % filePath
}
RunScript(scriptPath, ItemPos) {
hMenu := MenuGetHandle("Scripts")
if !IsMenuChecked(hMenu, ItemPos)
Run, % scriptPath
}
IsMenuChecked(hMenu, itemNumber) {
static MIIM_STATE := 1, MFS_CHECKED := 0x8
VarSetCapacity(MENUITEMINFO, size := 4*4 + A_PtrSize*8, 0)
NumPut(size, MENUITEMINFO)
NumPut(MIIM_STATE, MENUITEMINFO, 4)
DllCall("GetMenuItemInfo", Ptr, hMenu, UInt, itemNumber - 1, UInt, true, Ptr, &MENUITEMINFO)
Return !!(NumGet(MENUITEMINFO, 4*3, "UInt") & MFS_CHECKED)
}
CreateActionsSubMenu(k, info) {
static MyBitness := GetProcessBitness( DllCall("GetCurrentProcessId") )
if !(MyBitness = 32 && info.bitness = 64) && !(info.name ~= "i)\.exe( \(\d+\))?$") {
handler := Func("SuspendResumeThreads").Bind(info)
Menu, Actions%k%, Add, Suspend Script, % handler
}
if info.suspended
Menu, Actions%k%, Check, Suspend Script
else {
handler := Func("SelectMenuItem").Bind(info.hwnd, 5)
Menu, Actions%k%, Add, Pause Script, % handler
if info.paused
Menu, Actions%k%, Check, Pause Script
}
Menu, Actions%k%, Add
if !(info.name ~= "i)\.exe$") {
handler := Func("RunFileContextMenuItem").Bind(info.scriptPath, "Edit Script")
Menu, Actions%k%, Add, Edit Script, % handler
}
handler := Func("AddToStartup").Bind(info.scriptPath, k)
Menu, Actions%k%, Add, Add to Startup, % handler
if FileExist(A_Startup . "\" . info.name . ".lnk")
Menu, Actions%k%, Check, Add to Startup
handler := Func("OpenScriptFolder").Bind(info.scriptPath)
Menu, Actions%k%, Add, Open File Location, % handler
Menu, Actions%k%, Add
handler := Func("KillAndReload").Bind(info.PID, info.scriptPath)
Menu, Actions%k%, Add, Kill And Reload, % handler
handler := Func("KillScript").Bind(info.PID)
Menu, Actions%k%, Add, Kill Script, % handler
if !info.suspended {
handler := Func("SelectMenuItem").Bind(info.hwnd, 1)
Menu, Actions%k%, Add, Reload, % handler
handler := Func("SelectMenuItem").Bind(info.hwnd, 8)
Menu, Actions%k%, Add, Exit, % handler
}
}
OpenScriptFolder(filePath) {
SplitPath, filePath,, dir
for window in ComObjCreate("Shell.Application").Windows {
try ShellFolderView := window.Document
try if ( (Folder := ShellFolderView.Folder).Self.Path = dir ) {
for item in Folder.Items {
if (item.Path = filePath && found := true)
break 2
}
}
}
if !found
Run, explorer /select`, "%filePath%"
else {
WinActivate, % "ahk_id" window.hwnd
ShellFolderView.SelectItem(item, 1|4|8|16)
}
}
SelectMenuItem(hwnd, nMenu) {
WinMenuSelectItem, ahk_id %hWnd%,, 1&, % nMenu . "&"
}
AddToStartup(scriptPath) {
SplitPath, scriptPath, fileName
lnk := A_Startup . "\" . fileName . ".lnk"
if FileExist(lnk)
FileDelete, % lnk
else
FileCreateShortcut, % scriptPath, % lnk
}
SuspendResumeThreads(info) {
static MyBitness := GetProcessBitness( DllCall("GetCurrentProcessId") )
, THREAD_SUSPEND_RESUME := 0x2
for k, v in info.threads {
hThread := DllCall("OpenThread", "UInt", THREAD_SUSPEND_RESUME, "UInt", false, "UInt", v.ThreadID, "Ptr")
if info.suspended
DllCall("ResumeThread", "Ptr", hThread)
else {
if (MyBitness = info.bitness)
DllCall("SuspendThread", "Ptr", hThread)
else
DllCall("Wow64SuspendThread", "Ptr", hThread)
}
DllCall("CloseHandle", "Ptr", hThread)
}
}
KillScript(PID) {
Process, Close, % PID
Process, WaitClose, % PID
DeleteDummyIcons()
}
KillAndReload(PID, scriptPath) {
KillScript(PID)
Run, % scriptPath
}
IsPaused(hWnd) {
static WM_ENTERMENULOOP := 0x211, WM_EXITMENULOOP := 0x212, MF_BYPOSITION := 0x400, MF_CHECKED := 8
, Params := ["Ptr", 0, "Ptr", 0, "UInt", SMTO_ABORTIFHUNG := 0x2|SMTO_ERRORONEXIT := 0x20, "UInt", 500, "Ptr", 0]
Loop 2
if !DllCall("SendMessageTimeout", Ptr, hWnd, UInt, A_Index = 1 ? WM_ENTERMENULOOP : WM_EXITMENULOOP, Params*)
Return
hWinMenu := DllCall("GetMenu", Ptr, hWnd, Ptr)
hFileMenu := DllCall("GetSubMenu", Ptr, hWinMenu, Int, 0, Ptr)
Return DllCall("GetMenuState", Ptr, hFileMenu, UInt, 4, UInt, MF_BYPOSITION) = MF_CHECKED ; Pause state
}
ExitScript() {
ExitApp
}
DeleteDummyIcons()
{
static TB_GETBUTTON := 0x417
, TB_BUTTONCOUNT := 0x418
, PtrSize := 4 << A_Is64bitOS
, szTBBUTTON := 8 + PtrSize*3
, szTRAYDATA := 16 + PtrSize*2
DHW_Prev := A_DetectHiddenWindows
DetectHiddenWindows, On
WinGet, PID, PID, ahk_exe explorer.exe
RemoteBuff := new RemoteBuffer(PID, szTRAYDATA)
Dummy := []
Loop 2 {
if (A_Index = 2)
ControlGet, hToolBar, hwnd,, ToolbarWindow321, ahk_class NotifyIconOverflowWindow
else {
for k, v in ["TrayNotifyWnd", "SysPager", "ToolbarWindow32"]
hToolBar := DllCall("FindWindowEx", "Ptr", k = 1 ? WinExist("ahk_class Shell_TrayWnd") : hToolBar, "Ptr", 0, "Str", v, "UInt", 0, "Ptr")
}
SendMessage, TB_BUTTONCOUNT,,,, % "ahk_id" . hToolBar
Loop % ErrorLevel {
SendMessage, TB_GETBUTTON, A_Index - 1, RemoteBuff.ptr,, % "ahk_id" . hToolBar
RemoteBuff.Read(TBBUTTON, szTBBUTTON)
RemoteBuff.Read(TRAYDATA, szTRAYDATA, NumGet(&TBBUTTON + 8 + PtrSize) - RemoteBuff.ptr)
hWndIcon := NumGet(TRAYDATA), uID := NumGet(&TRAYDATA + PtrSize, "UInt")
if !WinExist("ahk_id" hWndIcon)
Dummy.Insert({hWnd: hWndIcon, uID: uID})
}
}
DetectHiddenWindows, %DHW_Prev%
for k, v in Dummy
RemoveTrayIcon(v.hWnd, v.uID)
}
RemoveTrayIcon(hWnd, uID)
{
VarSetCapacity(NOTIFYICONDATA, size := A_PtrSize = 8 ? 848 : A_IsUnicode? 828 : 444, 0)
NumPut(size, NOTIFYICONDATA, "UInt")
NumPut(hWnd, NOTIFYICONDATA, A_PtrSize)
NumPut(uID, NOTIFYICONDATA, 2*A_PtrSize, "UInt")
DllCall("shell32\Shell_NotifyIcon", "UInt", NIM_DELETE := 2, Ptr, &NOTIFYICONDATA)
Return
}
class RemoteBuffer
{
__New(PID, size) {
static flags := (PROCESS_VM_OPERATION := 0x8) | (PROCESS_VM_WRITE := 0x20) | (PROCESS_VM_READ := 0x10)
, Params := ["UInt", MEM_COMMIT := 0x1000, "UInt", PAGE_READWRITE := 0x4, "Ptr"]
if !this.hProc := DllCall("OpenProcess", "UInt", flags, "Int", 0, "UInt", PID, "Ptr")
throw Exception("Can't open remote process PID = " . PID . "`nA_LastError: " . A_LastError, "RemoteBuffer.__New")
if !this.ptr := DllCall("VirtualAllocEx", "Ptr", this.hProc, "Ptr", 0, "Ptr", size, Params*) {
DllCall("CloseHandle", "Ptr", this.hProc)
throw Exception("Can't allocate memory in remote process PID = " . PID . "`nA_LastError: " . A_LastError, "RemoteBuffer.__New")
}
}
__Delete() {
DllCall("VirtualFreeEx", "Ptr", this.hProc, "Ptr", this.ptr, "UInt", 0, "UInt", MEM_RELEASE := 0x8000)
DllCall("CloseHandle", "Ptr", this.hProc)
}
Read(ByRef localBuff, size, offset = 0) {
VarSetCapacity(localBuff, size, 0)
if !DllCall("ReadProcessMemory", "Ptr", this.hProc, "Ptr", this.ptr + offset, "Ptr", &localBuff, "Ptr", size, "PtrP", bytesRead)
throw Exception("Can't read data from remote buffer`nA_LastError: " . A_LastError, "RemoteBuffer.Read")
Return bytesRead
}
Write(pData, size, offset = 0) {
if !res := DllCall("WriteProcessMemory", "Ptr", this.hProc, "Ptr", this.ptr + offset, "Ptr", pData, "Ptr", size, "PtrP", bytesWritten)
throw Exception("Can't write data to remote buffer`nA_LastError: " . A_LastError, "RemoteBuffer.Write")
Return bytesWritten
}
}
Папку, из которой будут показаны скрипты в подменю "My Scripts" нужно прописать в начале в переменной scriptDirectory.