1 (изменено: Drugoy, 2013-10-20 20:33:48)

Тема: AHK: MasterScript (менеджер .ahk скриптов)

MasterScript

Я использую много мелких ahk скриптов. Каждый из запущенных скриптов добавляет в трэй одинаковую иконку с зелёной [Н], так что различить их можно только наводя на них курсором и читая текст во всплывающих подсказках.
Поэтому я написал простенький скрипт для управления файлами и процессами ahk-скриптов.

Вот как это выглядит:
Окно скрипта состоит из двух вкладок: 1 для управления файлами скриптов (помимо навигации среди папок - также приделана функция сохранения выбранных скриптов в закладках, чтобы потому можно было их быстро вызвать)
https://i.imgur.com/oFFFJu0.png
а в другой вкладке можно управлять процессами запущенных скриптов:
https://i.imgur.com/UCg74vz.png

Вот, что сейчас умеет скрипт:
- выполнять команды, которые расписаны на кнопках
- добавлять в закладки [и сохранять эту информацию в .ini] скрипты (чтобы в дальнейшем можно было [пере]запустить их все разом)
- растягиваемое GUI
- навигация по папкам (в поисках скриптов) с помощью TreeView (древовидное отображение)
- скрипт также поддерживает некоторые настройки, например умеет запоминать позицию собственного окна, чтобы при следующем вызове восстановиться в том же месте и в том же размере

Скрипт, возможно, также будет полезен новичкам, как хороший пример работы ListView, TreeView, GUI в общем (оно полностью растягиваемое, имеет кнопочки, статус-бар и даже вкладки!), сбор/фильтрация списка процессов, таймеры, gLabel'ы, и т.д.

Вероятно, ещё много стоит дорабатывать и у меня были планы на дальнейшее развитие скрипта, но сначала хотелось бы оценить насколько будет (и будет ли вообще) востребован этот скрипт.
Я буду рад если вы попробуете скрипт и оставите свои отзывы.  Также, я буду рад не только патчам к скрипту, но и всем баг репортам, разумным фичереквестам и просто конструктивной критике.

+ Список изменений:

v2.1:
[+] Переделал дерево в "Умное Дерево": теперь оно способно показывать большие структуры и достаточно быстро, за счёт того, что достраивание дерева происходит каждый раз, когда пользователь раскрывает какую-то его ветвь.
[x] Теперь навигация происходит по папкам не от "startFolder", а от корней всех дисков (поддерживаются диски только FIXED и REMOVABLE типов).
[x] У разных типов дисков - разные иконки.

v2:
[+] Добавил новую фичу "Process Assistants" (подробнее о ней - ниже).
[x] Переписал "сердце" скрипта: отслеживание обычных процессов и процессов скриптов.
[x] Добавил кучу проверок (код стал безопасней).
[x] Переписал и добавил кучу комментариев в код (каждая функция теперь превосходно описана)
[x] Переписал некоторые функции: упростил их вызовы и укоротил их имена, имена переменных и имена аргументов.
[-] Известный баг: заметил, что при каждом закрытии и повторном открытии окна (в случае, если пользователем выбрано запоминать позицию и размер окна, что есть значение по умолчанию) - размер окна чуточку увеличивается.
[-] Заметил, что функция NoTrayOrphans(), которая чистит трей от иконок мёртвых процессов - не работает под x64 версией AutoHotkey. Не я писал эту функцию, а её автор пока не выходит на связь. Буду рад, если кто-то добавит функции совместимость с x64 версией AutoHotkey.

+ О новой добавленной фиче 'Process Assistants':

https://i.imgur.com/SpXjm4F.png

'Process Assistant' работает таким образом: вы создаёте для неё правила, в которых вы связываете определённые процессы с указанными скриптами и как только скрипт замечает, что указанный процесс запущен - он запускает и "ассистирующие" ему скрипты. Как только тот процесс перестаёт существовать - то закрываются и ассистирующие скрипты.
В коде скрипта в секции "Settings" можно задать вид того, как будут закрываться скрипты: нежно (т.е. с выполнением у них OnExit) или же жёстко (всё равно что прибить процесс диспетчером задач).

Существуют следующие правила создания правил для 'Proces Assistant':
1. Каждое правило должно состоять из 2-ух частей, разделённых символом ">":
  a. в левой части правила указываются "Trigger Conditions" (TCs), т.е. условия при которых будут запущены соответствующие скрипты. В 1 правиле могут быть указаны сразу несколько TCs: для этого их надо разделить символом "|" и в таком случае правило будет срабатывать ЕСЛИ ХОТЯ БЫ ОДИН из указанных процессов запущен. Процессы можно описывать по имени их исполняемого файла (explorer.exe) или же по полному или частичному (без "\" в начале) пути к нему.
  b. в правой части правила указываются "Triggered Actions" (TAs). Их тоже можно указать сразу несколько в одном правиле и тогда, когда правило будет срабатывать - будут запускаться или закрываться сразу все указанные в правой части правила скрипты. Скрипты указываются полными путями к их .ahk файлу (без указания пути к autohotkey.exe).
2. В одно правило может содержать несколько TCs и/или TAs: просто разделяйте их символом "|".

Пара примеров правильно составленных правил:
firefox.exe|Program Files\GoogleChrome\chrome.exe|C:\Program Files\Internet Explorer\iexplore.exe>C:\Program Files\AHK-Scripts\browserHelper.ahk

notepad.exe>C:\Program Files\AHK-Scripts\pimpMyPad.ahk

C:\Games\DOTA\dota.exe>C:\DOTA Scripts\cooldownSoundNotify.ahk|C:\DOTA Scripts\cheats\showInvisibleEnemies.ahk

Текущий адрес скрипта такой:
https://github.com/Drugoy/Autohotkey-sc … Script.ahk
Там же находится и issue tracker.

+ код скрипта
/* MasterScript.ahk
Version: 2.1
Last time modified: 20:13 20.10.2013

Description: a script manager for *.ahk scripts.

Script author: Drugoy a.k.a. Drugmix
Contacts: idrugoy@gmail.com, drug0y@ya.ru
https://github.com/Drugoy/Autohotkey-scripts-.ahk/tree/master/ScriptManager.ahk/MasterScript.ahk
http://auto-hotkey.com/boards/viewtopic.php?f=6&t=109&p=1612
http://forum.script-coding.com/viewtopic.php?id=8724
*/

;{ TODO:
; 1. Functions to control scripts:
;     a. Hide/restore scripts' tray icons.
; 2. Improve TreeView.
; 3. [If possible:] Combine suspendProcess() and resumeProcess() into a single function.
;    This might be helpful: http://www.autohotkey.com/board/topic/41725-how-do-i-disable-a-script-from-a-different-script/#entry287262
; 4. [If possible:] Add more info about processes to ProcessList: hotkey suspend state, script's pause state.
; 5. Handle scripts' icons hiding/restoring.
;}

;{ Settings block.
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Recommended for catching common errors.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance, force
DetectHiddenWindows, On    ; Needed for "pause" and "suspend" commands.
memoryScanInterval := 1000    ; Specify a value in milliseconds.
GroupAdd ScriptHwnd_A, % "ahk_pid " DllCall("GetCurrentProcessId")
SplitPath, A_AhkPath,, startFolder
rememberPosAndSize := 1    ; 1 = Make script's window remember it's position and size between window's closures. 0 = always open 800x600 on the center of the screen.
storePosAndSize := 1    ; 1 = Make script store info (into the "Settings.ini" file) about it's window's size and position between script's closures. 0 = do not store that info in the Settings.ini.
howToQuitAssistants := "exit"    ; Specify either "exit" or "kill" as the values for this variable. This will tell the script how to close the scripts-assistants if the their triggering process(es) were closed: "exit" closes the scripts gently (the "OnExit" subroutine will work out, it's the same as you manually select "Exit" from the script's tray icon's context menu) and "kill" kills them brutally (the "OnExit" subroutine probably won't get executed, it's the same as killing the process from the Task Manager).
;}

;{ GUI Creation.
    ;{ Create folder icons.
ImageListID := IL_Create(3)    ; Create an ImageList to hold 1 icon.
    IL_Add(ImageListID, "shell32.dll", 4)    ; 'Folder' icon.
    IL_Add(ImageListID, "shell32.dll", 80)    ; 'Logical disk' icon.
    IL_Add(ImageListID, "shell32.dll", 27)    ; 'Removable disk' icon.
;    IL_Add(ImageListID, "shell32.dll", 13)    ; 'Process' icon.
;    IL_Add(ImageListID, "shell32.dll", 44)    ; 'Bookmark' icon.
;    IL_Add(ImageListID, "shell32.dll", 46)    ; 'Up to the root folder' icon.
;    IL_Add(ImageListID, "shell32.dll", 71)    ; 'Script' icon.
;    IL_Add(ImageListID, "shell32.dll", 138)    ; 'Run' icon.
;    IL_Add(ImageListID, "shell32.dll", 272)    ; 'Delete' icon.
;    IL_Add(ImageListID, "shell32.dll", 285)    ; Neat 'Script' icon.
;    IL_Add(ImageListID, "shell32.dll", 286)    ; Neat 'Folder' icon.
;    IL_Add(ImageListID, "shell32.dll", 288)    ; Neat 'Bookmark' icon.
;    IL_Add(ImageListID, "shell32.dll", 298)    ; 'Folders tree' icon.
    ;}
    ;{ Tray menu.
Menu, Tray, NoStandard
Menu, Tray, Add, Manage Scripts, GuiShow    ; Create a tray menu's menuitem and bind it to a label that opens main window.
Menu, Tray, Default, Manage Scripts
Menu, Tray, Add
Menu, Tray, Standard
    ;}
    ;{ Add tabs and their contents.
Gui, Add, Tab2, AltSubmit x0 y0 w568 h46 Choose1 +Theme -Background gTabSwitch vactiveTab, Manage files|Manage processes|Manage process assistants    ; AltSubmit here is needed to make variable 'activeTab' get active tab's number, not name.
        ;{ Tab #1: 'Manage files'.
Gui, Tab, Manage files
Gui, Add, Text, x26 y26, Choose a folder:
Gui, Add, Button, x301 y21 gRunSelected, Run selected
Gui, Add, Button, x+0 gBookmarkSelected, Bookmark selected
Gui, Add, Button, x+0 gDeleteSelected, Delete selected
; Folder list (left pane).
Gui, Add, TreeView, AltSubmit x0 y+0 +Resize gFolderTree vFolderTree HwndFolderTreeHwnd ImageList%ImageListID%    ; Add TreeView for navigation in the FileSystem.    ; ICON

; File list (right pane).
Gui, Add, ListView, AltSubmit x+0 +Resize +Grid gFileList vFileList HwndFileListHwnd, Name|Size (bytes)|Created|Modified
; Set the static widths for some of it's columns
LV_ModifyCol(2, 76)
LV_ModifyCol(3, 117)
LV_ModifyCol(4, 117)

Gui, Add, Text, vtextBS, Bookmarked scripts:

; Bookmarks (bottom pane).
Gui, Add, ListView, AltSubmit +Resize +Grid gBookmarksList vBookmarksList, #|Name|Full Path|Size|Created|Modified
; Set the static widths for some of it's columns
LV_ModifyCol(1, 20)
LV_ModifyCol(4, 76)
LV_ModifyCol(5, 117)
LV_ModifyCol(6, 117)
        ;}
        ;{ Tab #2: 'Manage processes'.
Gui, Tab, Manage processes
; Add buttons to trigger functions.
Gui, Add, Button, x2 y21 gExit, Exit
Gui, Add, Button, x+0 gKill, Kill
Gui, Add, Button, x+0 gkillNreexecute, Kill and re-execute
Gui, Add, Button, x+0 gReload, Reload
Gui, Add, Button, x+0 gPause, (Un) pause
Gui, Add, Button, x+0 gSuspendHotkeys, (Un) suspend hotkeys
Gui, Add, Button, x+0 gSuspendProcess, Suspend process
Gui, Add, Button, x+0 gResumeProcess, Resume process

; Add the main "ListView" element and define it's size, contents, and a label binding.
Gui, Add, ListView, x0 y+0 +Resize +Grid gManageProcesses vManageProcesses, #|PID|Name|Path
; Set the static widths for some of it's columns
LV_ModifyCol(1, 20)
LV_ModifyCol(2, 40)
        ;}
        ;{ Tab #3: 'Manage process assistants'.
Gui, Tab, Manage process assistants
Gui, Add, Button, x374 y21 gAddNewRule, Add new rule
Gui, Add, Button, x+0 gDeleteRules, Delete selected rule(s)
Gui, Add, ListView, x0 y+0 +Resize +Grid vAssistantsList, #|Trigger condition|Scripts to execute
LV_ModifyCol(1, 20)
        ;}
        ;{ StatusBar
Gui, Add, StatusBar
SB_SetParts(60, 85)
        ;}
        ;{ Startup labels executions.
Gui, Submit, NoHide
token := 1
GoSub, AssistantsList
GoSub, ProcessList
GoSub, ManageProcesses

            ;{ Initial construction of the TreeView.
; Blame:
; 1. Except for FIXED and REMOVABLE, there are other types of drives (CDROM, NETWORK, RAMDISK, UNKNOWN).
; 2. I should add 'bookmarked folders' feature.
; 3. The list of drives currently is static, but it should be dynamic.
DriveGet, fixedDrivesList, List, FIXED
DriveGet, removableDrivesList, List, REMOVABLE
Loop, Parse, fixedDrivesList    ; Add all fixed disks to the TreeView.
    buildTree(A_LoopField ":", TV_Add(A_LoopField ":",, "Icon2"))
If removableDrivesList
    Loop, Parse, removableDrivesList    ; Add all removable disks to the TreeView.
        buildTree(A_LoopField ":", TV_Add(A_LoopField ":",, "Icon3"))
            ;}
GoSub, FolderTree
GoSub, BookmarksList
SysGet, UA, MonitorWorkArea    ; Getting Usable Area info.
If storePosAndSize
{
    IniRead, sw_W, Settings.ini, Script's window, sizeW, 800
    IniRead, sw_H, Settings.ini, Script's window, sizeH, 600
    IniRead, sw_X, Settings.ini, Script's window, posX, % (UARight - sw_W) / 2
    IniRead, sw_Y, Settings.ini, Script's window, posY, % (UABottom - sw_H) / 2
}
Else
    sw_W := 800, sw_H := 600, sw_X := (UARight - sw_W) / 2, sw_Y := (UABottom - sw_H) / 2
SetTimer, ProcessList, %memoryScanInterval%
GoSub, GuiShow
; OnMessage(0x219, "WM_DEVICECHANGE")
OnExit, ExitApp
Return
        ;}
    ;}
;}

;{ GUI Actions
    ;{ G-Labels of main GUI.
GuiShow:
    Gui, +Resize +MinSize666x222
    ; Gui, Show, % "x" sw_X " y" sw_Y " w" sw_W - 16 " h" sw_H - 38, Manage Scripts    ; FIXME: sometimes this causes the "Error: Invalid option" upon script's execution + it will probably have window size bug on other platforms/theme, since I've tuned the values to fix it for Win7 with Aero.
    Gui, Show, % "x" sw_X " y" sw_Y " w" sw_W " h" sw_H , Manage Scripts
Return

GuiSize:    ; Expand or shrink the ListView in response to the user's resizing of the window.
    If !(A_EventInfo == 1)
    {    ; The window has been resized or maximized. Resize GUI items to match the window's size.
        workingAreaHeight := A_GuiHeight - 86
        If (activeTab == 1)
        {
            GuiControl, -Redraw, textBS
            GuiControl, -Redraw, FileList
            GuiControl, -Redraw, FolderTree
            GuiControl, -Redraw, BookmarksList
        }
        Else If (activeTab == 2)
            GuiControl, -Redraw, ManageProcesses
        Else If (activeTab == 3)
            GuiControl, -Redraw, AssistantsList
        GuiControl, Move, FolderTree, % " h" . (workingAreaHeight * 0.677)
        ControlGetPos,, FT_yCoord, FT_Width, FT_Height,, ahk_id %FolderTreeHwnd%
        GuiControl, Move, textBS, % "x0 y" . (FT_yCoord + FT_Height - 30)
        GuiControl, Move, FileList, % "w" . (A_GuiWidth - FT_Width + 30) . " h" . (workingAreaHeight * 0.677)
        Gui, ListView, FileList
        LV_ModifyCol(1, A_GuiWidth - (FT_Width + 330))
        GuiControl, Move, BookmarksList, % "x0 y" . (FT_yCoord + FT_Height - 17) . "w" . (A_GuiWidth + 1) . " h" . (8 + workingAreaHeight * 0.323)
        Gui, ListView, BookmarksList
        LV_ModifyCol(2, (A_GuiWidth - 349) * 0.35)
        LV_ModifyCol(3, (A_GuiWidth - 349) * 0.65)
        GuiControl, Move, ManageProcesses, % "w" . (A_GuiWidth + 1) . " h" . (workingAreaHeight + 20)
        Gui, ListView, ManageProcesses
        LV_ModifyCol(3, (A_GuiWidth - 79) * 0.3)
        LV_ModifyCol(4, (A_GuiWidth - 79) * 0.7)
        GuiControl, Move, AssistantsList, % "w" . (A_GuiWidth + 1) . " h" . (workingAreaHeight + 20)
        Gui, ListView, AssistantsList
        LV_ModifyCol(2, (A_GuiWidth - 39) * 0.6)
        LV_ModifyCol(3, (A_GuiWidth - 39) * 0.4)
        If (activeTab == 1)
        {
            GuiControl, +Redraw, textBS
            GuiControl, +Redraw, FileList
            GuiControl, +Redraw, FolderTree
            GuiControl, +Redraw, BookmarksList
        }
        Else If (activeTab == 2)
            GuiControl, +Redraw, ManageProcesses
        Else If (activeTab == 3)
            GuiControl, +Redraw, AssistantsList
    }
Return

GuiClose:
    If storePosAndSize || rememberPosAndSize
        WinGetPos, sw_X, sw_Y, sw_W, sw_H, Manage Scripts ahk_class AutoHotkeyGUI
    Gui, Hide
Return

TabSwitch:
    Gui, Submit, NoHide
    If (activeTab == 1)
    {
        GuiControl, +Redraw, textBS
        GuiControl, +Redraw, FileList
        GuiControl, +Redraw, FolderTree
        GuiControl, +Redraw, BookmarksList
    }
    Else If (activeTab == 2)
        GuiControl, +Redraw, ManageProcesses
    Else If (activeTab == 3)
        GuiControl, +Redraw, AssistantsList
Return

ExitApp:
    Gosub, GuiClose
    If storePosAndSize
    {
            IniWrite, %sw_X%, Settings.ini, Script's window, posX
            IniWrite, %sw_Y%, Settings.ini, Script's window, posY
            IniWrite, %sw_W%, Settings.ini, Script's window, sizeW
            IniWrite, %sw_H%, Settings.ini, Script's window, sizeH
    }
    ExitApp
    ;}
    ;{ Tab #1: gLabels of [Tree/List]Views.
        ;{ FolderTree
FolderTree:    ; TreeView's G-label that should update the "FolderTree" TreeView as well as trigger "FileList" ListView update.
    Global activeControl := A_ThisLabel
    If (A_GuiEvent == "") || (A_GuiEvent == "Normal") || (A_GuiEvent == "S") || (A_GuiEvent == "+")    ; In case of script's initialization, user's left click, keyboard selection or tree expansion - (re)fill the 'FileList' listview.
    {
        If (A_GuiEvent == "Normal")    ; If user left clicked anything in the TreeView.
        {
            If (A_EventInfo != 0)    ; If user clicked an empty space at right from a folder's name.
                TV_Modify(A_EventInfo, "Select")    ; Forcefully select that line.
            Else If (A_EventInfo == 0)    ; If user clicked an empty space (not the one at right from a folder's name).
                TV_Modify(0)    ; Remove selection and thus make 'FileList' ListView show the root folder's contents.
        }
        Gui, TreeView, FolderTree
        TV_GetText(selectedItemText, A_EventInfo)    ; Determine the full path of the selected folder:
        ParentID := A_EventInfo
        If memorizePath    ; Variable 'memorizePath' is used as a token: if it returns true - then we shall use old path to the folder as the current one (otherwise the default path would be used).
            memorizePath := selectedFullPath
        Loop    ; Build the full path to the selected folder.
        {
            ParentID := TV_GetParent(ParentID)
            If !ParentID    ; No more ancestors.
                Break
            TV_GetText(ParentText, ParentID)
            SelectedItemText = %ParentText%\%selectedItemText%
        }
        ; selectedFullPath := startFolder "\" selectedItemText
        If memorizePath
            selectedFullPath := memorizePath
        If (A_GuiEvent == "") && !(token)
            Return
        Else If (A_GuiEvent == "+")
        {
            Loop %selectedItemText%\*.*, 2    ; Parse all the children of the selected item.
            {
                thisChildID := TV_GetChild(A_EventInfo)    ; Get first child's ID.
                If thisChildID
                    TV_Delete(thisChildID)
            }
            buildTree(selectedItemText, A_EventInfo)    ; Add children and grandchildren to the selected item.
        }
        Gui, ListView, FileList    ; Put the files into the ListView:
        LV_Delete()    ; Delete old data.
        GuiControl, -Redraw, FileList    ; Improve performance by disabling redrawing during load.
        token := memorizePath := FileCount := TotalSize := 0    ; Init prior to loop below.
        Loop %selectedItemText%\*.ahk    ; This omits folders and shows only .ahk-files in the ListView.
        {
            FormatTime, created, %A_LoopFileTimeCreated%, dd.MM.yyyy (HH:mm:ss)
            FormatTime, modified, %A_LoopFileTimeModified%, dd.MM.yyyy (HH:mm:ss)
            LV_Add("", A_LoopFileName, Round(A_LoopFileSize / 1024, 1) . " KB", created, modified)
            FileCount++
            TotalSize += A_LoopFileSize
        }
        GuiControl, +Redraw, FileList
        GoSub, sbUpdate
    }
Return
        ;}
        ;{ FileList
FileList:
    If (A_GuiEvent == "Normal") || (A_GuiEvent == "RightClick")
        Global activeControl := A_ThisLabel
Return
        ;}
        ;{ BookmarksList
BookmarksList:
    If (A_GuiEvent == "Normal") || (A_GuiEvent == "*") || (bookmarksModified == 1)
        Global activeControl := A_ThisLabel
    If !((A_GuiEvent == "") || (bookmarksModified == 1)) || !FileExist("Settings.ini") || (A_GuiEvent == "C")    ; Filter events out: (re)fill the listview only if the script just started, or we added/removed (a) bookmark(s). And don't fill anything if we have no bookmarks at all.
        Return
    If bookmarksModified    ; That variable is used as token for adding and deleting bookmarks.
        GoSub, BookmarksModified    ; First update the bookmarks file, and only then fill the listview.
    A_IndexMy := nBookmarks := bookmarks := token := ""
    Gui, ListView, BookmarksList
    LV_Delete()    ; Clear all rows.
    IniRead, bookmarks, Settings.ini, Bookmarks, list
    If bookmarks
    {
        StringSplit, bookmarks, bookmarks, |
        While (A_IndexMy < bookmarks0)
        {
            A_IndexMy++
            thisBookmark := bookmarks%A_IndexMy%
            IfExist, %thisBookmark%    ; Define whether the previously bookmared file exists.
            {    ; If the file exists - display it in the list.
                SplitPath, thisBookmark, name    ; Get file's name from it's path.
                FileGetSize, size, %thisBookmark%    ; Get file's size.
                FileGetTime, created, %thisBookmark%, C    ; Get file's creation date.
                FormatTime, created, %created%, dd.MM.yyyy (HH:mm:ss)    ; Transofrm creation date into a readable format.
                FileGetTime, modified, %thisBookmark%    ; Get file's last modification date.
                FormatTime, modified, %modified%, dd.MM.yyyy (HH:mm:ss)    ; Transofrm creation date into a readable format.
                LV_Insert(A_IndexMy, "", A_IndexMy, name, thisBookmark, Round(size / 1024, 1) . " KB", created, modified)    ; Add the listitem.
            }
            Else    ; If the file doesn't exist - remove that bookmark.
            {
                bookmarksModified := 1
                If !bookmarksToDelete
                    bookmarksToDelete := A_IndexMy
                Else
                    bookmarksToDelete := bookmarksToDelete . "," A_IndexMy
            }
        }
    }
    If bookmarksModified
        GoSub, BookmarksModified
Return

BookmarksModified:
    If bookmarksToDelete
    {
        Loop, Parse, bookmarks, |
            If (!RegExMatch(bookmarksToDelete, "\b" A_Index "\b"))
                nBookmarks .= (StrLen(nBookmarks) ? "|" : "") A_LoopField
        bookmarks := nBookmarks
    }
    Loop ; Remove double pipes.
    {
        IfInString, bookmarks, ||
            StringReplace, bookmarks, bookmarks, ||, |, All
        Else
            Break
    }
    IniWrite, %bookmarks%, Settings.ini, Bookmarks, list
    bookmarksToDelete := bookmarksModified := ""
    GoSub, BookmarksList
Return
        ;}
    ;}
    ;{ Tab #2: gLabel of ListView.
ManageProcesses:    ; Update the list of running scripts on the Tab #2.
    Global activeControl := A_ThisLabel, oldListItems := listItems, newRows := deadRows := 0
    Gui, ListView, ManageProcesses
    Loop, %indexScripts%    ; Transform the scriptsSnapshot's "pid" values into a pipe-separated string and store it in the 'listItems' variable.
        listItems := (A_Index != 1) ? (listItems "|" scriptsSnapshot[A_Index, "pid"]) : (scriptsSnapshot[A_Index, "pid"])
    Loop, parse, oldListItems, |    ; Look for scripts' dead processes and fulfill 'deadRows' variable with their rows' numbers separated by pipes.
    {
        this := A_LoopField
        Loop, Parse, listItems, |
        {
            stillLives := 0
            If (this == A_LoopField)
            {
                stillLives := 1
                Break
            }
        }
        If !stillLives
            deadRows := ((deadRows) ? (deadRows "|" A_Index) : (A_Index))
    }
    If deadRows    ; Delete rows if any scripts's processes were found dead.
    {
        Loop, Parse, deadRows, |
        {
            If (A_Index == 1)
                this := A_LoopField    ; This would be the 1st row to delete, thus since that row and up to the end of the list - we'll have to re-index them.
            LV_Delete(A_LoopField + 1 - A_Index)
        }
        Loop, %indexScripts%    ; Re-index rows #'s (first column) if needed.
            If (A_Index >= this)
                LV_Modify(A_Index,, A_Index)
    }
    Loop, Parse, listItems, |    ; Look for scripts' new processes and fulfill 'newRows' variable with their rows' numbers separated by pipes.
    {
        this := A_LoopField
        Loop, Parse, oldListItems, |
        {
            newFound := 0
            If (this == A_LoopField)
            {
                newFound := 1
                Break
            }
        }
        If !newFound
            newRows := ((newRows) ? (newRows "|" A_Index) : (A_Index))
    }
    If newRows    ; Add new rows for the newly found scripts.
        Loop, Parse, newRows, |
            LV_Add(, A_LoopField, scriptsSnapshot[A_LoopField, "pid"], scriptsSnapshot[A_LoopField, "name"], scriptsSnapshot[A_LoopField, "path"])
Return
    ;}
    ;{ Tab #3: gLabels of ListView.
AssistantsList:
    Global activeControl := A_ThisLabel, Global conditions, Global triggeredActions
    rowShift := 0
    Gui, ListView, AssistantsList
    IniRead, rules, Settings.ini, Assistants
    If rules
    {
        StringSplit, rule, rules, `n    ; Each rule is stored on a new line.
        Loop, %rule0%
        {
            ruleN := A_Index    ; 'ruleN' == the # of the rule being parsed.
            StringTrimLeft, rule%ruleN%, rule%ruleN%, 1 + StrLen(ruleN)    ; Cut away ini-file's "keys".
            Loop, Parse, rule%A_Index%, >    ; Trigger condition (TC) in a rule is separated by the ">" from the triggered actions (TA), which are the process(es) to be executed upon TC.
            {
                If (A_Index == 1)    ; Left part of each rule contains a group of pipe-separated TCs.
                {
                    conditions := ((conditions) ? (conditions "?" A_LoopField) : (A_LoopField))    ; Preparing data for further parsing by the 'ProcessList' subroutine. Save TC-groups of all the rules into a single variable 'conditions' and separate the groups with the "?".
                    If A_LoopField    ; Safe check against empty groups of TCs.
                    {
                        Loop, Parse, A_LoopField, |    ; If there are multiple TCs - they should be pipe-separated.
                        {
                            If A_LoopField    ; Safe check against empty TCs.
                            {
                                tcRows := A_Index    ; Number of rows to be occupied by the TC.
                                LV_Add(, ((A_Index == 1) ? (ruleN) : ("")), A_LoopField)
                            }
                        }
                    }
                }
                Else If (A_Index == 2)    ; Right part of each rule contains pipe separated TAs.
                {
                    triggeredActions := ((triggeredActions) ? (triggeredActions "?" A_LoopField) : (A_LoopField))    ; Preparing data for further parsing by the 'ProcessList' subroutine. Save TA-groups of all the rules into a single variable 'triggeredActions' and separate the groups with the "?".
                    If A_LoopField    ; Safe check against empty groups of TAs.
                    {
                        Loop, Parse, A_LoopField, |    ; If there are multiple TAs - they should be pipe-separated.
                        {
                            If A_LoopField    ; Safe check against empty TAs.
                            {
                                taRows := A_Index    ; Number of rows to be occupied by the TAs.
                                If (taRows > tcRows)
                                    LV_Add(,,, A_LoopField)
                                Else
                                    LV_Modify(taRows + rowShift,,,, A_LoopField)
                            }
                        }
                    }
                }
            }
            rowsOccupied := ((rowsOccupied) ? (rowsOccupied "|" ((taRows > tcRows) ? (taRows) : (tcRows))) : (((taRows > tcRows) ? (taRows) : (tcRows))))    ; This variable contains pipe-separated numbers which represent the number of rows occupied by each rule.
            rowShift := ((rowShift) ? (rowShift + ((taRows > tcRows) ? (taRows) : (tcRows))) : (((taRows > tcRows) ? (taRows) : (tcRows))))    ; This variable contains the total number of the occupied rows.
        }
    }
Return
    ;}
    ;{ ProcessList.
ProcessList:
    If !indexProcesses
        processesOldSnapshot := []
    Else
        processesOldSnapshot := processesSnapshot
    If !indexScripts
        scriptsOldSnapshot := []
    Else
        scriptsOldSnapshot := scriptsSnapshot
    Global processesSnapshot := [], Global scriptsSnapshot := [], Global indexScripts := 0, Global indexProcesses := 0
    For Process In ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process")    ; Parsing through a list of running processes to filter out non-ahk ones (filters are based on "If RegExMatch" rules).
    {    ; A list of accessible parameters related to the running processes: http://msdn.microsoft.com/en-us/library/windows/desktop/aa394372%28v=vs.85%29.aspx
        indexProcesses++
        processesSnapshot[indexProcesses, "pid"] := Process.ProcessId
        processesSnapshot[indexProcesses, "exe"] := Process.ExecutablePath
        ; processesSnapshot[indexProcesses, "cmd"] := Process.CommandLine
        If (RegExMatch(Process.CommandLine, "Si)^(""|\s)*\Q" A_AhkPath "\E.*\\(?<Name>.*\.ahk)(""|\s)*$", script)) && (RegExMatch(Process.CommandLine, "Si)^(""|\s)*\Q" A_AhkPath "\E.*""(?<Path>.*\.ahk)(""|\s)*$", script))
        {
            indexScripts++
            scriptsSnapshot[indexScripts, "pid"] := Process.ProcessId    ; Using "ProcessId" param to fulfill our "pidsArray" array.
            scriptsSnapshot[indexScripts, "name"] := scriptName    ; The first RegExMatch outputs to "scriptName" variable, who's contents we use to fulfill our "scriptNamesArray" array.
            scriptsSnapshot[indexScripts, "path"] := scriptPath    ; The second RegExMatch outputs to "scriptPath" variable, who's contents we use to fulfill our "scriptPathArray" array.
        }
    }
    isTCGroupMet()
    If (activeTab == 2)
        GoSub, ManageProcesses
Return
    ;}
    ;{ StatusBar.
sbUpdate:
; Update the three parts of the status bar to show info about the currently selected folder:
    SB_SetText(FileCount . " files", 1)
    SB_SetText(Round(TotalSize / 1024, 1) . " KB", 2)
    SB_SetText(selectedFullPath, 3)
Return
    ;}
    ;{ Tab #1: gLabels of buttons.
RunSelected:    ; G-Label of "Run selected" button.
    If (activeControl == "FileList")    ; In case the last active GUI element was "FileList" ListView.
    {
        Gui, ListView, FileList
        selected := selectedFullPath "\" getScriptNames()
        If !selected
            Return
        StringReplace, selected, selected, |, |%selectedFullPath%\, All
    }
    Else If (activeControl == "BookmarksList")    ; In case the last active GUI element was "BookmarksList" ListView.
    {
        Gui, ListView, BookmarksList
        selected := getScriptNames()
        If !selected
            Return
    }
    run(selected)
Return

BookmarkSelected:    ; G-Label of "Bookmark selected" button.
    Gui, ListView, FileList
    selected := getScriptNames()
    If !selected
        Return
    IniRead, bookmarks, Settings.ini, Bookmarks, list
    selected := selectedFullPath "\" selected
    StringReplace, selected, selected, |, |%selectedFullPath%\, All
    StringReplace, selected, selected, \\, \, All
    bookmarks := ((bookmarks) ? (bookmarks "|" selected) : (selected))
    IniWrite, %bookmarks%, Settings.ini, Bookmarks, list
    bookmarksModified := 1
    GoSub, BookmarksList
Return

DeleteSelected:    ; G-Label of "Delete selected" button.
    If (activeControl == "BookmarksList") || (activeControl == "FileList")
        Gui, ListView, %activeControl%
    If (activeControl == "BookmarksList")    ; In case the last active GUI element was "BookmarksList" ListView.
    {
        bookmarksToDelete := getRowNs()
        If !bookmarksToDelete
            Return
        bookmarksModified := 1
        GoSub, %activeControl%
    }
    Else If (activeControl == "FileList")    ; In case the last active GUI element was "FileList" ListView.
    {
        selected := getScriptNames()
        If !selected
            Return
        Msgbox, 1, Confirmation required, Are you sure want to delete the selected file(s)?
        IfMsgBox, OK
        {
            selected := selectedFullPath "\" selected
            StringReplace, selected, selected, |, |%selectedFullPath%\, All
            Loop, Parse, selected, |
                FileDelete, %A_LoopField%
            memorizePath := 1    ; This is used as a token.
            GoSub, FolderTree
        }
    }
    ; Else If (activeControl == "FolderTree")    ; In case the last active GUI element was "FileList" TreeView.
    ; {
    ;     Msgbox, 1, Confirmation required, Are you sure want to delete the selected folder(s) and it's/their contents?
    ;     IfMsgBox, OK
    ;     {
    ;         
    ;     }
    ; }
Return
    ;}
    ;{ Tab #2: gLabels of buttons.
Exit:
    Gui, ListView, ManageProcesses
    exit(getPIDs())
Return

Kill:
    Gui, ListView, ManageProcesses
    kill(getPIDs())
Return

killNreexecute:
    Gui, ListView, ManageProcesses
    killNreexecute(getPIDs())
Return

Reload:
    Gui, ListView, ManageProcesses
    reload(getPIDs())
Return

Pause:
    Gui, ListView, ManageProcesses
    pause(getPIDs())
Return

SuspendHotkeys:
    Gui, ListView, ManageProcesses
    suspendHotkeys(getPIDs())
Return

SuspendProcess:
    Gui, ListView, ManageProcesses
    suspendProcess(getPIDs())
Return

ResumeProcess:
    Gui, ListView, ManageProcesses
    resumeProcess(getPIDs())
Return
    ;}
    ;{ Tab #3: gLabels of buttons.
AddNewRule:
InputBox, ruleAdd, Add new 'Process Assistant' rule,
(
'Process Assistant' feature works so: you create rules for it, where you specify Trigger Condition) (or 'TC' further in this text) - which process should be assisted with Triggered Action (or 'TA' further in this text) - with which *.ahk script and then just whenever the specified process appears - the corresponding script will get executed and whenever the specified process dies - the corresponding script will get closed too.
In the "Settings" section of the script (in it's source code) you may select a way how to close the scripts. By default, it uses a gentle method which lets the scripts execute their "OnExit" subroutine.

There are the following rules for 'Process Assistant' rule creation:
1. Every rule has to consist of 2 parts, separated by the ">" character:
  a. the left part of a rule is used to specify TCs. TCs can be specified as a process name (explorer.exe) or as a full or partial path (without the "\" at start) to an executable. If multiple TCs are specified in 1 rule - that means that if ANY of those processes appears - it will trigger the rule.
  b. the right part of a rule is used to specify TAs. TAs can be specified only by it's full path to the *.ahk file (but don't specify the path to the "AutoHotkey.exe"). If multiple TAs are specified in 1 rule - that means that ALL of them will get executed/closed whenever the trigger works out.
2. One rule may contain multiple TCs or TAs: you just need to separate them by the "|" (pipe) symbol.

A few examples:
firefox.exe|Program Files\GoogleChrome\chrome.exe|C:\Program Files\Internet Explorer\iexplore.exe>C:\Program Files\AHK-Scripts\browserHelper.ahk

notepad.exe>C:\Program Files\AHK-Scripts\pimpMyPad.ahk

C:\Games\DOTA\dota.exe>C:\DOTA Scripts\cooldownSoundNotify.ahk|C:\DOTA Scripts\cheats\showInvisibleEnemies.ahk
),, 745, 515
If !(ErrorLevel) && ruleAdd    ; Do something only if user clicked [OK] and if he actually entered something.
    IfNotInString, ruleAdd, >    ; Fool-proof.
    {
        Run, https://en.wikipedia.org/wiki/RTFM
        Msgbox RTFM!
    }
    Else    ; If everything seems to be okay - add the rule to the Settings.ini and re-build the 'AssistantsList' LV.
    {
        IniWrite, %ruleAdd%, Settings.ini, Assistants, % rule0 + 1
        Gui, ListView, AssistantsList
        LV_Delete()
        GoSub, AssistantsList
    }
Return

DeleteRules:
    Gui, ListView, AssistantsList
    rulesToDelete := newRules := selectedRow := rowsSelected := rulesToDeleteIndex := isThatRowOfRule := "", thisIndex := thatIndex := "0"
    selectedRows := getRowNs()
    If !(selectedRows)    ; Safe check.
        Return
    Loop, Parse, selectedRows, |    ; Getting the number of items in the 'selectedRows'.
        rowsSelected := A_Index
    Loop, Parse, selectedRows, |
    {
        selectedRow := A_LoopField
        Loop
        {
            isThatRowOfRule := ""
            LV_GetText(isThatRowOfRule, selectedRow, 1)
            If isThatRowOfRule    ; we found # in the left column.
            {
                If !rulesToDelete    ; no rules to delete yet, so we just assign it
                    rulesToDelete := isThatRowOfRule
                Else    ; there are rules to delete, so we need to decide wheter to add a new value or not
                {
                    Loop, Parse, rulesToDelete, |    ; Getting the number of items in the 'rulesToDelete'.
                        rulesToDeleteIndex := A_Index
                    Loop, Parse, rulesToDelete, |
                    {
                        If (isThatRowOfRule == A_LoopField)
                            Break
                        Else If (rulesToDeleteIndex == A_Index) && (isThatRowOfRule != A_LoopField)
                            rulesToDelete .= "|" isThatRowOfRule
                    }
                }
                Break
            }
            Else    ; Didn't find the number in the left column, checking the row above.
                selectedRow--
        }
    }
    Loop, Parse, rulesToDelete, |    ; Getting the number of items in the 'rulesToDelete'.
        rulesToDeleteIndex := A_Index
    Loop, %rule0%
    {
        thisIndex := A_Index
        Loop, Parse, rulesToDelete, |
        {
            If (A_LoopField == thisIndex)
                Break
            Else If (A_LoopField != thisIndex) && (A_Index == rulesToDeleteIndex)
            {
                thatIndex++
                newRules := ((newRules) ? (newRules "`n" thatIndex "=" rule%thisIndex%) : (thatIndex "=" rule%thisIndex%))
            }
        }
    }
    IniWrite, %newRules%, Settings.ini, Assistants
    LV_Delete()
    GoSub, AssistantsList
Return
    ;}
;}

;{ HOTKEYS
#IfWinActive ahk_group ScriptHwnd_A
~Delete::
    If (activeTab == 1)
        GoSub, DeleteSelected
    Else If (activeTab == 2)
        Gosub, Kill
Return

~Esc::
    If (activeControl == "BookmarksList") || (activeControl == "FileList") || (activeControl == "ManageProcesses")
    {
        Gui, ListView, %activeControl%
        LV_Modify(0, "-Select")
    }
    Else If (activeControl == "FolderTree")
        TV_Modify(0)
Return

#IfWinActive
;}
;{ FUNCTIONS
    ;{ Functions to gather data.
getRowNs()    ; Get selected rows' numbers.
{
; Used by: Tab #1 'Manage files' - LVs: 'FileList', 'BookmarksList'; buttons: 'Run selected', 'Bookmark selected', 'Delete selected'. Tab #2 'Manage processes' - LVs: 'ManageProcesses'; buttons: "Kill and re-execute"; functions: getScriptNames(), getPIDs(), getScriptPaths().
; Input: none.
; Output: selected rows' numbers (separated by pipes, if many).
    Loop, % LV_GetCount("Selected")
        rowNs := ((rowNs) ? (rowNs "|" rowN := LV_GetNext(rowN)) : (rowN := LV_GetNext(rowN)))
    Return rowNs
}

getScriptNames()    ; Get scripts' names of the selected rows.
{
; Used by: Tab #1 'Manage files' - LVs: 'FileList', 'BookmarksList'; buttons: 'Run selected', 'Bookmark selected', 'Delete selected'.
; Input: none.
; Output: script names of the files in the selected rows.
    rowNs := getRowNs()
    Loop, Parse, rowNs, |
    {
        If (activeControl == "FileList")
            LV_GetText(thisScriptName, A_LoopField, 1)    ; 'FileList' LV Column #1 contains files' names.
        Else If (activeControl == "BookmarksList")
            LV_GetText(thisScriptName, A_LoopField, 3)    ; 'BookmarksList' LV Column #3 contains full paths of the scripts.
        Else If (activeControl == "ManageProcesses")
            LV_GetText(thisScriptName, A_LoopField, 4)    ; 'ManageProcesses' LV Column #4 contains full paths of the scripts.
        scriptNames := ((scriptNames) ? (scriptNames "|" thisScriptName) : (thisScriptName))
    }
    Return scriptNames
}

getPIDs()    ; Get PIDs of selected processes.
{
; Used by: Tab #2 'Manage processes' - LVs: 'ManageProcesses'; buttons: 'Exit', 'Kill', 'Kill and re-execute', 'Reload', '(Un) pause', '(Un) suspend hotkeys', 'Suspend process', 'Resume process'.
; Input: none.
; Output: PIDs (separated by pipes, if many).
    rowNs := getRowNs()
    Loop, Parse, rowNs, |
    {
        LV_GetText(thisPID, A_LoopField, 2)    ; Column #2 contains PIDs.
        PIDs := ((PIDs) ? (PIDs "|" thisPID) : (thisPID))
    }
    Return PIDs
}

getScriptPaths()    ; Get scripts' paths of the selected rows.
{
; Usable by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Kill and re-execute'.
; Input: none.
; Output: script paths of the files in the selected rows.
    rowNs := getRowNs()
    Loop, Parse, rowNs, |
        scriptsPaths := ((A_Index == 1) ? (scriptsSnapshot[A_LoopField, "path"]) : (scriptsPaths "|" scriptsSnapshot[A_LoopField, "path"]))
    Return scriptsPaths
}
    ;}
    ;{ Functions of process control.
run(paths)    ; Runs selected scripts.
{
; Used by: Tab #1 'Manage files' - LVs: 'FileList', 'BookmarksList'; buttons: 'Run selected', 'Kill and re-execute'; functions: isTAGroupRunning().
; Input: path or paths (separated by pipes, if many).
; Output: none.
    If !paths
        Return
    Loop, Parse, paths, |
        Run, "%A_AhkPath%" "%A_LoopField%"
}

exit(pids)    ; Closes processes nicely (uses PostMessage).
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Exit'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
        PostMessage, 0x111, 65307,,, ahk_pid %A_LoopField%
}

kill(pids)    ; Kills processes unnicely (uses "Process, Close").
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Kill', 'Kill and re-execute'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
        Process, Close, %A_LoopField%
    NoTrayOrphans()
}

killNreexecute(pids)    ; Kills processes unnicely (uses "Process, Close") and then re-executes them.
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Kill and re-execute'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    scriptsPaths := getScriptPaths()
    kill(pids)
    NoTrayOrphans()
    run(scriptsPaths)
}

reload(pids)    ; Reload (uses PostMessage) selected scripts.
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Reload'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
        PostMessage, 0x111, 65303,,, ahk_pid %A_LoopField%
}

pause(pids)    ; Pause selected scripts.
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: '(Un) pause'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
        PostMessage, 0x111, 65403,,, ahk_pid %A_LoopField%
}

suspendHotkeys(pids)    ; Suspend hotkeys of selected scripts.
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Kill and re-execute'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
        PostMessage, 0x111, 65404,,, ahk_pid %A_LoopField%
}

suspendProcess(pids)    ; Suspend processes of selected scripts.
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Suspend process''.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
    {
        If !(procHWND := DllCall("OpenProcess", "uInt", 0x1F0FFF, "Int", 0, "Int", A_LoopField))
            Return -1
        DllCall("ntdll.dll\NtSuspendProcess", "Int", procHWND)
        DllCall("CloseHandle", "Int", procHWND)
    }
}

resumeProcess(pids)    ; Resume processes of selected scripts.
{
; Used by: Tab #2 'Manage processes' - LV 'ManageProcesses'; buttons: 'Resume process'.
; Input: PID(s) (separated by pipes, if many).
; Output: none.
    If !pids
        Return
    Loop, Parse, pids, |
    {
        If !(procHWND := DllCall("OpenProcess", "uInt", 0x1F0FFF, "Int", 0, "Int", A_LoopField))
            Return -1
        DllCall("ntdll.dll\NtResumeProcess", "Int", procHWND)
        DllCall("CloseHandle", "Int", procHWND)
    }
}
    ;}
    ;{ Functions needed for 'Process Assistant' to work.

isTCGroupMet(ruleGroupN = 0)    ; Checks either all or specific TC group if any of it's TC's is running. It also calls other functions, like isTCMet() and isTAGroupRunning().
{
; Used by: soubroutines: 'ProcessList'; functions: isTCGroupMet().
; Input: specific TC-group's number or if it's called with no argument (or 0) - it calls self recursively.
; Output: none.
    If conditions    ; Just a safe check, not really needed.
    {
        Loop, Parse, conditions, ?    ; Parse TC-groups in the contents of 'conditions' variable.
        {
            If !ruleGroupN    ; The function was called with no argument, thus it should recursively call self to check every TC group.
                isTCGroupMet(A_Index)
            Else If (A_Index != ruleGroupN)
                Continue
            Else
            {
                StringSplit, this, A_LoopField, |
                that := A_Index
                Loop, Parse, A_LoopField, |    ; Parse TCs in the specified TC-group.
                {
                    isAnyTCMet := isTCMet(A_LoopField)
                    If isAnyTCMet    ; At least 1 TC in this group is met, we need to check if the corresponding TA group is running or not and execute it if needed.
                    {
                        isTAGroupRunning(that, 1)
                        Break
                    }
                    Else If (this0 == A_Index)    ; We've parsed the whole TC group and found out that no TCs in this group are met, thus we have to make sure that the corresponding TA group is dead too.
                        isTAGroupRunning(that, 0)
                }
            }
        }
    }
    Return
}
isTCMet(TC)    ; Check if a specific TC (not a group) is running or not.
{
; Used by: function isTCGroupMet().
; Input: TC's path.
; Output: "1" if running or otherwise "0".
    Loop, %indexProcesses%
    {
        this := processesSnapshot[A_Index, "exe"]
        IfInString, this, \%TC%
        {
            TCMet := 1
            Break
        }
        Else If (A_Index == indexProcesses)
            TCMet := 0
    }
    Return TCMet
}

isTAGroupRunning(ruleGroupN, SwitchOnOrOff)    ; Check if specified TA-group is running and either kill the scripts (0) or run the scripts (1).
{
; Used by: functions: isTCGroupMet().
; Input: 1st argument is a number of a TA-group to check, 2nd argument forces either to make sure that all the scripts' processes from the corresponding TA-group are dead (0) and kill the living ones; or that they are running (1) and execute the not yet running ones.
; Output: none.
    Loop, Parse, triggeredActions, ?
    {
        If (ruleGroupN != A_Index)
            Continue
        Loop, Parse, A_LoopField, |
        {
            isAnyTAMet := isTAMet(A_LoopField)
            If !(isAnyTAMet) && (SwitchOnOrOff)
                run(A_LoopField)
            Else If (isAnyTAMet) && !(SwitchOnOrOff)
                toBeKilled := ((toBeKilled) ? (toBeKilled "|" isAnyTAMet) : (isAnyTAMet))
        }
    }
    If toBeKilled
        %howToQuitAssistants%(toBeKilled)
}

isTAMet(TA)    ; Check if a specific TA (not a group) is running or not.
{
; Used by: functions: isTAGroupRunning().
; Input: TA's path.
; Output: script's PID if it's running or otherwise "0".
    Loop, %indexScripts% 
    {
        this := scriptsSnapshot[A_Index, "path"]
        If (TA == this)
        {
            TAMet := scriptsSnapshot[A_Index, "pid"]
            Break
        }
        Else If (A_Index == indexScripts)
            TAMet := 0
    }
    Return TAMet
}
    ;}
    ;{ Fulfill 'TreeView' GUI.
buildTree(folder, ParentItemID = 0)
{
; This function adds all the subfolders in the specified folder to the TreeView.
; It also calls itself recursively to build a nested tree structure of any depth.
    If !folder
        Return
    Loop %folder%\*, 2    ; Retrieve all of Folder's sub-folders.
        buildOneBranch(A_LoopFileFullPath, TV_Add(A_LoopFileName, ParentItemID, "Icon1"))
}

buildOneBranch(Folder, ParentItemID = 0)
{
    If !folder
        Return
    Loop %folder%\*.*, 2    ; Retrieve all of Folder's sub-folders.
        TV_Add(A_LoopFileName, ParentItemID, "Icon1")
}

; WM_DEVICECHANGE(wp, lp)
; {
;     ; Static DBT_DEVICEARRIVAL := 0x8000, DBT_DEVICEREMOVECOMPLETE := 0x8004, DBT_DEVTYP_VOLUME := 2
;     If ((wp == 0x8000 || wp == 0x8004) && NumGet(lp + 4, "UInt") == 2)
;     {
;         dbcv_unitmask := NumGet(lp+12, "UInt")
;         Loop, 26    ; The number of letters in latin alphabet.
;         {
;             driveLetter := Chr(Asc("A") + A_Index - 1)
;         } Until (dbcv_unitmask >> (A_Index - 1))&1
;         MsgBox, % "Диск """ driveLetter ":"" " (wp == 0x8000 ? "подключен" : "отключен") ".`nПытаюсь обновить дерево соответствующим образом.`nwp: '" wp "'"
;         If (wp == 0x8000)    ; A new drive got connected
;             TV_Add(driveLetter)
;         Else If (wp == 0x8004)    ; A drive got removed.
;         {
;             Loop
;             {
;                 If (A_Index == 1)
;                     driveID := TV_GetChild(0)
;                 Else
;                     driveID := TV_GetNext(driveID)
;                 TV_GetText(thisDrive , driveID)
;             } Until (driveLetter == thisDrive)
;             TV_Delete(driveID)
;         }
;     }
; }
    ;}
    ;{ NoTrayOrphans() - a bunch of functions to remove tray icons of dead processes.
NoTrayOrphans()
{
    TrayInfo := TrayIcons(sExeName, "ahk_class Shell_TrayWnd", "ToolbarWindow32" . GetTrayBar()) "`n"
        . TrayIcons(sExeName, "ahk_class NotifyIconOverflowWindow", "ToolbarWindow321")
    Loop, Parse, TrayInfo, `n
    {
        ProcessName := StrX(A_Loopfield, "| Process: ", " |")
        ProcesshWnd := StrX(A_Loopfield, "| hWnd: ", " |")
        ProcessuID := StrX(A_Loopfield, "| uID: ", " |")
        If !ProcessName && ProcesshWnd
            RemoveTrayIcon(ProcesshWnd, ProcessuID)
    }
}

TrayIcons(sExeName, traywindow, control)
{
    DetectHiddenWindows, On
    WinGet, pidTaskbar, PID, %traywindow%
    hProc := DllCall("OpenProcess", "Uint", 0x38, "int", 0, "Uint", pidTaskbar)
    pProc := DllCall("VirtualAllocEx", "Uint", hProc, "Uint", 0, "Uint", 32, "Uint", 0x1000, "Uint", 0x4)
    SendMessage, 0x418, 0, 0, %control%, %traywindow%
    Loop, %ErrorLevel%
    {
        SendMessage, 0x417, A_Index - 1, pProc, %control%, %traywindow%
        VarSetCapacity(btn, 32, 0), VarSetCapacity(nfo, 32, 0)
        DllCall("ReadProcessMemory", "Uint", hProc, "Uint", pProc, "Uint", &btn, "Uint", 32, "Uint", 0)
        iBitmap := NumGet(btn, 0)
        idn := NumGet(btn, 4)
        Statyle := NumGet(btn, 8)
        If dwData := NumGet(btn, 12)
            iString := NumGet(btn, 16)
        Else
        {
            dwData := NumGet(btn, 16, "int64")
            iString := NumGet(btn, 24, "int64")
        }
        DllCall("ReadProcessMemory", "Uint", hProc, "Uint", dwData, "Uint", &nfo, "Uint", 32, "Uint", 0)
        If NumGet(btn,12)
        {
            hWnd := NumGet(nfo, 0)
            uID := NumGet(nfo, 4)
            nMsg := NumGet(nfo, 8)
            hIcon := NumGet(nfo, 20)
        }
        Else
        {
            hWnd := NumGet(nfo, 0, "int64")
            uID := NumGet(nfo, 8)
            nMsg := NumGet(nfo, 12)
            hIcon := NumGet(nfo, 24)
        }
        WinGet, pid, PID, ahk_id %hWnd%
        WinGet, sProcess, ProcessName, ahk_id %hWnd%
        WinGetClass, sClass, ahk_id %hWnd%
        If !sExeName || (sExeName == sProcess) || (sExeName == pid)
        {
            VarSetCapacity(sTooltip, 128)
            VarSetCapacity(wTooltip, 128*2)
            DllCall("ReadProcessMemory", "Uint", hProc, "Uint", iString, "Uint", &wTooltip, "Uint", 128*2, "Uint", 0)
            DllCall("WideCharToMultiByte", "Uint", 0, "Uint", 0, "str", wTooltip, "int", -1, "str", sTooltip, "int", 128, "Uint", 0, "Uint", 0)
            sTrayIcons .= "idx: " A_Index - 1 " | idn: " idn " | Pid: " pid " | uID: " uID " | MessageID: " nMsg " | hWnd: " hWnd " | Class: " sClass " | Process: " sProcess " | Icon: " hIcon " | Tooltip: " wTooltip "`n"
        }
    }
    DllCall("VirtualFreeEx", "Uint", hProc, "Uint", pProc, "Uint", 0, "Uint", 0x8000)
    DllCall("CloseHandle", "Uint", hProc)
    Return sTrayIcons
}

GetTrayBar()
{
    ControlGet, hParent, hWnd,, TrayNotifyWnd1, ahk_class Shell_TrayWnd
    ControlGet, hChild, hWnd,, ToolbarWindow321, ahk_id %hParent%
    Loop
    {
        ControlGet, hWnd, hWnd,, ToolbarWindow32%A_Index%, ahk_class Shell_TrayWnd
        If (hWnd == hChild)
            idxTB := A_Index
        If !hWnd || (hWnd == hChild)
            Break
    }
    Return idxTB
}

StrX(H, BS = "", ES = "", Tr = 1, ByRef OS = 1)
{
    Return (SP := InStr(H, BS, 0, OS)) && (L := InStr(H, ES, 0, SP + StrLen(BS))) && (OS := L + StrLen(ES)) ? SubStr(H, SP := Tr ? SP + StrLen(BS) : SP, (Tr ? L : L + StrLen(ES)) -SP) : ""
}

RemoveTrayIcon(hWnd, uID, nMsg = 0, hIcon = 0, nRemove = 2)
{
    NumPut(VarSetCapacity(ni,444,0), ni)
    NumPut(hWnd, ni, 4)
    NumPut(uID, ni, 8)
    NumPut(1|2|4, ni, 12)
    NumPut(nMsg, ni, 16)
    NumPut(hIcon, ni, 20)
    Return DllCall("shell32\Shell_NotifyIconA", "Uint", nRemove, "Uint", &ni)
}
    ;}
;}

2

Re: AHK: MasterScript (менеджер .ahk скриптов)

Drugoy пишет:

Каждый из запущенных скриптов добавляет в трэй одинаковую иконку с зелёной [Н], так что различить их можно только наводя на них курсором и читая текст во всплывающих подсказках.

#Persistent
Menu, Tray, Icon, Shell32.dll, 131

— присваиваем любую иконку.

Drugoy пишет:

Поэтому я написал простенький скрипт для управления файлами и процессами ahk-скриптов.

Вряд ли кто захочет разбираться в "простеньком" скрипте на 700 с лишним строк, тем более без описания, как им пользоваться. Лучше задавать более конкретные вопросы с минимальным кодом, демонстрирующим проблему.

Вообще, лично я никогда не испытывал потребности в таком вот "менеджере скриптов". Часть моих скриптов загружается при старте системы, часть вызывается горячими клавишами. Зачем нужен этот "менеджер" в принципе? Что он может сделать такого, чего нельзя реализовать в каждом скрипте по отдельности?

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

3 (изменено: Drugoy, 2013-10-03 20:06:35)

Re: AHK: MasterScript (менеджер .ahk скриптов)

teadrinker пишет:

— присваиваем любую иконку.

Я планировал и далее дорабатывать скрипт и приделать возможность вообще прятать иконки скриптов.

teadrinker пишет:

тем более без описания, как им пользоваться. Лучше задавать более конкретные вопросы с минимальным кодом, демонстрирующим проблему.

Пользоваться им просто: взять и запустить.
По-умолчанию, он подцепляет навигацию по папкам из места, где расположен autohotkey.exe.
Но в блоке "settings block" можно раскомментировать строку где присваивается значение для переменной startFolder и подставить туда другой путь.
Это временное решение, я думал приделать более продвинутую навигацию после того, как разберусь как сделать TreeView таким, чтобы он отображал все диски и их содержимое на 1-ом и 2-ом уровне и подгружал дальнейшие уровни в дерево только тогда, когда пользователь выбрал какую-то папку (в противном случае, он сканирует все диски чтобы построить древовидную структуру папок и это занимает несколько минут).

teadrinker пишет:

Вообще, лично я никогда не испытывал потребности в таком вот "менеджере скриптов". Часть моих скриптов загружается при старте системы, часть вызывается горячими клавишами. Зачем нужен этот "менеджер" в принципе? Что он может сделать такого, чего нельзя реализовать в каждом скрипте по отдельности?

Ничего принципиально нового он не умеет. Просто управление процессами и возможность "забукмарчить" выбранные скрипты.
Не знаю как у вас, а у меня на компьютере десятки скриптов и все рассованы по разным папочкам по особой структуре. И часто нужно запустить несколько скриптов из разных папок, а вручную лезть за ними - лень.
А каждому скрипту ещё и прописывать уникальную трэй-иконку - так вообще лень, тем более, что многим скриптам и иконка-то не нужна.
А ещё иногда скрипты виснут и их надо убить или перезапустить: лезешь в таск менеджер, а там они все одинаково выглядят, пока не начнёшь разглядывать свойства каждого отдельного процесса.
Это всё зависит от user's experience. У Вас он, видимо, такой, что мой скрипт Вам просто не нужен.

teadrinker пишет:

Вряд ли кто захочет разбираться в "простеньком" скрипте на 700 с лишним строк

Там наведена структура, нужно чтоб Ваш текстовый редактор поддерживал folding строк кода (а это умеют, кажется, все текстовые редакторы хоть сколько-то более продвинутые чем notepad.exe):

+ пруфпик

https://i.imgur.com/ogKSlMW.png

4 (изменено: Drugoy, 2013-10-18 03:44:54)

Re: AHK: MasterScript (менеджер .ahk скриптов)

Мажорное обновление!

Список изменений:
[+] Добавил новую фичу "Process Assistants" (подробнее о ней - ниже).
[x] Переписал "сердце" скрипта: отслеживание обычных процессов и процессов скриптов.
[x] Добавил кучу проверок (код стал безопасней).
[x] Переписал и добавил кучу комментариев в код (каждая функция теперь превосходно описана)
[x] Переписал некоторые функции: упростил их вызовы и укоротил их имена, имена переменных и имена аргументов.
[-] Известный баг: заметил, что при каждом закрытии и повторном открытии окна (в случае, если пользователем выбрано запоминать позицию и размер окна, что есть значение по умолчанию) - размер окна чуточку увеличивается.
[-] Заметил, что функция NoTrayOrphans(), которая чистит трей от иконок мёртвых процессов - не работает под x64 версией AutoHotkey. Не я писал эту функцию, а её автор пока не выходит на связь. Буду рад, если кто-то добавит функции совместимость с x64 версией AutoHotkey..

О новой добавленной фиче "Process Assistants":
https://i.imgur.com/SpXjm4F.png

'Process Assistant' работает таким образом: вы создаёте для неё правила, в которых вы связываете определённые процессы с указанными скриптами и как только скрипт замечает, что указанный процесс запущен - он запускает и "ассистирующие" ему скрипты. Как только тот процесс перестаёт существовать - то закрываются и ассистирующие скрипты.
В коде скрипта в секции "Settings" можно задать вид того, как будут закрываться скрипты: нежно (т.е. с выполнением у них OnExit) или же жёстко (всё равно что прибить процесс диспетчером задач).

Существуют следующие правила создания правил для 'Proces Assistant':
1. Каждое правило должно состоять из 2-ух частей, разделённых символом ">":
  a. в левой части правила указываются "Trigger Conditions" (TCs), т.е. условия при которых будут запущены соответствующие скрипты. В 1 правиле могут быть указаны сразу несколько TCs: для этого их надо разделить символом "|" и в таком случае правило будет срабатывать ЕСЛИ ХОТЯ БЫ ОДИН из указанных процессов запущен. Процессы можно описывать по имени их исполняемого файла (explorer.exe) или же по полному или частичному (без "\" в начале) пути к нему.
  b. в правой части правила указываются "Triggered Actions" (TAs). Их тоже можно указать сразу несколько в одном правиле и тогда, когда правило будет срабатывать - будут запускаться или закрываться сразу все указанные в правой части правила скрипты. Скрипты указываются полными путями к их .ahk файлу (без указания пути к autohotkey.exe).
2. В одно правило может содержать несколько TCs и/или TAs: просто разделяйте их символом "|".

Пара примеров правильно составленных правил:
firefox.exe|Program Files\GoogleChrome\chrome.exe|C:\Program Files\Internet Explorer\iexplore.exe>C:\Program Files\AHK-Scripts\browserHelper.ahk

notepad.exe>C:\Program Files\AHK-Scripts\pimpMyPad.ahk

C:\Games\DOTA\dota.exe>C:\DOTA Scripts\cooldownSoundNotify.ahk|C:\DOTA Scripts\cheats\showInvisibleEnemies.ahk

5

Re: AHK: MasterScript (менеджер .ahk скриптов)

Drugoy Кстати, вот тебе идея - напиши компилятор скриптов. Я его начал писать и забросил по тех. проблемам - OS переустанавливать собрался... вот уже как 3 месяца собираюсь
А компилятор пакетный, т.е. через ini-файл, идея была такая.
Для каждого скрипта есть ini-файл и он указывается как параметр компилятору скриптов, а тот на его основе формирует ком. строку 

+ вот фрагмент
vTxt := "Запуск компилятора"
GuiControl,, Msg, % vTxt
Sleep, 1000
vTarget := "Ahk2Exe.exe /in C:\AHK\AllSystemOS\AllSystemOS.ahk /out C:\ASOS.exe /icon C:\AHK\AllSystemOS\ASOS_RES\Os_Settings_Gear.ico /bin ""Unicode 32-bit.bin"" /mpress 1"
; Рабочая папка компилятора
vWorkingDir := "C:\Program Files (x86)\AutoHotkey\Compiler"
; Запуск компилятора
Run, %vTarget% , %vWorkingDir%
sleep 1500 ; Чуть подождём = 1,5 сек
vTxt := "Запускаем ASOS.exe"
GuiControl,, Msg, % vTxt
Sleep, 1000
; Имя запускаемого процесса 
vTarget := "ASOS.exe"
; Его рабочая папка
vWorkingDir := "C:\"
; Запускаем ASOS.exe
Gui, Hide
Sleep, 3000
Run, %vTarget% , %vWorkingDir%
;Запускаем редактор Notepad++ , если он был запущен ранее
if (fNP){
    Sleep, 5000 
    Gui, Show
    vTxt := "Запускаем редактор Notepad++"
    GuiControl,, Msg, % vTxt
    Sleep, 1000
    vTarget := "C:\Program Files (x86)\Notepad++\notepad++.exe"
    vWorkingDir := "C:\Users\SASA\AppData\Local\Temp\nppLocalization"
    Run, %vTarget% , %vWorkingDir%
    sleep 1500 ; Чуть подождём = 1,5 сек
   }
; И выходим
gosub GuiClose
return 

lGui:
vTxt := "Идёт компиляция скрипта"
Gui, -resize -maximizebox -minimizebox +caption -Border +AlwaysOnTop +Owner
Gui, font, s18 Bold
Gui, Add, Text, x2 y80 w460 h180 +Center vMsg
Gui, Show, x603 y303 h185 w462
Return
"На каждое действие есть равная ему противодействующая критика." Постулат Харриссона
OS Windows 7 x64
AutoHotkey v1.1.32.00 - November 24, 2019
Click to Download

6

Re: AHK: MasterScript (менеджер .ahk скриптов)

Indomito, извини, но я не вижу пока смысла вообще компилировать скрипты: основную массу скриптов я всё равно пишу под себя, на все домашние компы - я тоже уже поставил AutoHotkey.
Даже если ты носишь всё своё на флэшке и работаешь каждый раз на рандомных чужих компах - не вижу проблем закинуть на флэшку и автохоткей + менеджер ассоциаций расширений файлов для портабл флэшек (автоматом читает системные, потом прописывает нужные, а при завершении твоей работы с компом - может вернуть сохранённые системные назад как было).
А отдельные скрипты которые я пишу кому-то, у кого нет autohotkey - так это столь нечастая штука, что я скомпилировать вручную - нет никаких проблем, а уж для чего в этом случае может понадобиться .ini с описанием - хз.

7

Re: AHK: MasterScript (менеджер .ahk скриптов)

Обновление:
[+] Переделал дерево в "Умное Дерево": теперь оно способно показывать большие структуры и достаточно быстро, за счёт того, что достраивание дерева происходит каждый раз, когда пользователь раскрывает какую-то его ветвь.
[x] Теперь навигация происходит по папкам не от "startFolder", а от корней всех дисков (поддерживаются диски только FIXED и REMOVABLE типов).
[x] У разных типов дисков - разные иконки.

Критика:
1. Пока скрипт не следит за подключением новых/отключением старых дисков. Я знаю, как это исправить простым образом, но хотел исправить это "продвинутым" образом, но споткнулся на конфликте входящих вызовов OnMessage и исходящих вызовов ComObjGet, которые приводят к ошибке и поломке работы скрипта.
2. Стоит добавить в скрипт поддержу "закладок" для папок (добавление новых, построение дерева и удаление существующих). Скоро добавлю.

8 (изменено: Drugoy, 2014-10-28 00:00:08)

Re: AHK: MasterScript (менеджер .ahk скриптов)

Обновил скрипт (переписав внутренности уже n раз).
Теперь он следит за подключениями-отключениями съёмных дисков.
Закладки для папок были приделаны.
Скомбинировал пару кнопок для управления процессами, т.к. скрипт теперь умеет определять их текущее состояние.
Состояние скриптов показывается в виде иконок (см. скриншот в спойлере).
Работает даже заморозка процессов (только поосторожней с этой штукой, т.к. заморозка любого процесса в винде, который имеет хуки на мышиные нажатия, причиняет адские лаги курсору до момента разморозки или смерти процесса).

+ Свежие скриншоты

https://i.imgur.com/LmUR9ye.png

https://i.imgur.com/SeTj5g7.png

p.s.: я был бы рад, если б кто-нибудь попробовал скрипт и подсказал что в нём надо подправить, а что в него надо добавить.

9

Re: AHK: MasterScript (менеджер .ahk скриптов)

У меня показывает некоторые скрипты, как Suspended, хотя это не так:

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

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

10

Re: AHK: MasterScript (менеджер .ahk скриптов)

Это происходит в одном из двух случаев: либо когда у этого скрипта открыто меню его иконки в трее (это, похоже, не вылечить), либо когда у процесса есть несколько окон: тогда не понятно по какому из них снимать состояние (но это, возможно, подправлю как-нибудь).

11

Re: AHK: MasterScript (менеджер .ahk скриптов)

Вроде исправил этот досадный баг последним коммитом.
+ добавил кнопки Open и Edit.
+ раньше нельзя было управлять процессом самого мастер-скрипта. Теперь можно (правда теперь есть чуть более новый баг ).

12

Re: AHK: MasterScript (менеджер .ahk скриптов)

Попробуйте запустить скрипт и плавно двигать его окно за заголовок от одного края экрана к другому.

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

13

Re: AHK: MasterScript (менеджер .ahk скриптов)

teadrinker
похоже, что с этим, увы, ничего нельзя поделать
Я сначала подумал, что это из-за частого употребления LV_Modify() внутри функции memoryScanfunc(), вызываемой через метку MemoryScan, которая выполняется по таймеру, но я обновил скрипт переделав в нём memoryScanfunc() так, что LV_Modify() используется только тогда, когда у процесса какого-то скрипта сменилось состояние и ему надо сменить иконку в списке на 2-ой вкладке. Но это не помогло.

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

Я до сих пор мало понимаю в нитях и их приоритетах, и мне кажется тут никакие Critical/Priority/Thread не помогут, но если я ошибаюсь - подскажите что надо сделать.

Кстати, странно, но иногда этот глюк пропадает сам собой на какое-то время, но потом возвращается.

p.s.: в обновлённой версии также подправил работу всех командных кнопок так, чтобы если в списке процессов была выбрана группа процессов включая процесс самого masterscript'а, то действия бы надёжно выполнялись для всех процессов, а не прекращались бы на обработке masterscript'а, как это было раньше.

14

Re: AHK: MasterScript (менеджер .ahk скриптов)

Скорее всего это происходит из-за работы таймера. Нужно найти способ его приостанавливать на время перетаскивания, если скрипт предназначен для публикации, ибо подобные "подлагивания" очень раздражают, имхо, конечно.

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

15 (изменено: Irbis, 2014-11-14 13:16:29)

Re: AHK: MasterScript (менеджер .ahk скриптов)

Добавил cтрочку (1) в функцию сканирования памяти. Обновляться не будет во время перетаскивания и изменения размеров окна, лагов не наблюдается. Правда, если просто зажать ЛКМ в окне, обновление тоже приостановится.

memoryScanFunc()    ; LAGGY.
{
    Gui, ListView, ManageProcesses
    if !(WinActive("ahk_class AutoHotkeyGUI") && GetKeyState("vk1", "P"))     ; (1)
    For k, v In scriptsSnapshot    ; Repeat as many times as there are running AHK-scripts.
    {
        newState := (isProcessSuspended(v.pid) ? 5 : 1 + getScriptState(v.pid))
        If (v.icon != newState)
            LV_Modify(k, "Icon" v.icon := newState)
    }
}

Upd: более "правильное" решение нашлось при помощи OnMessage()
В секции автовыполнения:

OnMessage(0x3, "WinMoveResize")
OnMessage(0x5, "WinMoveResize")

функция memoryScanFunc() примет вид:

memoryScanFunc()    ; LAGGY.
{
    Global LastTimeMoveOrResize
    Gui, ListView, ManageProcesses
    if !LastTimeMoveOrResize || (A_TickCount - LastTimeMoveOrResize > 300)
    For k, v In scriptsSnapshot    ; Repeat as many times as there are running AHK-scripts.
    {
        newState := (isProcessSuspended(v.pid) ? 5 : 1 + getScriptState(v.pid))
        If (v.icon != newState)
            LV_Modify(k, "Icon" v.icon := newState)
    }
}

Добавлена функция-обработчик перемещения/изменения размеров окна:

WinMoveResize(wParam, lParam)
{
   Global LastTimeMoveOrResize := A_TickCount
}

16 (изменено: Irbis, 2014-11-14 21:42:43)

Re: AHK: MasterScript (менеджер .ahk скриптов)

Оказалось все еще проще - при перемещении можно просто обновлять таймер, так что он просто будет откладываться при каждом срабатывании OnMessage()
Эти строки остаются

OnMessage(0x3, "WinMoveResize")
OnMessage(0x5, "WinMoveResize")

функция memoryScanFunc() остается в авторском виде.

Обработчик событий перемещения/изменения:

WinMoveResize(wParam, lParam) {
SetTimer, MemoryScan, %memoryScanInterval%
}

Переменная memoryScanInterval должна быть объявлена глобальной в таком случае. (строка 26)

17 (изменено: Drugoy, 2014-11-14 20:24:27)

Re: AHK: MasterScript (менеджер .ahk скриптов)

Irbis, большое спасибо за столь тщательную, подробную и адресную помощь
Последний вариант с обновлением таймера - очень изящное решение, но просто науки ради: а почему если в WinMoveResize() вместо этой строки попробовать прописать Critical, 50 или Thread, NoTimers или Thread, Priority, 9001 - эти решения не решают проблему лагов при движении окна?

18

Re: AHK: MasterScript (менеджер .ahk скриптов)

Пропатчил скрипт в репозитории:
+ включил исправление от Irbis, исправляющее лаги отрисовки передвигаемого окна
+ добавил контекстных меню (с "умными" проверками, отключающими в них неприменимые для выбранного объекта действия [запрещено букмарчить букмарки])
+ в целях борьбы с зомби-процессами (процесс умер, а информация о нём осталась) заставил функцию kill() мгновенно обновлять и 'ManageProcesses' ListView, и массив scriptsSnapshot, с данными о запущенных скриптах.

19 (изменено: Irbis, 2014-11-15 18:00:56)

Re: AHK: MasterScript (менеджер .ahk скриптов)

Drugoy, пожалуйста.
По поводу лагов с Thread, NoTimers или Thread, Priority, 9001 дело в том, что при срабатывании OnMessage() новый поток не работает постояно, а быстро завершает свою работу, потом снова срабатывает и т.д. Таймер в перерывах запускает memoryScanFunc(), который и подвешивает перемещение окна. В пользу этого говорит небольшой проверочный код, попробуй подвигать окно или его границы.

WinMoveResize(wParam, lParam)
{
   Static c:=0
   tooltip % c++
}