Trong Posted July 29 Posted July 29 (edited) I want to share a framework that I am using in my "Taskbar Program" project. expandcollapse popup; =============================================================================================================================== ; SCRIPT: Controller-Worker-Task Manager Framework ; AUTHOR: Dao Van Trong - TRONG.PRO ; VERSION: 1.0 (Optimized, INI-driven, Tray-Only) ; DESCRIPTION: This script implements a robust Controller-Worker-Task architecture for running background processes. ; It is designed to be a flexible framework for automation tasks. ; ; ARCHITECTURE: ; - Controller: The main process that runs persistently. It has no GUI and is controlled entirely via a system tray icon. ; Its responsibilities include: ; 1. Reading the configuration from 'Configs.ini'. ; 2. Creating and managing the tray menu. ; 3. Starting, stopping, and monitoring Worker processes. ; 4. Launching one-shot Task processes. ; 5. Automatically reloading the configuration when 'Configs.ini' is modified. ; ; - Workers: Long-running background processes that perform recurring tasks (e.g., monitoring, syncing). ; They are started and stopped by the Controller and run completely hidden. ; Each worker monitors the Controller and will self-terminate if the Controller exits. ; ; - Tasks: Short-lived background processes that perform a single, one-shot action (e.g., generate a report). ; They can be configured to run exclusively (locking all other tasks) or concurrently. ; They can also be configured to run as a separate sub-process or directly within the Controller's process. ; ; HOW IT WORKS: ; - IPC (Inter-Process Communication): Communication between the Controller and Workers is handled via the Windows Registry. ; The Controller writes a signal to a specific registry key to gracefully shut down a Worker. ; - Asynchronous Operations: All monitoring (process status, config file changes) and Worker functions are handled ; asynchronously using AdlibRegister, ensuring the Controller remains responsive. ; - Configuration: All Workers and Tasks are defined in the master arrays within this script, but they are enabled, ; disabled, and configured via an external 'Configs.ini' file. This allows for easy customization without ; modifying the source code. ; - Logging: The script generates a detailed log file ('manager.log') in the same directory, recording all major ; events like startup, shutdown, worker status changes, and errors. ; =============================================================================================================================== #include <TrayConstants.au3> #include <Array.au3> #include <File.au3> #include <FileConstants.au3> ; =============================================================================================================================== ; --- GLOBAL CONSTANTS AND VARIABLES ; =============================================================================================================================== ; --- Settings --- Global Const $g_sConfigFile = @ScriptDir & "\Configs.ini" Global Const $g_sLogFile = @ScriptDir & "\Manager.log" Global $g_sAppName = "Controller-Worker-Task Manager" Global $g_iCheckConfigInterval = 10000 Global $g_sLastConfigModDate = "" ; --- Registry & IPC --- Global Const $g_sRegBase = "HKCU\Software\ControllerWorkerApp" Global Const $g_iAdlibCheckInterval = 1000 Global Const $g_iGracefulShutdownTimeout = 3000 ; --- Status Constants for internal logic --- Global Const $STATUS_STOPPED = 0, $STATUS_RUNNING = 1, $STATUS_ERROR = 2 ; --- Task Execution Mode & Type Constants --- Global Const $TASK_MODE_EXCLUSIVE = 0, $TASK_MODE_CONCURRENT = 1 Global Const $TASK_RUN_TYPE_SUBAPP = 0, $TASK_RUN_TYPE_DIRECT = 1 ; --- Menu Type Constants --- Global Const $MENU_TYPE_MAIN = 0, $MENU_TYPE_SUB = 1 ; --- MASTER DATA STRUCTURES (Hard-coded definitions) --- ; [InternalName, DisplayName, FunctionName, MenuType] Global Const $g_aMasterWorkers[10][4] = [ _ ["CheckResources", "Check Resources", "Worker1Function", $MENU_TYPE_MAIN], _ ["SyncFiles", "Sync Files", "Worker2Function", $MENU_TYPE_MAIN], _ ["MonitorNetwork", "Monitor Network", "Worker3Function", $MENU_TYPE_MAIN], _ ["BackupDB", "Backup DB", "Worker4Function", $MENU_TYPE_SUB], _ ["CleanupLogs", "Cleanup Logs", "Worker5Function", $MENU_TYPE_SUB], _ ["ProcessEmails", "Process Emails", "Worker6Function", $MENU_TYPE_SUB], _ ["AutoScreenshot", "Auto Screenshot", "Worker7Function", $MENU_TYPE_SUB], _ ["MonitorTemp", "Monitor Temp", "Worker8Function", $MENU_TYPE_SUB], _ ["CheckUpdates", "Check Updates", "Worker9Function", $MENU_TYPE_SUB], _ ["SecurityScan", "Security Scan", "Worker10Function", $MENU_TYPE_SUB] _ ] ; [InternalName, DisplayName, FunctionName, MenuType, ExecutionMode, RunType] Global Const $g_aMasterTasks[10][6] = [ _ ["QuickCleanup", "Quick Cleanup", "TaskA_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_SUBAPP], _ ["GenerateReport", "Generate Report", "TaskB_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_SUBAPP], _ ["SendNotification", "Send Notification (Direct)", "TaskJ_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_DIRECT], _ ["TaskC", "Run Task C (Direct)", "TaskC_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_DIRECT], _ ["TaskD", "Run Task D", "TaskD_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP], _ ["TaskE", "Run Task E", "TaskE_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP], _ ["TaskF", "Run Task F", "TaskF_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP], _ ["TaskG", "Run Task G", "TaskG_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP], _ ["TaskH", "Run Task H", "TaskH_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP], _ ["TaskI", "Run Task I", "TaskI_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP] _ ] ; --- Dynamic Data Structures (Populated from Master based on INI) --- Global $g_aWorkers, $g_iNumWorkers Global $g_aTasks, $g_iNumTasks ; --- Tray Menu Control Variables --- Global $g_aWorkerTrayItems[1], $g_aTaskTrayItems[1], $g_iTrayExit ; --- Shared Global Variables for Worker processes --- Global $g_sWorkerInternalName, $g_iControllerPID, $g_sWorkerRegKey ; =============================================================================================================================== ; --- MAIN SCRIPT LOGIC ; =============================================================================================================================== _Main() ;/** ; * @brief Main entry point of the script. ; * ; * Determines whether to run as the Controller or as a sub-process (Worker/Task) ; * based on the command-line arguments. ; */ Func _Main() If $CmdLine[0] > 0 Then Local $aCmd = StringSplit($CmdLine[1], ":") If $aCmd[0] < 2 Then Exit Local $sType = $aCmd[1], $sInternalName = $aCmd[2], $iControllerPID = ($CmdLine[0] > 1 ? $CmdLine[2] : 0) Switch $sType Case "worker" _RunAsWorker($sInternalName, $iControllerPID) Case "task" _RunAsTask($sInternalName) EndSwitch Else _RunAsController() EndIf EndFunc ;==>_Main ; =============================================================================================================================== ; --- CONTROLLER FUNCTIONS ; =============================================================================================================================== ;/** ; * @brief Initializes the Controller process. ; * ; * This function sets up the entire Controller environment: ; * 1. Cleans up any leftover registry keys from previous runs. ; * 2. Loads the configuration from Configs.ini. ; * 3. Creates the tray menu. ; * 4. Registers Adlib functions for background monitoring. ; * 5. Starts the main event loop. ; */ Func _RunAsController() _CleanupAllRegistryKeys() _LoadConfiguration() _CreateTrayMenu() AdlibRegister("_CheckStatus_Adlib", $g_iAdlibCheckInterval) AdlibRegister("_CheckForConfigChanges_Adlib", $g_iCheckConfigInterval) _WriteLog("INFO: Controller started successfully.") _ControllerMainLoop() EndFunc ;==>_RunAsController ;/** ; * @brief Loads and parses the configuration from Configs.ini. ; * ; * If Configs.ini exists, it reads the settings and the disable lists. ; * If not, it uses the default configuration (all master items enabled). ; * It then populates the dynamic $g_aWorkers and $g_aTasks arrays with only the enabled items. ; * This function is optimized to count items first, then Dim the arrays once to avoid ReDim in a loop. ; */ Func _LoadConfiguration() Local $sDisabledWorkers = "", $sDisabledTasks = "" If FileExists($g_sConfigFile) Then $g_sLastConfigModDate = FileGetTime($g_sConfigFile, $FT_MODIFIED, $FT_STRING) $g_sAppName = IniRead($g_sConfigFile, "Settings", "AppName", "Controller Framework") $g_iCheckConfigInterval = IniRead($g_sConfigFile, "Settings", "CheckConfigInterval", 10000) $sDisabledWorkers = "," & IniRead($g_sConfigFile, "Workers", "Disable", "") & "," $sDisabledTasks = "," & IniRead($g_sConfigFile, "Tasks", "Disable", "") & "," Else $g_sLastConfigModDate = "" $sDisabledWorkers = "," $sDisabledTasks = "," EndIf Local $iWorkerCount = 0, $iTaskCount = 0 For $i = 0 To UBound($g_aMasterWorkers) - 1 If Not StringInStr($sDisabledWorkers, "," & $g_aMasterWorkers[$i][0] & ",") Then $iWorkerCount += 1 Next For $i = 0 To UBound($g_aMasterTasks) - 1 If Not StringInStr($sDisabledTasks, "," & $g_aMasterTasks[$i][0] & ",") Then $iTaskCount += 1 Next $g_iNumWorkers = $iWorkerCount $g_iNumTasks = $iTaskCount Dim $g_aWorkers[$g_iNumWorkers][7] Dim $g_aTasks[$g_iNumTasks][7] Local $iWorkerIdx = 0, $iTaskIdx = 0 For $i = 0 To UBound($g_aMasterWorkers) - 1 Local $sInternalName = $g_aMasterWorkers[$i][0] If StringInStr($sDisabledWorkers, "," & $sInternalName & ",") Then ContinueLoop $g_aWorkers[$iWorkerIdx][0] = $sInternalName $g_aWorkers[$iWorkerIdx][1] = $g_aMasterWorkers[$i][1] $g_aWorkers[$iWorkerIdx][2] = $g_aMasterWorkers[$i][2] $g_aWorkers[$iWorkerIdx][3] = 0 $g_aWorkers[$iWorkerIdx][4] = False $g_aWorkers[$iWorkerIdx][5] = $STATUS_STOPPED $g_aWorkers[$iWorkerIdx][6] = $g_aMasterWorkers[$i][3] $iWorkerIdx += 1 Next For $i = 0 To UBound($g_aMasterTasks) - 1 Local $sInternalName = $g_aMasterTasks[$i][0] If StringInStr($sDisabledTasks, "," & $sInternalName & ",") Then ContinueLoop $g_aTasks[$iTaskIdx][0] = $sInternalName $g_aTasks[$iTaskIdx][1] = $g_aMasterTasks[$i][1] $g_aTasks[$iTaskIdx][2] = $g_aMasterTasks[$i][2] $g_aTasks[$iTaskIdx][3] = 0 $g_aTasks[$iTaskIdx][4] = $g_aMasterTasks[$i][4] $g_aTasks[$iTaskIdx][5] = $g_aMasterTasks[$i][5] $g_aTasks[$iTaskIdx][6] = $g_aMasterTasks[$i][3] $iTaskIdx += 1 Next EndFunc ;==>_LoadConfiguration ;/** ; * @brief Creates the entire tray menu structure based on the loaded configuration. ; * ; * It iterates through the dynamic $g_aWorkers and $g_aTasks arrays and creates ; * main menu items or sub-menu items based on their MenuType property. ; * This ensures that the menu item index directly corresponds to the data array index. ; */ Func _CreateTrayMenu() Opt("TrayMenuMode", 3) TraySetToolTip($g_sAppName) ReDim $g_aWorkerTrayItems[$g_iNumWorkers] ReDim $g_aTaskTrayItems[$g_iNumTasks] Local $hSubWorkersMenu = 0, $hSubTasksMenu = 0 For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][6] = $MENU_TYPE_MAIN Then $g_aWorkerTrayItems[$i] = TrayCreateItem("[OFF] " & $g_aWorkers[$i][1]) Else If $hSubWorkersMenu = 0 Then $hSubWorkersMenu = TrayCreateMenu("Sub Workers") $g_aWorkerTrayItems[$i] = TrayCreateItem("[OFF] " & $g_aWorkers[$i][1], $hSubWorkersMenu) EndIf Next TrayCreateItem("") For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][6] = $MENU_TYPE_MAIN Then $g_aTaskTrayItems[$i] = TrayCreateItem($g_aTasks[$i][1]) Else If $hSubTasksMenu = 0 Then $hSubTasksMenu = TrayCreateMenu("Sub Tasks") $g_aTaskTrayItems[$i] = TrayCreateItem($g_aTasks[$i][1], $hSubTasksMenu) EndIf Next TrayCreateItem("") $g_iTrayExit = TrayCreateItem("Exit") TraySetState(1) EndFunc ;==>_CreateTrayMenu ;/** ; * @brief The main event loop for the Controller. ; * ; * It waits for and dispatches tray menu click events to the appropriate handler functions. ; */ Func _ControllerMainLoop() While 1 Local $iTrayMsg = TrayGetMsg() Switch $iTrayMsg Case 0 Case $g_iTrayExit _ExitController() Case Else Local $iIndex = _GetIndexFromTrayID($iTrayMsg, $g_aWorkerTrayItems) If $iIndex <> -1 Then _HandleWorkerClick($iIndex) ContinueLoop EndIf $iIndex = _GetIndexFromTrayID($iTrayMsg, $g_aTaskTrayItems) If $iIndex <> -1 Then _HandleTaskClick($iIndex) ContinueLoop EndIf EndSwitch Sleep(100) WEnd EndFunc ;==>_ControllerMainLoop ;/** ; * @brief Handles a click on a Worker menu item. ; * @param $iIndex The index of the clicked worker in the $g_aWorkers array. ; */ Func _HandleWorkerClick($iIndex) _UpdateWorkerState($iIndex, Not $g_aWorkers[$iIndex][4]) EndFunc ;==>_HandleWorkerClick ;/** ; * @brief Handles a click on a Task menu item. ; * @param $iIndex The index of the clicked task in the $g_aTasks array. ; */ Func _HandleTaskClick($iIndex) _RunTask($iIndex) EndFunc ;==>_HandleTaskClick ;/** ; * @brief Central function to change a worker's state (on/off). ; * @param $iIndex The index of the worker. ; * @param $bNewState The new state to apply (True for ON, False for OFF). ; */ Func _UpdateWorkerState($iIndex, $bNewState) $g_aWorkers[$iIndex][4] = $bNewState If $bNewState Then _StartWorker($iIndex) Else _StopWorker($iIndex) EndIf _UpdateTrayItemForWorker($iIndex) EndFunc ;==>_UpdateWorkerState ;/** ; * @brief Updates a worker's tray menu item text and checked state. ; * @param $iIndex The index of the worker. ; */ Func _UpdateTrayItemForWorker($iIndex) Local $sPrefix, $iTrayState If $g_aWorkers[$iIndex][4] Then $sPrefix = "[ON] " $iTrayState = $TRAY_CHECKED Else $sPrefix = "[OFF] " $iTrayState = $TRAY_UNCHECKED EndIf TrayItemSetText($g_aWorkerTrayItems[$iIndex], $sPrefix & $g_aWorkers[$iIndex][1]) TrayItemSetState($g_aWorkerTrayItems[$iIndex], $iTrayState) EndFunc ;==>_UpdateTrayItemForWorker ;/** ; * @brief Starts a worker sub-process. ; * @param $iIndex The index of the worker. ; */ Func _StartWorker($iIndex) If ProcessExists($g_aWorkers[$iIndex][3]) Then Return Local $sCommand = 'worker:' & $g_aWorkers[$iIndex][0] _WriteLog("INFO: Starting Worker '" & $g_aWorkers[$iIndex][1] & "'...") Local $iPID = _RunScript($sCommand) If $iPID > 0 Then $g_aWorkers[$iIndex][3] = $iPID $g_aWorkers[$iIndex][5] = $STATUS_RUNNING Else _WriteLog("ERROR: Failed to start Worker '" & $g_aWorkers[$iIndex][1] & "'.") $g_aWorkers[$iIndex][4] = False $g_aWorkers[$iIndex][5] = $STATUS_ERROR EndIf EndFunc ;==>_StartWorker ;/** ; * @brief Stops a worker sub-process gracefully. ; * @param $iIndex The index of the worker. ; */ Func _StopWorker($iIndex) Local $iPID = $g_aWorkers[$iIndex][3] If $iPID = 0 Or Not ProcessExists($iPID) Then $g_aWorkers[$iIndex][3] = 0 $g_aWorkers[$iIndex][5] = $STATUS_STOPPED Return EndIf _WriteLog("INFO: Stopping Worker '" & $g_aWorkers[$iIndex][1] & "' (PID: " & $iPID & ")...") Local $sRegKey = $g_sRegBase & "\" & $g_aWorkers[$iIndex][0] RegWrite($sRegKey, "Signal", "REG_SZ", "exit") If @error Then _WriteLog("ERROR: Failed to write exit signal to registry for " & $g_aWorkers[$iIndex][0]) Local $iResult = ProcessWaitClose($iPID, $g_iGracefulShutdownTimeout / 1000) If $iResult = 0 Then _WriteLog("WARN: Worker PID " & $iPID & " did not respond. Forcing shutdown.") ProcessClose($iPID) EndIf RegDelete($sRegKey) $g_aWorkers[$iIndex][3] = 0 $g_aWorkers[$iIndex][5] = $STATUS_STOPPED EndFunc ;==>_StopWorker ;/** ; * @brief Runs a task either directly or as a sub-process. ; * @param $iIndex The index of the task. ; */ Func _RunTask($iIndex) Local $sTaskName = $g_aTasks[$iIndex][1] _WriteLog("INFO: Running Task '" & $sTaskName & "'...") If $g_aTasks[$iIndex][5] = $TASK_RUN_TYPE_DIRECT Then TrayItemSetState($g_aTaskTrayItems[$iIndex], $TRAY_DISABLE) Call($g_aTasks[$iIndex][2], $sTaskName) TrayItemSetState($g_aTaskTrayItems[$iIndex], $TRAY_ENABLE) _WriteLog("INFO: Direct Task '" & $sTaskName & "' has completed.") Else If $g_aTasks[$iIndex][3] <> 0 And ProcessExists($g_aTasks[$iIndex][3]) Then Return Local $sCommand = 'task:' & $g_aTasks[$iIndex][0] Local $iPID = _RunScript($sCommand, False) If $iPID > 0 Then $g_aTasks[$iIndex][3] = $iPID _UpdateAllTaskMenusState() Else _WriteLog("ERROR: Failed to run Task '" & $sTaskName & "'.") EndIf EndIf EndFunc ;==>_RunTask ;/** ; * @brief Adlib function to monitor the status of running workers and tasks. ; */ Func _CheckStatus_Adlib() For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][4] And Not ProcessExists($g_aWorkers[$i][3]) Then _WriteLog("WARN: Worker '" & $g_aWorkers[$i][1] & "' died. Restarting...") $g_aWorkers[$i][5] = $STATUS_ERROR _UpdateTrayItemForWorker($i) _StartWorker($i) EndIf Next Local $bTaskStateChanged = False For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][5] = $TASK_RUN_TYPE_SUBAPP And $g_aTasks[$i][3] > 0 And Not ProcessExists($g_aTasks[$i][3]) Then $g_aTasks[$i][3] = 0 $bTaskStateChanged = True EndIf Next If $bTaskStateChanged Then _UpdateAllTaskMenusState() EndFunc ;==>_CheckStatus_Adlib ;/** ; * @brief Checks if any exclusive task is currently running as a sub-process. ; * @return True if an exclusive task is running, otherwise False. ; */ Func _IsExclusiveTaskRunning() For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][4] = $TASK_MODE_EXCLUSIVE And $g_aTasks[$i][3] > 0 And ProcessExists($g_aTasks[$i][3]) Then Return True EndIf Next Return False EndFunc ;==>_IsExclusiveTaskRunning ;/** ; * @brief Updates the enabled/disabled state of all task menu items based on current activity. ; */ Func _UpdateAllTaskMenusState() Local $bExclusiveRunning = _IsExclusiveTaskRunning() For $i = 0 To $g_iNumTasks - 1 If $bExclusiveRunning Then TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_DISABLE) Else If $g_aTasks[$i][3] > 0 And ProcessExists($g_aTasks[$i][3]) Then TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_DISABLE) Else TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_ENABLE) EndIf EndIf Next EndFunc ;==>_UpdateAllTaskMenusState ;/** ; * @brief Adlib function to check for modifications to the Configs.ini file. ; */ Func _CheckForConfigChanges_Adlib() Local $iCurrentModDate = FileGetTime($g_sConfigFile, $FT_MODIFIED, $FT_STRING) If $iCurrentModDate <> $g_sLastConfigModDate Then _WriteLog("INFO: Configuration file change detected. Reloading...") _UnregisterAllMasterAdlibs() _StopAllWorkers() TraySetState(2) _LoadConfiguration() _CreateTrayMenu() _WriteLog("INFO: Configuration reloaded and menu recreated.") EndIf EndFunc ;==>_CheckForConfigChanges_Adlib ;/** ; * @brief Stops all currently active workers. ; */ Func _StopAllWorkers() For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][4] Then _StopWorker($i) Next EndFunc ;==>_StopAllWorkers ;/** ; * @brief A generic function to run the script as a sub-process. ; * @param $sArgument The argument to pass to the new process (e.g., "worker:MyWorker"). ; * @param $bPassControllerPID If True, the current controller's PID is passed as the second argument. ; * @return The PID of the new process, or 0 on failure. ; */ Func _RunScript($sArgument, $bPassControllerPID = True) Local $sControllerPID = ($bPassControllerPID ? " " & @AutoItPID : "") Local $sCommand = '"' & @ScriptFullPath & '" "' & $sArgument & '"' & $sControllerPID Local $sExecutable = (@Compiled ? "" : '"' & @AutoItExe & '" ') Return Run($sExecutable & $sCommand, @ScriptDir, @SW_HIDE) EndFunc ;==>_RunScript ;/** ; * @brief Performs all necessary cleanup before the Controller exits. ; */ Func _ExitController() _WriteLog("INFO: Controller shutting down...") _UnregisterAllMasterAdlibs() _CleanupAllRegistryKeys() _StopAllWorkers() Exit EndFunc ;==>_ExitController ;/** ; * @brief Unregisters all possible Adlib callbacks defined in the master lists. ; * ; * This is a crucial cleanup step to prevent orphaned callbacks, especially when reloading the configuration. ; */ Func _UnregisterAllMasterAdlibs() AdlibUnRegister("_CheckStatus_Adlib") AdlibUnRegister("_CheckForConfigChanges_Adlib") For $i = 0 To UBound($g_aMasterWorkers) - 1 AdlibUnRegister($g_aMasterWorkers[$i][2]) Next EndFunc ;==>_UnregisterAllMasterAdlibs ;/** ; * @brief Deletes the entire registry key used by the application for IPC. ; */ Func _CleanupAllRegistryKeys() RegDelete($g_sRegBase) If @error Then _WriteLog("ERROR: Failed to delete base registry key: " & $g_sRegBase) EndFunc ;==>_CleanupAllRegistryKeys ;/** ; * @brief Finds the array index corresponding to a given tray item ID. ; * @param $nID The tray item ID to find. ; * @param $aTrayItems The array of tray item IDs to search in. ; * @return The array index if found, otherwise -1. ; */ Func _GetIndexFromTrayID($nID, ByRef $aTrayItems) For $i = 0 To UBound($aTrayItems) - 1 If $aTrayItems[$i] = $nID Then Return $i Next Return -1 EndFunc ;==>_GetIndexFromTrayID ;/** ; * @brief Writes a message to both the console and the log file. ; * ; * This function provides a synchronized way to log events. Opening and closing the file ; * for each write minimizes the chance of file corruption from multiple processes. ; * @param $sMessage The message to log. ; */ Func _WriteLog($sMessage) Local $sTimeStamp = @YEAR & "/" & @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC Local $sFormattedMessage = $sTimeStamp & " - " & $sMessage & @CRLF ConsoleWrite($sFormattedMessage) Local $hFile = FileOpen($g_sLogFile, 2 + 8) ; Open in Write + Append mode to lock the file during write If $hFile = -1 Then Return FileWrite($hFile, $sFormattedMessage) FileClose($hFile) EndFunc ;==>_WriteLog ; =============================================================================================================================== ; --- WORKER / TASK SUB-PROCESS EXECUTION ; =============================================================================================================================== ;/** ; * @brief Initializes the script when run as a Worker sub-process. ; * @param $sInternalName The internal name of the worker to run. ; * @param $iControllerPID The PID of the parent Controller process to monitor. ; */ Func _RunAsWorker($sInternalName, $iControllerPID) $g_sWorkerInternalName = $sInternalName $g_iControllerPID = $iControllerPID $g_sWorkerRegKey = $g_sRegBase & "\" & $sInternalName RegDelete($g_sWorkerRegKey) Local $sFunction = "" For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][0] = $sInternalName Then $sFunction = $g_aMasterWorkers[$i][2] ExitLoop EndIf Next If $sFunction = "" Then Exit AdlibRegister($sFunction, 1000) AdlibRegister("_WorkerHeartbeat_Adlib", 2000) While 1 Sleep(100) WEnd EndFunc ;==>_RunAsWorker ;/** ; * @brief Adlib function for workers to monitor the Controller and exit signals. ; */ Func _WorkerHeartbeat_Adlib() If $g_iControllerPID > 0 And Not ProcessExists($g_iControllerPID) Then _WorkerExitGracefully() EndIf RegRead($g_sWorkerRegKey, "Signal") If @error Then _WorkerExitGracefully() Return EndIf If RegRead($g_sWorkerRegKey, "Signal") = "exit" Then _WorkerExitGracefully() EndIf EndFunc ;==>_WorkerHeartbeat_Adlib ;/** ; * @brief Performs all necessary cleanup for a Worker before it exits. ; */ Func _WorkerExitGracefully() AdlibUnRegister("_WorkerHeartbeat_Adlib") Local $sFunction = "" For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][0] = $g_sWorkerInternalName Then $sFunction = $g_aMasterWorkers[$i][2] ExitLoop EndIf Next If $sFunction <> "" Then AdlibUnRegister($sFunction) RegDelete($g_sWorkerRegKey) Exit EndFunc ;==>_WorkerExitGracefully ;/** ; * @brief Initializes the script when run as a Task sub-process. ; * @param $sInternalName The internal name of the task to run. ; */ Func _RunAsTask($sInternalName) Local $sFunction, $sDisplayName For $i = 0 To UBound($g_aMasterTasks) - 1 If $g_aMasterTasks[$i][0] = $sInternalName Then $sDisplayName = $g_aMasterTasks[$i][1] $sFunction = $g_aMasterTasks[$i][2] ExitLoop EndIf Next If $sFunction = "" Then Exit Call($sFunction, $sDisplayName) Exit EndFunc ;==>_RunAsTask ; =============================================================================================================================== ; --- SPECIFIC WORKER/TASK FUNCTIONS (IMPLEMENTATION) ; =============================================================================================================================== Func Worker1Function() _WriteLog("Worker 'Check Resources' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker1Function Func Worker2Function() _WriteLog("Worker 'Sync Files' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker2Function Func Worker3Function() _WriteLog("Worker 'Monitor Network' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker3Function Func Worker4Function() _WriteLog("Worker 'Backup DB' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker4Function Func Worker5Function() _WriteLog("Worker 'Cleanup Logs' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker5Function Func Worker6Function() _WriteLog("Worker 'Process Emails' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker6Function Func Worker7Function() _WriteLog("Worker 'Auto Screenshot' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker7Function Func Worker8Function() _WriteLog("Worker 'Monitor Temp' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker8Function Func Worker9Function() _WriteLog("Worker 'Check Updates' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker9Function Func Worker10Function() _WriteLog("Worker 'Security Scan' is running...") ; This is PlaceHoder - Your Code Repalce Here EndFunc ;==>Worker10Function Func TaskA_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(2000) EndFunc ;==>TaskA_Function Func TaskB_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(3000) EndFunc ;==>TaskB_Function Func TaskC_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskC_Function Func TaskD_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskD_Function Func TaskE_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskE_Function Func TaskF_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskF_Function Func TaskG_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskG_Function Func TaskH_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskH_Function Func TaskI_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1500) EndFunc ;==>TaskI_Function Func TaskJ_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Repalce Here Sleep(1000) EndFunc ;==>TaskJ_Function v1.2 (Performance Optimized, On/Off Declaration, Au3Stripper Compatible): expandcollapse popup; =============================================================================================================================== ; SCRIPT: Controller-Worker-Task Manager Framework ; AUTHOR: Dao Van Trong - TRONG.PRO ; VERSION: 1.2 (Performance Optimized, On/Off Declaration, Au3Stripper Compatible) ; DESCRIPTION: This script implements a robust Controller-Worker-Task architecture for running background processes. ; It is designed to be a flexible framework for automation tasks. ; ; ARCHITECTURE: ; - Controller: The main process that runs persistently. It has no GUI and is controlled entirely via a system tray icon. ; Its responsibilities include: ; 1. Reading the configuration from 'Configs.ini'. ; 2. Creating and managing the tray menu. ; 3. Starting, stopping, and monitoring Worker processes. ; 4. Launching one-shot Task processes. ; 5. Automatically reloading the configuration when 'Configs.ini' is modified. ; ; - Workers: Long-running background processes that perform recurring tasks (e.g., monitoring, syncing). ; They are started and stopped by the Controller and run completely hidden. ; Each worker monitors the Controller and will self-terminate if the Controller exits. ; ; - Tasks: Short-lived background processes that perform a single, one-shot action (e.g., generate a report). ; They can be configured to run exclusively (locking all other tasks) or concurrently. ; They can also be configured to run as a separate sub-process or directly within the Controller's process. ; ; HOW IT WORKS: ; - IPC (Inter-Process Communication): Communication between the Controller and Workers is handled via the Windows Registry. ; The Controller writes a signal to a specific registry key to gracefully shut down a Worker. ; - Asynchronous Operations: All monitoring (process status, config file changes) and Worker functions are handled ; asynchronously using AdlibRegister, ensuring the Controller remains responsive. ; - Configuration: All Workers and Tasks are defined in the master arrays within this script, but they are enabled, ; disabled, and configured via an external 'Configs.ini' file. This allows for easy customization without ; modifying the source code. ; - Logging: The script generates a detailed log file ('manager.log') in the same directory, recording all major ; events like startup, shutdown, worker status changes, and errors. ; ; PERFORMANCE IMPROVEMENTS: ; - Optimized tray menu handling with reduced event processing overhead ; - Faster exit processing with immediate cleanup ; - Reduced Adlib function frequency for better responsiveness ; - Improved memory management and array operations ; ; NEW FEATURES: ; - On/Off declaration for MasterWorkers and MasterTasks ; - Au3Stripper compatibility with proper function protection ; - Enhanced configuration system ; =============================================================================================================================== #include <TrayConstants.au3> #include <Array.au3> #include <File.au3> #include <FileConstants.au3> ; Au3Stripper protection directives #Au3Stripper_DoNotStrip_Func=Worker1Function,Worker2Function,Worker3Function,Worker4Function,Worker5Function,Worker6Function,Worker7Function,Worker8Function,Worker9Function,Worker10Function #Au3Stripper_DoNotStrip_Func=TaskA_Function,TaskB_Function,TaskC_Function,TaskD_Function,TaskE_Function,TaskF_Function,TaskG_Function,TaskH_Function,TaskI_Function,TaskJ_Function #Au3Stripper_DoNotStrip_Func=_CheckStatus_Adlib,_CheckForConfigChanges_Adlib,_WorkerHeartbeat_Adlib #Au3Stripper_DoNotStrip_Var=$g_aMasterWorkers,$g_aMasterTasks,$g_aWorkers,$g_aTasks,$g_aWorkerTrayItems,$g_aTaskTrayItems ; =============================================================================================================================== ; --- GLOBAL CONSTANTS AND VARIABLES ; =============================================================================================================================== ; --- Settings --- Global Const $g_sConfigFile = @ScriptDir & "\Configs.ini" Global Const $g_sLogFile = @ScriptDir & "\Manager.log" Global $g_sAppName = "Controller-Worker-Task Manager" Global $g_iCheckConfigInterval = 15000 ; Increased for better performance Global $g_sLastConfigModDate = "" ; --- Registry & IPC --- Global Const $g_sRegBase = "HKCU\Software\ControllerWorkerApp" Global Const $g_iAdlibCheckInterval = 2000 ; Reduced frequency for better performance Global Const $g_iGracefulShutdownTimeout = 2000 ; Reduced for faster exit ; --- Status Constants for internal logic --- Global Const $STATUS_STOPPED = 0, $STATUS_RUNNING = 1, $STATUS_ERROR = 2 ; --- Task Execution Mode & Type Constants --- Global Const $TASK_MODE_EXCLUSIVE = 0, $TASK_MODE_CONCURRENT = 1 Global Const $TASK_RUN_TYPE_SUBAPP = 0, $TASK_RUN_TYPE_DIRECT = 1 ; --- Menu Type Constants --- Global Const $MENU_TYPE_MAIN = 0, $MENU_TYPE_SUB = 1 ; --- Performance optimization flags --- Global $g_bShuttingDown = False Global $g_bConfigReloading = False ; --- MASTER DATA STRUCTURES (Hard-coded definitions) --- ; [InternalName, DisplayName, FunctionName, MenuType, Status(On/Off)] Global Const $g_aMasterWorkers[10][5] = [ _ ["CheckResources", "Check Resources", "Worker1Function", $MENU_TYPE_MAIN, "On"], _ ["SyncFiles", "Sync Files", "Worker2Function", $MENU_TYPE_MAIN, "On"], _ ["MonitorNetwork", "Monitor Network", "Worker3Function", $MENU_TYPE_MAIN, "On"], _ ["BackupDB", "Backup DB", "Worker4Function", $MENU_TYPE_SUB, "On"], _ ["CleanupLogs", "Cleanup Logs", "Worker5Function", $MENU_TYPE_SUB, "On"], _ ["ProcessEmails", "Process Emails", "Worker6Function", $MENU_TYPE_SUB, "On"], _ ["AutoScreenshot", "Auto Screenshot", "Worker7Function", $MENU_TYPE_SUB, "Off"], _ ["MonitorTemp", "Monitor Temp", "Worker8Function", $MENU_TYPE_SUB, "Off"], _ ["CheckUpdates", "Check Updates", "Worker9Function", $MENU_TYPE_SUB, "On"], _ ["SecurityScan", "Security Scan", "Worker10Function", $MENU_TYPE_SUB, "Off"] _ ] ; [InternalName, DisplayName, FunctionName, MenuType, ExecutionMode, RunType, Status(On/Off)] Global Const $g_aMasterTasks[10][7] = [ _ ["QuickCleanup", "Quick Cleanup", "TaskA_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_SUBAPP, "On"], _ ["GenerateReport", "Generate Report", "TaskB_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_SUBAPP, "On"], _ ["SendNotification", "Send Notification (Direct)", "TaskJ_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_DIRECT, "On"], _ ["TaskC", "Run Task C (Direct)", "TaskC_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_DIRECT, "On"], _ ["TaskD", "Run Task D", "TaskD_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off"], _ ["TaskE", "Run Task E", "TaskE_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off"], _ ["TaskF", "Run Task F", "TaskF_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "On"], _ ["TaskG", "Run Task G", "TaskG_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off"], _ ["TaskH", "Run Task H", "TaskH_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "On"], _ ["TaskI", "Run Task I", "TaskI_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off"] _ ] ; --- Dynamic Data Structures (Populated from Master based on INI) --- Global $g_aWorkers, $g_iNumWorkers Global $g_aTasks, $g_iNumTasks ; --- Tray Menu Control Variables --- Global $g_aWorkerTrayItems[1], $g_aTaskTrayItems[1], $g_iTrayExit ; --- Shared Global Variables for Worker processes --- Global $g_sWorkerInternalName, $g_iControllerPID, $g_sWorkerRegKey ; --- Performance optimization variables --- Global $g_iLastStatusCheck = 0 Global $g_iLastConfigCheck = 0 ; =============================================================================================================================== ; --- MAIN SCRIPT LOGIC ; =============================================================================================================================== _Main() ;/** ; * @brief Main entry point of the script. ; * ; * Determines whether to run as the Controller or as a sub-process (Worker/Task) ; * based on the command-line arguments. ; */ Func _Main() If $CmdLine[0] > 0 Then Local $aCmd = StringSplit($CmdLine[1], ":") If $aCmd[0] < 2 Then Exit Local $sType = $aCmd[1], $sInternalName = $aCmd[2], $iControllerPID = ($CmdLine[0] > 1 ? $CmdLine[2] : 0) Switch $sType Case "worker" _RunAsWorker($sInternalName, $iControllerPID) Case "task" _RunAsTask($sInternalName) EndSwitch Else _RunAsController() EndIf EndFunc ;==>_Main ; =============================================================================================================================== ; --- CONTROLLER FUNCTIONS ; =============================================================================================================================== ;/** ; * @brief Initializes the Controller process. ; * ; * This function sets up the entire Controller environment: ; * 1. Cleans up any leftover registry keys from previous runs. ; * 2. Loads the configuration from Configs.ini. ; * 3. Creates the tray menu. ; * 4. Registers Adlib functions for background monitoring. ; * 5. Starts the main event loop. ; */ Func _RunAsController() ; Performance optimization: Batch initialization _CleanupAllRegistryKeys() _LoadConfiguration() _CreateTrayMenu() ; Optimized Adlib registration with performance flags $g_iLastStatusCheck = TimerInit() $g_iLastConfigCheck = TimerInit() AdlibRegister("_CheckStatus_Adlib", $g_iAdlibCheckInterval) AdlibRegister("_CheckForConfigChanges_Adlib", $g_iCheckConfigInterval) _WriteLog("INFO: Controller started successfully.") _ControllerMainLoop() EndFunc ;==>_RunAsController ;/** ; * @brief Loads and parses the configuration from Configs.ini. ; * ; * Enhanced version with On/Off status checking for master arrays. ; * If Configs.ini exists, it reads the settings and the disable lists. ; * If not, it uses the default configuration (all master items with "On" status enabled). ; * It then populates the dynamic $g_aWorkers and $g_aTasks arrays with only the enabled items. ; */ Func _LoadConfiguration() $g_bConfigReloading = True Local $sDisabledWorkers = "", $sDisabledTasks = "" If FileExists($g_sConfigFile) Then $g_sLastConfigModDate = FileGetTime($g_sConfigFile, $FT_MODIFIED, $FT_STRING) $g_sAppName = IniRead($g_sConfigFile, "Settings", "AppName", "Controller Framework") $g_iCheckConfigInterval = IniRead($g_sConfigFile, "Settings", "CheckConfigInterval", 15000) $sDisabledWorkers = "," & IniRead($g_sConfigFile, "Workers", "Disable", "") & "," $sDisabledTasks = "," & IniRead($g_sConfigFile, "Tasks", "Disable", "") & "," Else $g_sLastConfigModDate = "" $sDisabledWorkers = "," $sDisabledTasks = "," EndIf ; Enhanced filtering: Check both INI disable list and master array On/Off status Local $iWorkerCount = 0, $iTaskCount = 0 For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][4] = "On" And Not StringInStr($sDisabledWorkers, "," & $g_aMasterWorkers[$i][0] & ",") Then $iWorkerCount += 1 EndIf Next For $i = 0 To UBound($g_aMasterTasks) - 1 If $g_aMasterTasks[$i][6] = "On" And Not StringInStr($sDisabledTasks, "," & $g_aMasterTasks[$i][0] & ",") Then $iTaskCount += 1 EndIf Next $g_iNumWorkers = $iWorkerCount $g_iNumTasks = $iTaskCount ; Performance optimization: Only create arrays if needed If $g_iNumWorkers > 0 Then Dim $g_aWorkers[$g_iNumWorkers][7] Local $iWorkerIdx = 0 For $i = 0 To UBound($g_aMasterWorkers) - 1 Local $sInternalName = $g_aMasterWorkers[$i][0] If $g_aMasterWorkers[$i][4] = "Off" Or StringInStr($sDisabledWorkers, "," & $sInternalName & ",") Then ContinueLoop $g_aWorkers[$iWorkerIdx][0] = $sInternalName $g_aWorkers[$iWorkerIdx][1] = $g_aMasterWorkers[$i][1] $g_aWorkers[$iWorkerIdx][2] = $g_aMasterWorkers[$i][2] $g_aWorkers[$iWorkerIdx][3] = 0 $g_aWorkers[$iWorkerIdx][4] = False $g_aWorkers[$iWorkerIdx][5] = $STATUS_STOPPED $g_aWorkers[$iWorkerIdx][6] = $g_aMasterWorkers[$i][3] $iWorkerIdx += 1 Next Else Dim $g_aWorkers[0][0] ; Empty array EndIf If $g_iNumTasks > 0 Then Dim $g_aTasks[$g_iNumTasks][7] Local $iTaskIdx = 0 For $i = 0 To UBound($g_aMasterTasks) - 1 Local $sInternalName = $g_aMasterTasks[$i][0] If $g_aMasterTasks[$i][6] = "Off" Or StringInStr($sDisabledTasks, "," & $sInternalName & ",") Then ContinueLoop $g_aTasks[$iTaskIdx][0] = $sInternalName $g_aTasks[$iTaskIdx][1] = $g_aMasterTasks[$i][1] $g_aTasks[$iTaskIdx][2] = $g_aMasterTasks[$i][2] $g_aTasks[$iTaskIdx][3] = 0 $g_aTasks[$iTaskIdx][4] = $g_aMasterTasks[$i][4] $g_aTasks[$iTaskIdx][5] = $g_aMasterTasks[$i][5] $g_aTasks[$iTaskIdx][6] = $g_aMasterTasks[$i][3] $iTaskIdx += 1 Next Else Dim $g_aTasks[0][0] ; Empty array EndIf $g_bConfigReloading = False EndFunc ;==>_LoadConfiguration ;/** ; * @brief Creates the entire tray menu structure based on the loaded configuration. ; * ; * Enhanced version with better performance and empty array handling. ; */ Func _CreateTrayMenu() Opt("TrayMenuMode", 3) TraySetToolTip($g_sAppName) ; Performance optimization: Only create arrays if needed If $g_iNumWorkers > 0 Then ReDim $g_aWorkerTrayItems[$g_iNumWorkers] Local $hSubWorkersMenu = 0 For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][6] = $MENU_TYPE_MAIN Then $g_aWorkerTrayItems[$i] = TrayCreateItem("[OFF] " & $g_aWorkers[$i][1]) Else If $hSubWorkersMenu = 0 Then $hSubWorkersMenu = TrayCreateMenu("Sub Workers") $g_aWorkerTrayItems[$i] = TrayCreateItem("[OFF] " & $g_aWorkers[$i][1], $hSubWorkersMenu) EndIf Next Else ReDim $g_aWorkerTrayItems[0] EndIf TrayCreateItem("") If $g_iNumTasks > 0 Then ReDim $g_aTaskTrayItems[$g_iNumTasks] Local $hSubTasksMenu = 0 For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][6] = $MENU_TYPE_MAIN Then $g_aTaskTrayItems[$i] = TrayCreateItem($g_aTasks[$i][1]) Else If $hSubTasksMenu = 0 Then $hSubTasksMenu = TrayCreateMenu("Sub Tasks") $g_aTaskTrayItems[$i] = TrayCreateItem($g_aTasks[$i][1], $hSubTasksMenu) EndIf Next Else ReDim $g_aTaskTrayItems[0] EndIf TrayCreateItem("") $g_iTrayExit = TrayCreateItem("Exit") TraySetState(1) EndFunc ;==>_CreateTrayMenu ;/** ; * @brief The main event loop for the Controller. ; * ; * Enhanced with performance optimizations and faster exit handling. ; */ Func _ControllerMainLoop() While Not $g_bShuttingDown Local $iTrayMsg = TrayGetMsg() Switch $iTrayMsg Case 0 ; No message - continue Case $g_iTrayExit ; Immediate exit handling for better responsiveness $g_bShuttingDown = True _ExitController() Case Else ; Performance optimization: Early exit if reloading config If $g_bConfigReloading Then ContinueLoop Local $iIndex = _GetIndexFromTrayID($iTrayMsg, $g_aWorkerTrayItems) If $iIndex <> -1 Then _HandleWorkerClick($iIndex) ContinueLoop EndIf $iIndex = _GetIndexFromTrayID($iTrayMsg, $g_aTaskTrayItems) If $iIndex <> -1 Then _HandleTaskClick($iIndex) ContinueLoop EndIf EndSwitch Sleep(50) ; Reduced sleep for better responsiveness WEnd EndFunc ;==>_ControllerMainLoop ;/** ; * @brief Handles a click on a Worker menu item. ; * @param $iIndex The index of the clicked worker in the $g_aWorkers array. ; */ Func _HandleWorkerClick($iIndex) If $g_bShuttingDown Or $g_bConfigReloading Then Return _UpdateWorkerState($iIndex, Not $g_aWorkers[$iIndex][4]) EndFunc ;==>_HandleWorkerClick ;/** ; * @brief Handles a click on a Task menu item. ; * @param $iIndex The index of the clicked task in the $g_aTasks array. ; */ Func _HandleTaskClick($iIndex) If $g_bShuttingDown Or $g_bConfigReloading Then Return _RunTask($iIndex) EndFunc ;==>_HandleTaskClick ;/** ; * @brief Central function to change a worker's state (on/off). ; * @param $iIndex The index of the worker. ; * @param $bNewState The new state to apply (True for ON, False for OFF). ; */ Func _UpdateWorkerState($iIndex, $bNewState) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return $g_aWorkers[$iIndex][4] = $bNewState If $bNewState Then _StartWorker($iIndex) Else _StopWorker($iIndex) EndIf _UpdateTrayItemForWorker($iIndex) EndFunc ;==>_UpdateWorkerState ;/** ; * @brief Updates a worker's tray menu item text and checked state. ; * @param $iIndex The index of the worker. ; */ Func _UpdateTrayItemForWorker($iIndex) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return Local $sPrefix, $iTrayState If $g_aWorkers[$iIndex][4] Then $sPrefix = "[ON] " $iTrayState = $TRAY_CHECKED Else $sPrefix = "[OFF] " $iTrayState = $TRAY_UNCHECKED EndIf TrayItemSetText($g_aWorkerTrayItems[$iIndex], $sPrefix & $g_aWorkers[$iIndex][1]) TrayItemSetState($g_aWorkerTrayItems[$iIndex], $iTrayState) EndFunc ;==>_UpdateTrayItemForWorker ;/** ; * @brief Starts a worker sub-process. ; * @param $iIndex The index of the worker. ; */ Func _StartWorker($iIndex) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return If ProcessExists($g_aWorkers[$iIndex][3]) Then Return Local $sCommand = 'worker:' & $g_aWorkers[$iIndex][0] _WriteLog("INFO: Starting Worker '" & $g_aWorkers[$iIndex][1] & "'...") Local $iPID = _RunScript($sCommand) If $iPID > 0 Then $g_aWorkers[$iIndex][3] = $iPID $g_aWorkers[$iIndex][5] = $STATUS_RUNNING Else _WriteLog("ERROR: Failed to start Worker '" & $g_aWorkers[$iIndex][1] & "'.") $g_aWorkers[$iIndex][4] = False $g_aWorkers[$iIndex][5] = $STATUS_ERROR EndIf EndFunc ;==>_StartWorker ;/** ; * @brief Stops a worker sub-process gracefully. ; * @param $iIndex The index of the worker. ; */ Func _StopWorker($iIndex) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return Local $iPID = $g_aWorkers[$iIndex][3] If $iPID = 0 Or Not ProcessExists($iPID) Then $g_aWorkers[$iIndex][3] = 0 $g_aWorkers[$iIndex][5] = $STATUS_STOPPED Return EndIf _WriteLog("INFO: Stopping Worker '" & $g_aWorkers[$iIndex][1] & "' (PID: " & $iPID & ")...") Local $sRegKey = $g_sRegBase & "\" & $g_aWorkers[$iIndex][0] RegWrite($sRegKey, "Signal", "REG_SZ", "exit") If @error Then _WriteLog("ERROR: Failed to write exit signal to registry for " & $g_aWorkers[$iIndex][0]) Local $iResult = ProcessWaitClose($iPID, $g_iGracefulShutdownTimeout / 1000) If $iResult = 0 Then _WriteLog("WARN: Worker PID " & $iPID & " did not respond. Forcing shutdown.") ProcessClose($iPID) EndIf RegDelete($sRegKey) $g_aWorkers[$iIndex][3] = 0 $g_aWorkers[$iIndex][5] = $STATUS_STOPPED EndFunc ;==>_StopWorker ;/** ; * @brief Runs a task either directly or as a sub-process. ; * @param $iIndex The index of the task. ; */ Func _RunTask($iIndex) If $g_iNumTasks = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumTasks Then Return Local $sTaskName = $g_aTasks[$iIndex][1] _WriteLog("INFO: Running Task '" & $sTaskName & "'...") If $g_aTasks[$iIndex][5] = $TASK_RUN_TYPE_DIRECT Then TrayItemSetState($g_aTaskTrayItems[$iIndex], $TRAY_DISABLE) Call($g_aTasks[$iIndex][2], $sTaskName) TrayItemSetState($g_aTaskTrayItems[$iIndex], $TRAY_ENABLE) _WriteLog("INFO: Direct Task '" & $sTaskName & "' has completed.") Else If $g_aTasks[$iIndex][3] <> 0 And ProcessExists($g_aTasks[$iIndex][3]) Then Return Local $sCommand = 'task:' & $g_aTasks[$iIndex][0] Local $iPID = _RunScript($sCommand, False) If $iPID > 0 Then $g_aTasks[$iIndex][3] = $iPID _UpdateAllTaskMenusState() Else _WriteLog("ERROR: Failed to run Task '" & $sTaskName & "'.") EndIf EndIf EndFunc ;==>_RunTask ;/** ; * @brief Optimized Adlib function to monitor the status of running workers and tasks. ; */ Func _CheckStatus_Adlib() ; Performance optimization: Skip if shutting down or reloading If $g_bShuttingDown Or $g_bConfigReloading Then Return ; Rate limiting for better performance If TimerDiff($g_iLastStatusCheck) < $g_iAdlibCheckInterval Then Return $g_iLastStatusCheck = TimerInit() ; Check workers only if we have any If $g_iNumWorkers > 0 Then For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][4] And Not ProcessExists($g_aWorkers[$i][3]) Then _WriteLog("WARN: Worker '" & $g_aWorkers[$i][1] & "' died. Restarting...") $g_aWorkers[$i][5] = $STATUS_ERROR _UpdateTrayItemForWorker($i) _StartWorker($i) EndIf Next EndIf ; Check tasks only if we have any If $g_iNumTasks > 0 Then Local $bTaskStateChanged = False For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][5] = $TASK_RUN_TYPE_SUBAPP And $g_aTasks[$i][3] > 0 And Not ProcessExists($g_aTasks[$i][3]) Then $g_aTasks[$i][3] = 0 $bTaskStateChanged = True EndIf Next If $bTaskStateChanged Then _UpdateAllTaskMenusState() EndIf EndFunc ;==>_CheckStatus_Adlib ;/** ; * @brief Checks if any exclusive task is currently running as a sub-process. ; * @return True if an exclusive task is running, otherwise False. ; */ Func _IsExclusiveTaskRunning() If $g_iNumTasks = 0 Then Return False For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][4] = $TASK_MODE_EXCLUSIVE And $g_aTasks[$i][3] > 0 And ProcessExists($g_aTasks[$i][3]) Then Return True EndIf Next Return False EndFunc ;==>_IsExclusiveTaskRunning ;/** ; * @brief Updates the enabled/disabled state of all task menu items based on current activity. ; */ Func _UpdateAllTaskMenusState() If $g_iNumTasks = 0 Then Return Local $bExclusiveRunning = _IsExclusiveTaskRunning() For $i = 0 To $g_iNumTasks - 1 If $bExclusiveRunning Then TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_DISABLE) Else If $g_aTasks[$i][3] > 0 And ProcessExists($g_aTasks[$i][3]) Then TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_DISABLE) Else TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_ENABLE) EndIf EndIf Next EndFunc ;==>_UpdateAllTaskMenusState ;/** ; * @brief Optimized Adlib function to check for modifications to the Configs.ini file. ; */ Func _CheckForConfigChanges_Adlib() ; Performance optimization: Skip if shutting down If $g_bShuttingDown Then Return ; Rate limiting for better performance If TimerDiff($g_iLastConfigCheck) < $g_iCheckConfigInterval Then Return $g_iLastConfigCheck = TimerInit() Local $sCurrentModDate = FileGetTime($g_sConfigFile, $FT_MODIFIED, $FT_STRING) If $sCurrentModDate <> $g_sLastConfigModDate And $sCurrentModDate <> "" Then _WriteLog("INFO: Configuration file change detected. Reloading...") $g_bConfigReloading = True _UnregisterAllMasterAdlibs() _StopAllWorkers() TraySetState(2) _LoadConfiguration() _CreateTrayMenu() ; Re-register Adlib functions after config reload AdlibRegister("_CheckStatus_Adlib", $g_iAdlibCheckInterval) AdlibRegister("_CheckForConfigChanges_Adlib", $g_iCheckConfigInterval) _WriteLog("INFO: Configuration reloaded and menu recreated.") EndIf EndFunc ;==>_CheckForConfigChanges_Adlib ;/** ; * @brief Stops all currently active workers with performance optimization. ; */ Func _StopAllWorkers() If $g_iNumWorkers = 0 Then Return _WriteLog("INFO: Stopping all workers...") ; Performance optimization: Parallel shutdown approach Local $aWorkerPIDs[$g_iNumWorkers] Local $aWorkerRegKeys[$g_iNumWorkers] ; First pass: Send exit signals to all workers For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][4] And $g_aWorkers[$i][3] > 0 And ProcessExists($g_aWorkers[$i][3]) Then $aWorkerPIDs[$i] = $g_aWorkers[$i][3] $aWorkerRegKeys[$i] = $g_sRegBase & "\" & $g_aWorkers[$i][0] RegWrite($aWorkerRegKeys[$i], "Signal", "REG_SZ", "exit") If @error Then _WriteLog("ERROR: Failed to write exit signal for " & $g_aWorkers[$i][0]) Else $aWorkerPIDs[$i] = 0 EndIf Next ; Wait for graceful shutdown Sleep(500) ; Second pass: Force close any remaining processes For $i = 0 To $g_iNumWorkers - 1 If $aWorkerPIDs[$i] > 0 Then If ProcessExists($aWorkerPIDs[$i]) Then _WriteLog("WARN: Force closing Worker PID " & $aWorkerPIDs[$i]) ProcessClose($aWorkerPIDs[$i]) EndIf RegDelete($aWorkerRegKeys[$i]) $g_aWorkers[$i][3] = 0 $g_aWorkers[$i][4] = False $g_aWorkers[$i][5] = $STATUS_STOPPED EndIf Next EndFunc ;==>_StopAllWorkers ;/** ; * @brief A generic function to run the script as a sub-process. ; * @param $sArgument The argument to pass to the new process (e.g., "worker:MyWorker"). ; * @param $bPassControllerPID If True, the current controller's PID is passed as the second argument. ; * @return The PID of the new process, or 0 on failure. ; */ Func _RunScript($sArgument, $bPassControllerPID = True) Local $sControllerPID = ($bPassControllerPID ? " " & @AutoItPID : "") Local $sCommand = '"' & @ScriptFullPath & '" "' & $sArgument & '"' & $sControllerPID Local $sExecutable = (@Compiled ? "" : '"' & @AutoItExe & '" ') Return Run($sExecutable & $sCommand, @ScriptDir, @SW_HIDE) EndFunc ;==>_RunScript ;/** ; * @brief Performs all necessary cleanup before the Controller exits. ; * Enhanced with faster shutdown process. ; */ Func _ExitController() $g_bShuttingDown = True _WriteLog("INFO: Controller shutting down...") ; Immediate cleanup for faster exit _UnregisterAllMasterAdlibs() _StopAllWorkers() _CleanupAllRegistryKeys() _WriteLog("INFO: Controller shutdown complete.") Exit EndFunc ;==>_ExitController ;/** ; * @brief Enhanced function to unregister all possible Adlib callbacks. ; * Now includes protection for Au3Stripper and handles empty arrays. ; */ Func _UnregisterAllMasterAdlibs() AdlibUnRegister("_CheckStatus_Adlib") AdlibUnRegister("_CheckForConfigChanges_Adlib") ; Unregister all master worker functions (protected from Au3Stripper) For $i = 0 To UBound($g_aMasterWorkers) - 1 AdlibUnRegister($g_aMasterWorkers[$i][2]) Next EndFunc ;==>_UnregisterAllMasterAdlibs ;/** ; * @brief Deletes the entire registry key used by the application for IPC. ; */ Func _CleanupAllRegistryKeys() RegDelete($g_sRegBase) If @error Then _WriteLog("WARN: Registry key cleanup - base key may not exist: " & $g_sRegBase) EndFunc ;==>_CleanupAllRegistryKeys ;/** ; * @brief Enhanced function to find array index with bounds checking. ; * @param $nID The tray item ID to find. ; * @param $aTrayItems The array of tray item IDs to search in. ; * @return The array index if found, otherwise -1. ; */ Func _GetIndexFromTrayID($nID, ByRef $aTrayItems) Local $iUBound = UBound($aTrayItems) If $iUBound = 0 Then Return -1 For $i = 0 To $iUBound - 1 If $aTrayItems[$i] = $nID Then Return $i Next Return -1 EndFunc ;==>_GetIndexFromTrayID ;/** ; * @brief Enhanced logging function with better performance and error handling. ; * @param $sMessage The message to log. ; */ Func _WriteLog($sMessage) Local $sTimeStamp = @YEAR & "/" & @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC Local $sFormattedMessage = $sTimeStamp & " - " & $sMessage & @CRLF ConsoleWrite($sFormattedMessage) ; Performance optimization: Non-blocking file write Local $hFile = FileOpen($g_sLogFile, $FO_APPEND + $FO_UTF8_NOBOM) If $hFile <> -1 Then FileWrite($hFile, $sFormattedMessage) FileClose($hFile) EndIf EndFunc ;==>_WriteLog ; =============================================================================================================================== ; --- WORKER / TASK SUB-PROCESS EXECUTION ; =============================================================================================================================== ;/** ; * @brief Initializes the script when run as a Worker sub-process. ; * @param $sInternalName The internal name of the worker to run. ; * @param $iControllerPID The PID of the parent Controller process to monitor. ; */ Func _RunAsWorker($sInternalName, $iControllerPID) $g_sWorkerInternalName = $sInternalName $g_iControllerPID = $iControllerPID $g_sWorkerRegKey = $g_sRegBase & "\" & $sInternalName RegDelete($g_sWorkerRegKey) Local $sFunction = "" For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][0] = $sInternalName Then $sFunction = $g_aMasterWorkers[$i][2] ExitLoop EndIf Next If $sFunction = "" Then Exit ; Performance optimization: Longer intervals for worker processes AdlibRegister($sFunction, 1000) AdlibRegister("_WorkerHeartbeat_Adlib", 3000) ; Reduced frequency While 1 Sleep(200) ; Slightly longer sleep for worker processes WEnd EndFunc ;==>_RunAsWorker ;/** ; * @brief Enhanced Adlib function for workers to monitor the Controller and exit signals. ; */ Func _WorkerHeartbeat_Adlib() ; Check if controller is still running If $g_iControllerPID > 0 And Not ProcessExists($g_iControllerPID) Then _WorkerExitGracefully() Return EndIf ; Check for exit signal from registry Local $sSignal = RegRead($g_sWorkerRegKey, "Signal") If @error Then _WorkerExitGracefully() Return EndIf If $sSignal = "exit" Then _WorkerExitGracefully() EndIf EndFunc ;==>_WorkerHeartbeat_Adlib ;/** ; * @brief Enhanced cleanup function for Workers before exit. ; */ Func _WorkerExitGracefully() AdlibUnRegister("_WorkerHeartbeat_Adlib") ; Find and unregister the specific worker function Local $sFunction = "" For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][0] = $g_sWorkerInternalName Then $sFunction = $g_aMasterWorkers[$i][2] ExitLoop EndIf Next If $sFunction <> "" Then AdlibUnRegister($sFunction) RegDelete($g_sWorkerRegKey) Exit EndFunc ;==>_WorkerExitGracefully ;/** ; * @brief Initializes the script when run as a Task sub-process. ; * @param $sInternalName The internal name of the task to run. ; */ Func _RunAsTask($sInternalName) Local $sFunction, $sDisplayName For $i = 0 To UBound($g_aMasterTasks) - 1 If $g_aMasterTasks[$i][0] = $sInternalName Then $sDisplayName = $g_aMasterTasks[$i][1] $sFunction = $g_aMasterTasks[$i][2] ExitLoop EndIf Next If $sFunction = "" Then Exit Call($sFunction, $sDisplayName) Exit EndFunc ;==>_RunAsTask ; =============================================================================================================================== ; --- SPECIFIC WORKER/TASK FUNCTIONS (IMPLEMENTATION) ; --- These functions are protected by Au3Stripper directives ; =============================================================================================================================== Func Worker1Function() _WriteLog("Worker 'Check Resources' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker1Function Func Worker2Function() _WriteLog("Worker 'Sync Files' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker2Function Func Worker3Function() _WriteLog("Worker 'Monitor Network' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker3Function Func Worker4Function() _WriteLog("Worker 'Backup DB' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker4Function Func Worker5Function() _WriteLog("Worker 'Cleanup Logs' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker5Function Func Worker6Function() _WriteLog("Worker 'Process Emails' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker6Function Func Worker7Function() _WriteLog("Worker 'Auto Screenshot' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker7Function Func Worker8Function() _WriteLog("Worker 'Monitor Temp' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker8Function Func Worker9Function() _WriteLog("Worker 'Check Updates' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker9Function Func Worker10Function() _WriteLog("Worker 'Security Scan' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker10Function Func TaskA_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(2000) EndFunc ;==>TaskA_Function Func TaskB_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(3000) EndFunc ;==>TaskB_Function Func TaskC_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>TaskC_Function Func TaskD_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskD_Function Func TaskE_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskE_Function Func TaskF_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskF_Function Func TaskG_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskG_Function Func TaskH_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskH_Function Func TaskI_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskI_Function Func TaskJ_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskJ_Function Edited August 15 by Trong ioa747, Danyfirex and WildByDesign 2 1 Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal
Trong Posted August 15 Author Posted August 15 (edited) v2: Added AutoStart functionality expandcollapse popup; =============================================================================================================================== ; SCRIPT: Controller-Worker-Task Manager Framework ; AUTHOR: Dao Van Trong - TRONG.PRO ; VERSION: 2.0 (Added AutoStart functionality) ; DESCRIPTION: This script implements a robust Controller-Worker-Task architecture for running background processes. ; It is designed to be a flexible framework for automation tasks. ; ; ARCHITECTURE: ; - Controller: The main process that runs persistently. It has no GUI and is controlled entirely via a system tray icon. ; Its responsibilities include: ; 1. Reading the configuration from 'Configs.ini'. ; 2. Creating and managing the tray menu. ; 3. Starting, stopping, and monitoring Worker processes. ; 4. Launching one-shot Task processes. ; 5. Automatically reloading the configuration when 'Configs.ini' is modified. ; 6. Auto-starting specified Workers and Tasks on launch. ; ; - Workers: Long-running background processes that perform recurring tasks (e.g., monitoring, syncing). ; They are started and stopped by the Controller and run completely hidden. ; Each worker monitors the Controller and will self-terminate if the Controller exits. ; ; - Tasks: Short-lived background processes that perform a single, one-shot action (e.g., generate a report). ; They can be configured to run exclusively (locking all other tasks) or concurrently. ; They can also be configured to run as a separate sub-process or directly within the Controller's process. ; ; HOW IT WORKS: ; - IPC (Inter-Process Communication): Communication between the Controller and Workers is handled via the Windows Registry. ; The Controller writes a signal to a specific registry key to gracefully shut down a Worker. ; - Asynchronous Operations: All monitoring (process status, config file changes) and Worker functions are handled ; asynchronously using AdlibRegister, ensuring the Controller remains responsive. ; - Configuration: All Workers and Tasks are defined in the master arrays within this script, but they are enabled, ; disabled, and configured via an external 'Configs.ini' file. This allows for easy customization without ; modifying the source code. ; - Logging: The script generates a detailed log file ('manager.log') in the same directory, recording all major ; events like startup, shutdown, worker status changes, and errors. ; ; PERFORMANCE IMPROVEMENTS: ; - Optimized tray menu handling with reduced event processing overhead ; - Faster exit processing with immediate cleanup ; - Reduced Adlib function frequency for better responsiveness ; - Improved memory management and array operations ; ; NEW FEATURES: ; - On/Off declaration for MasterWorkers and MasterTasks ; - Au3Stripper compatibility with proper function protection ; - Enhanced configuration system ; - AutoStart option for Workers and Tasks ; =============================================================================================================================== #include <TrayConstants.au3> #include <Array.au3> #include <File.au3> #include <FileConstants.au3> ; Au3Stripper protection directives #Au3Stripper_Ignore_Funcs=Worker1Function,Worker2Function,Worker3Function,Worker4Function,Worker5Function,Worker6Function,Worker7Function,Worker8Function,Worker9Function,Worker10Function #Au3Stripper_Ignore_Funcs=TaskA_Function,TaskB_Function,TaskC_Function,TaskD_Function,TaskE_Function,TaskF_Function,TaskG_Function,TaskH_Function,TaskI_Function,TaskJ_Function #Au3Stripper_Ignore_Funcs=_CheckStatus_Adlib,_CheckForConfigChanges_Adlib,_WorkerHeartbeat_Adlib #Au3Stripper_Ignore_Variables=$g_aMasterWorkers,$g_aMasterTasks,$g_aWorkers,$g_aTasks,$g_aWorkerTrayItems,$g_aTaskTrayItems ; =============================================================================================================================== ; --- GLOBAL CONSTANTS AND VARIABLES ; =============================================================================================================================== ; --- Settings --- Global Const $g_sConfigFile = @ScriptDir & "\Configs.ini" Global Const $g_sLogFile = @ScriptDir & "\Manager.log" Global $g_sAppName = "Controller-Worker-Task Manager" Global $g_iCheckConfigInterval = 15000 ; Increased for better performance Global $g_sLastConfigModDate = "" ; --- Registry & IPC --- Global Const $g_sRegBase = "HKCU\Software\ControllerWorkerApp" Global Const $g_iAdlibCheckInterval = 2000 ; Reduced frequency for better performance Global Const $g_iGracefulShutdownTimeout = 2000 ; Reduced for faster exit ; --- Status Constants for internal logic --- Global Const $STATUS_STOPPED = 0, $STATUS_RUNNING = 1, $STATUS_ERROR = 2 ; --- Task Execution Mode & Type Constants --- Global Const $TASK_MODE_EXCLUSIVE = 0, $TASK_MODE_CONCURRENT = 1 Global Const $TASK_RUN_TYPE_SUBAPP = 0, $TASK_RUN_TYPE_DIRECT = 1 ; --- Menu Type Constants --- Global Const $MENU_TYPE_MAIN = 0, $MENU_TYPE_SUB = 1 ; --- Performance optimization flags --- Global $g_bShuttingDown = False Global $g_bConfigReloading = False ; --- MASTER DATA STRUCTURES (Hard-coded definitions) --- ; [InternalName, DisplayName, FunctionName, MenuType, Status(On/Off), AutoStart(On/Off)] Global Const $g_aMasterWorkers[10][6] = [ _ ["CheckResources", "Check Resources", "Worker1Function", $MENU_TYPE_MAIN, "On", "On"], _ ; AutoStart Enabled ["SyncFiles", "Sync Files", "Worker2Function", $MENU_TYPE_MAIN, "On", "Off"], _ ["MonitorNetwork", "Monitor Network", "Worker3Function", $MENU_TYPE_MAIN, "On", "On"], _ ; AutoStart Enabled ["BackupDB", "Backup DB", "Worker4Function", $MENU_TYPE_SUB, "On", "Off"], _ ["CleanupLogs", "Cleanup Logs", "Worker5Function", $MENU_TYPE_SUB, "On", "Off"], _ ["ProcessEmails", "Process Emails", "Worker6Function", $MENU_TYPE_SUB, "On", "Off"], _ ["AutoScreenshot", "Auto Screenshot", "Worker7Function", $MENU_TYPE_SUB, "Off", "Off"], _ ["MonitorTemp", "Monitor Temp", "Worker8Function", $MENU_TYPE_SUB, "Off", "Off"], _ ["CheckUpdates", "Check Updates", "Worker9Function", $MENU_TYPE_SUB, "On", "Off"], _ ["SecurityScan", "Security Scan", "Worker10Function", $MENU_TYPE_SUB, "Off", "Off"] _ ] ; [InternalName, DisplayName, FunctionName, MenuType, ExecutionMode, RunType, Status(On/Off), AutoStart(On/Off)] Global Const $g_aMasterTasks[10][8] = [ _ ["QuickCleanup", "Quick Cleanup", "TaskA_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_SUBAPP, "On", "Off"], _ ["GenerateReport", "Generate Report", "TaskB_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_SUBAPP, "On", "On"], _ ; AutoStart Enabled ["SendNotification", "Send Notification (Direct)", "TaskJ_Function", $MENU_TYPE_MAIN, $TASK_MODE_EXCLUSIVE, $TASK_RUN_TYPE_DIRECT, "On", "Off"], _ ["TaskC", "Run Task C (Direct)", "TaskC_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_DIRECT, "On", "Off"], _ ["TaskD", "Run Task D", "TaskD_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off", "Off"], _ ["TaskE", "Run Task E", "TaskE_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off", "Off"], _ ["TaskF", "Run Task F", "TaskF_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "On", "Off"], _ ["TaskG", "Run Task G", "TaskG_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off", "Off"], _ ["TaskH", "Run Task H", "TaskH_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "On", "Off"], _ ["TaskI", "Run Task I", "TaskI_Function", $MENU_TYPE_SUB, $TASK_MODE_CONCURRENT, $TASK_RUN_TYPE_SUBAPP, "Off", "Off"] _ ] ; --- Dynamic Data Structures (Populated from Master based on INI) --- Global $g_aWorkers, $g_iNumWorkers Global $g_aTasks, $g_iNumTasks ; --- Tray Menu Control Variables --- Global $g_aWorkerTrayItems[1], $g_aTaskTrayItems[1], $g_iTrayExit ; --- Shared Global Variables for Worker processes --- Global $g_sWorkerInternalName, $g_iControllerPID, $g_sWorkerRegKey ; --- Performance optimization variables --- Global $g_iLastStatusCheck = 0 Global $g_iLastConfigCheck = 0 ; =============================================================================================================================== ; --- MAIN SCRIPT LOGIC ; =============================================================================================================================== _Main() ;/** ; * @brief Main entry point of the script. ; * ; * Determines whether to run as the Controller or as a sub-process (Worker/Task) ; * based on the command-line arguments. ; */ Func _Main() If $CmdLine[0] > 0 Then Local $aCmd = StringSplit($CmdLine[1], ":") If $aCmd[0] < 2 Then Exit Local $sType = $aCmd[1], $sInternalName = $aCmd[2], $iControllerPID = ($CmdLine[0] > 1 ? $CmdLine[2] : 0) Switch $sType Case "worker" _RunAsWorker($sInternalName, $iControllerPID) Case "task" _RunAsTask($sInternalName) EndSwitch Else _RunAsController() EndIf EndFunc ;==>_Main ; =============================================================================================================================== ; --- CONTROLLER FUNCTIONS ; =============================================================================================================================== ;/** ; * @brief Initializes the Controller process. ; * ; * This function sets up the entire Controller environment: ; * 1. Cleans up any leftover registry keys from previous runs. ; * 2. Loads the configuration from Configs.ini. ; * 3. Creates the tray menu. ; * 4. Auto-starts any configured items. ; * 5. Registers Adlib functions for background monitoring. ; * 6. Starts the main event loop. ; */ Func _RunAsController() ; Performance optimization: Batch initialization _CleanupAllRegistryKeys() _LoadConfiguration() _CreateTrayMenu() ; Auto-start configured items _AutoStartItems() ; Optimized Adlib registration with performance flags $g_iLastStatusCheck = TimerInit() $g_iLastConfigCheck = TimerInit() AdlibRegister("_CheckStatus_Adlib", $g_iAdlibCheckInterval) AdlibRegister("_CheckForConfigChanges_Adlib", $g_iCheckConfigInterval) _WriteLog("INFO: Controller started successfully.") _ControllerMainLoop() EndFunc ;==>_RunAsController ;/** ; * @brief Loads and parses the configuration from Configs.ini. ; * ; * Enhanced version with On/Off status checking for master arrays. ; * If Configs.ini exists, it reads the settings and the disable lists. ; * If not, it uses the default configuration (all master items with "On" status enabled). ; * It then populates the dynamic $g_aWorkers and $g_aTasks arrays with only the enabled items. ; */ Func _LoadConfiguration() $g_bConfigReloading = True Local $sDisabledWorkers = "", $sDisabledTasks = "" If FileExists($g_sConfigFile) Then $g_sLastConfigModDate = FileGetTime($g_sConfigFile, $FT_MODIFIED, $FT_STRING) $g_sAppName = IniRead($g_sConfigFile, "Settings", "AppName", "Controller Framework") $g_iCheckConfigInterval = IniRead($g_sConfigFile, "Settings", "CheckConfigInterval", 15000) $sDisabledWorkers = "," & IniRead($g_sConfigFile, "Workers", "Disable", "") & "," $sDisabledTasks = "," & IniRead($g_sConfigFile, "Tasks", "Disable", "") & "," Else $g_sLastConfigModDate = "" $sDisabledWorkers = "," $sDisabledTasks = "," EndIf ; Enhanced filtering: Check both INI disable list and master array On/Off status Local $iWorkerCount = 0, $iTaskCount = 0 For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][4] = "On" And Not StringInStr($sDisabledWorkers, "," & $g_aMasterWorkers[$i][0] & ",") Then $iWorkerCount += 1 EndIf Next For $i = 0 To UBound($g_aMasterTasks) - 1 If $g_aMasterTasks[$i][6] = "On" And Not StringInStr($sDisabledTasks, "," & $g_aMasterTasks[$i][0] & ",") Then $iTaskCount += 1 EndIf Next $g_iNumWorkers = $iWorkerCount $g_iNumTasks = $iTaskCount ; Performance optimization: Only create arrays if needed If $g_iNumWorkers > 0 Then Dim $g_aWorkers[$g_iNumWorkers][8] ; UPDATED: Increased size for AutoStart flag Local $iWorkerIdx = 0 For $i = 0 To UBound($g_aMasterWorkers) - 1 Local $sInternalName = $g_aMasterWorkers[$i][0] If $g_aMasterWorkers[$i][4] = "Off" Or StringInStr($sDisabledWorkers, "," & $sInternalName & ",") Then ContinueLoop $g_aWorkers[$iWorkerIdx][0] = $sInternalName $g_aWorkers[$iWorkerIdx][1] = $g_aMasterWorkers[$i][1] $g_aWorkers[$iWorkerIdx][2] = $g_aMasterWorkers[$i][2] $g_aWorkers[$iWorkerIdx][3] = 0 $g_aWorkers[$iWorkerIdx][4] = False $g_aWorkers[$iWorkerIdx][5] = $STATUS_STOPPED $g_aWorkers[$iWorkerIdx][6] = $g_aMasterWorkers[$i][3] $g_aWorkers[$iWorkerIdx][7] = $g_aMasterWorkers[$i][5] ; NEW: Copy AutoStart flag $iWorkerIdx += 1 Next Else Dim $g_aWorkers[0][0] ; Empty array EndIf If $g_iNumTasks > 0 Then Dim $g_aTasks[$g_iNumTasks][8] ; UPDATED: Increased size for AutoStart flag Local $iTaskIdx = 0 For $i = 0 To UBound($g_aMasterTasks) - 1 Local $sInternalName = $g_aMasterTasks[$i][0] If $g_aMasterTasks[$i][6] = "Off" Or StringInStr($sDisabledTasks, "," & $sInternalName & ",") Then ContinueLoop $g_aTasks[$iTaskIdx][0] = $sInternalName $g_aTasks[$iTaskIdx][1] = $g_aMasterTasks[$i][1] $g_aTasks[$iTaskIdx][2] = $g_aMasterTasks[$i][2] $g_aTasks[$iTaskIdx][3] = 0 $g_aTasks[$iTaskIdx][4] = $g_aMasterTasks[$i][4] $g_aTasks[$iTaskIdx][5] = $g_aMasterTasks[$i][5] $g_aTasks[$iTaskIdx][6] = $g_aMasterTasks[$i][3] $g_aTasks[$iTaskIdx][7] = $g_aMasterTasks[$i][7] ; NEW: Copy AutoStart flag $iTaskIdx += 1 Next Else Dim $g_aTasks[0][0] ; Empty array EndIf $g_bConfigReloading = False EndFunc ;==>_LoadConfiguration ;/** ; * @brief Creates the entire tray menu structure based on the loaded configuration. ; * ; * Enhanced version with better performance and empty array handling. ; */ Func _CreateTrayMenu() Opt("TrayMenuMode", 3) TraySetToolTip($g_sAppName) ; Performance optimization: Only create arrays if needed If $g_iNumWorkers > 0 Then ReDim $g_aWorkerTrayItems[$g_iNumWorkers] Local $hSubWorkersMenu = 0 For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][6] = $MENU_TYPE_MAIN Then $g_aWorkerTrayItems[$i] = TrayCreateItem("[OFF] " & $g_aWorkers[$i][1]) Else If $hSubWorkersMenu = 0 Then $hSubWorkersMenu = TrayCreateMenu("Sub Workers") $g_aWorkerTrayItems[$i] = TrayCreateItem("[OFF] " & $g_aWorkers[$i][1], $hSubWorkersMenu) EndIf Next Else ReDim $g_aWorkerTrayItems[0] EndIf TrayCreateItem("") If $g_iNumTasks > 0 Then ReDim $g_aTaskTrayItems[$g_iNumTasks] Local $hSubTasksMenu = 0 For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][6] = $MENU_TYPE_MAIN Then $g_aTaskTrayItems[$i] = TrayCreateItem($g_aTasks[$i][1]) Else If $hSubTasksMenu = 0 Then $hSubTasksMenu = TrayCreateMenu("Sub Tasks") $g_aTaskTrayItems[$i] = TrayCreateItem($g_aTasks[$i][1], $hSubTasksMenu) EndIf Next Else ReDim $g_aTaskTrayItems[0] EndIf TrayCreateItem("") $g_iTrayExit = TrayCreateItem("Exit") TraySetState(1) EndFunc ;==>_CreateTrayMenu ;/** ; * @brief The main event loop for the Controller. ; * ; * Enhanced with performance optimizations and faster exit handling. ; */ Func _ControllerMainLoop() While Not $g_bShuttingDown Local $iTrayMsg = TrayGetMsg() Switch $iTrayMsg Case 0 ; No message - continue Case $g_iTrayExit ; Immediate exit handling for better responsiveness $g_bShuttingDown = True _ExitController() Case Else ; Performance optimization: Early exit if reloading config If $g_bConfigReloading Then ContinueLoop Local $iIndex = _GetIndexFromTrayID($iTrayMsg, $g_aWorkerTrayItems) If $iIndex <> -1 Then _HandleWorkerClick($iIndex) ContinueLoop EndIf $iIndex = _GetIndexFromTrayID($iTrayMsg, $g_aTaskTrayItems) If $iIndex <> -1 Then _HandleTaskClick($iIndex) ContinueLoop EndIf EndSwitch Sleep(50) ; Reduced sleep for better responsiveness WEnd EndFunc ;==>_ControllerMainLoop ;/** ; * @brief Handles a click on a Worker menu item. ; * @param $iIndex The index of the clicked worker in the $g_aWorkers array. ; */ Func _HandleWorkerClick($iIndex) If $g_bShuttingDown Or $g_bConfigReloading Then Return _UpdateWorkerState($iIndex, Not $g_aWorkers[$iIndex][4]) EndFunc ;==>_HandleWorkerClick ;/** ; * @brief Handles a click on a Task menu item. ; * @param $iIndex The index of the clicked task in the $g_aTasks array. ; */ Func _HandleTaskClick($iIndex) If $g_bShuttingDown Or $g_bConfigReloading Then Return _RunTask($iIndex) EndFunc ;==>_HandleTaskClick ;/** ; * @brief Central function to change a worker's state (on/off). ; * @param $iIndex The index of the worker. ; * @param $bNewState The new state to apply (True for ON, False for OFF). ; */ Func _UpdateWorkerState($iIndex, $bNewState) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return $g_aWorkers[$iIndex][4] = $bNewState If $bNewState Then _StartWorker($iIndex) Else _StopWorker($iIndex) EndIf _UpdateTrayItemForWorker($iIndex) EndFunc ;==>_UpdateWorkerState ;/** ; * @brief Updates a worker's tray menu item text and checked state. ; * @param $iIndex The index of the worker. ; */ Func _UpdateTrayItemForWorker($iIndex) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return Local $sPrefix, $iTrayState If $g_aWorkers[$iIndex][4] Then $sPrefix = "[ON] " $iTrayState = $TRAY_CHECKED Else $sPrefix = "[OFF] " $iTrayState = $TRAY_UNCHECKED EndIf TrayItemSetText($g_aWorkerTrayItems[$iIndex], $sPrefix & $g_aWorkers[$iIndex][1]) TrayItemSetState($g_aWorkerTrayItems[$iIndex], $iTrayState) EndFunc ;==>_UpdateTrayItemForWorker ;/** ; * @brief Starts a worker sub-process. ; * @param $iIndex The index of the worker. ; */ Func _StartWorker($iIndex) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return If ProcessExists($g_aWorkers[$iIndex][3]) Then Return Local $sCommand = 'worker:' & $g_aWorkers[$iIndex][0] _WriteLog("INFO: Starting Worker '" & $g_aWorkers[$iIndex][1] & "'...") Local $iPID = _RunScript($sCommand) If $iPID > 0 Then $g_aWorkers[$iIndex][3] = $iPID $g_aWorkers[$iIndex][5] = $STATUS_RUNNING Else _WriteLog("ERROR: Failed to start Worker '" & $g_aWorkers[$iIndex][1] & "'.") $g_aWorkers[$iIndex][4] = False $g_aWorkers[$iIndex][5] = $STATUS_ERROR EndIf EndFunc ;==>_StartWorker ;/** ; * @brief Stops a worker sub-process gracefully. ; * @param $iIndex The index of the worker. ; */ Func _StopWorker($iIndex) If $g_iNumWorkers = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumWorkers Then Return Local $iPID = $g_aWorkers[$iIndex][3] If $iPID = 0 Or Not ProcessExists($iPID) Then $g_aWorkers[$iIndex][3] = 0 $g_aWorkers[$iIndex][5] = $STATUS_STOPPED Return EndIf _WriteLog("INFO: Stopping Worker '" & $g_aWorkers[$iIndex][1] & "' (PID: " & $iPID & ")...") Local $sRegKey = $g_sRegBase & "\" & $g_aWorkers[$iIndex][0] RegWrite($sRegKey, "Signal", "REG_SZ", "exit") If @error Then _WriteLog("ERROR: Failed to write exit signal to registry for " & $g_aWorkers[$iIndex][0]) Local $iResult = ProcessWaitClose($iPID, $g_iGracefulShutdownTimeout / 1000) If $iResult = 0 Then _WriteLog("WARN: Worker PID " & $iPID & " did not respond. Forcing shutdown.") ProcessClose($iPID) EndIf RegDelete($sRegKey) $g_aWorkers[$iIndex][3] = 0 $g_aWorkers[$iIndex][5] = $STATUS_STOPPED EndFunc ;==>_StopWorker ;/** ; * @brief Runs a task either directly or as a sub-process. ; * @param $iIndex The index of the task. ; */ Func _RunTask($iIndex) If $g_iNumTasks = 0 Or $iIndex < 0 Or $iIndex >= $g_iNumTasks Then Return Local $sTaskName = $g_aTasks[$iIndex][1] _WriteLog("INFO: Running Task '" & $sTaskName & "'...") If $g_aTasks[$iIndex][5] = $TASK_RUN_TYPE_DIRECT Then TrayItemSetState($g_aTaskTrayItems[$iIndex], $TRAY_DISABLE) Call($g_aTasks[$iIndex][2], $sTaskName) TrayItemSetState($g_aTaskTrayItems[$iIndex], $TRAY_ENABLE) _WriteLog("INFO: Direct Task '" & $sTaskName & "' has completed.") Else If $g_aTasks[$iIndex][3] <> 0 And ProcessExists($g_aTasks[$iIndex][3]) Then Return Local $sCommand = 'task:' & $g_aTasks[$iIndex][0] Local $iPID = _RunScript($sCommand, False) If $iPID > 0 Then $g_aTasks[$iIndex][3] = $iPID _UpdateAllTaskMenusState() Else _WriteLog("ERROR: Failed to run Task '" & $sTaskName & "'.") EndIf EndIf EndFunc ;==>_RunTask ;/** ; * @brief Automatically starts workers and tasks that are configured to run on startup. ; */ Func _AutoStartItems() _WriteLog("INFO: Checking for items to auto-start...") ; Auto-start workers If $g_iNumWorkers > 0 Then For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][7] = "On" Then _WriteLog("INFO: Auto-starting Worker '" & $g_aWorkers[$i][1] & "'...") _UpdateWorkerState($i, True) EndIf Next EndIf ; Auto-start tasks If $g_iNumTasks > 0 Then For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][7] = "On" Then _WriteLog("INFO: Auto-starting Task '" & $g_aTasks[$i][1] & "'...") _RunTask($i) EndIf Next EndIf EndFunc ;==>_AutoStartItems ;/** ; * @brief Optimized Adlib function to monitor the status of running workers and tasks. ; */ Func _CheckStatus_Adlib() ; Performance optimization: Skip if shutting down or reloading If $g_bShuttingDown Or $g_bConfigReloading Then Return ; Rate limiting for better performance If TimerDiff($g_iLastStatusCheck) < $g_iAdlibCheckInterval Then Return $g_iLastStatusCheck = TimerInit() ; Check workers only if we have any If $g_iNumWorkers > 0 Then For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][4] And Not ProcessExists($g_aWorkers[$i][3]) Then _WriteLog("WARN: Worker '" & $g_aWorkers[$i][1] & "' died. Restarting...") $g_aWorkers[$i][5] = $STATUS_ERROR _UpdateTrayItemForWorker($i) _StartWorker($i) EndIf Next EndIf ; Check tasks only if we have any If $g_iNumTasks > 0 Then Local $bTaskStateChanged = False For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][5] = $TASK_RUN_TYPE_SUBAPP And $g_aTasks[$i][3] > 0 And Not ProcessExists($g_aTasks[$i][3]) Then $g_aTasks[$i][3] = 0 $bTaskStateChanged = True EndIf Next If $bTaskStateChanged Then _UpdateAllTaskMenusState() EndIf EndFunc ;==>_CheckStatus_Adlib ;/** ; * @brief Checks if any exclusive task is currently running as a sub-process. ; * @return True if an exclusive task is running, otherwise False. ; */ Func _IsExclusiveTaskRunning() If $g_iNumTasks = 0 Then Return False For $i = 0 To $g_iNumTasks - 1 If $g_aTasks[$i][4] = $TASK_MODE_EXCLUSIVE And $g_aTasks[$i][3] > 0 And ProcessExists($g_aTasks[$i][3]) Then Return True EndIf Next Return False EndFunc ;==>_IsExclusiveTaskRunning ;/** ; * @brief Updates the enabled/disabled state of all task menu items based on current activity. ; */ Func _UpdateAllTaskMenusState() If $g_iNumTasks = 0 Then Return Local $bExclusiveRunning = _IsExclusiveTaskRunning() For $i = 0 To $g_iNumTasks - 1 If $bExclusiveRunning Then TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_DISABLE) Else If $g_aTasks[$i][3] > 0 And ProcessExists($g_aTasks[$i][3]) Then TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_DISABLE) Else TrayItemSetState($g_aTaskTrayItems[$i], $TRAY_ENABLE) EndIf EndIf Next EndFunc ;==>_UpdateAllTaskMenusState ;/** ; * @brief Optimized Adlib function to check for modifications to the Configs.ini file. ; */ Func _CheckForConfigChanges_Adlib() ; Performance optimization: Skip if shutting down If $g_bShuttingDown Then Return ; Rate limiting for better performance If TimerDiff($g_iLastConfigCheck) < $g_iCheckConfigInterval Then Return $g_iLastConfigCheck = TimerInit() Local $sCurrentModDate = FileGetTime($g_sConfigFile, $FT_MODIFIED, $FT_STRING) If $sCurrentModDate <> $g_sLastConfigModDate And $sCurrentModDate <> "" Then _WriteLog("INFO: Configuration file change detected. Reloading...") $g_bConfigReloading = True _UnregisterAllMasterAdlibs() _StopAllWorkers() TraySetState(2) _LoadConfiguration() _CreateTrayMenu() ; Re-register Adlib functions after config reload AdlibRegister("_CheckStatus_Adlib", $g_iAdlibCheckInterval) AdlibRegister("_CheckForConfigChanges_Adlib", $g_iCheckConfigInterval) _WriteLog("INFO: Configuration reloaded and menu recreated.") EndIf EndFunc ;==>_CheckForConfigChanges_Adlib ;/** ; * @brief Stops all currently active workers with performance optimization. ; */ Func _StopAllWorkers() If $g_iNumWorkers = 0 Then Return _WriteLog("INFO: Stopping all workers...") ; Performance optimization: Parallel shutdown approach Local $aWorkerPIDs[$g_iNumWorkers] Local $aWorkerRegKeys[$g_iNumWorkers] ; First pass: Send exit signals to all workers For $i = 0 To $g_iNumWorkers - 1 If $g_aWorkers[$i][4] And $g_aWorkers[$i][3] > 0 And ProcessExists($g_aWorkers[$i][3]) Then $aWorkerPIDs[$i] = $g_aWorkers[$i][3] $aWorkerRegKeys[$i] = $g_sRegBase & "\" & $g_aWorkers[$i][0] RegWrite($aWorkerRegKeys[$i], "Signal", "REG_SZ", "exit") If @error Then _WriteLog("ERROR: Failed to write exit signal for " & $g_aWorkers[$i][0]) Else $aWorkerPIDs[$i] = 0 EndIf Next ; Wait for graceful shutdown Sleep(500) ; Second pass: Force close any remaining processes For $i = 0 To $g_iNumWorkers - 1 If $aWorkerPIDs[$i] > 0 Then If ProcessExists($aWorkerPIDs[$i]) Then _WriteLog("WARN: Force closing Worker PID " & $aWorkerPIDs[$i]) ProcessClose($aWorkerPIDs[$i]) EndIf RegDelete($aWorkerRegKeys[$i]) $g_aWorkers[$i][3] = 0 $g_aWorkers[$i][4] = False $g_aWorkers[$i][5] = $STATUS_STOPPED EndIf Next EndFunc ;==>_StopAllWorkers ;/** ; * @brief A generic function to run the script as a sub-process. ; * @param $sArgument The argument to pass to the new process (e.g., "worker:MyWorker"). ; * @param $bPassControllerPID If True, the current controller's PID is passed as the second argument. ; * @return The PID of the new process, or 0 on failure. ; */ Func _RunScript($sArgument, $bPassControllerPID = True) Local $sControllerPID = ($bPassControllerPID ? " " & @AutoItPID : "") Local $sCommand = '"' & @ScriptFullPath & '" "' & $sArgument & '"' & $sControllerPID Local $sExecutable = (@Compiled ? "" : '"' & @AutoItExe & '" ') Return Run($sExecutable & $sCommand, @ScriptDir, @SW_HIDE) EndFunc ;==>_RunScript ;/** ; * @brief Performs all necessary cleanup before the Controller exits. ; * Enhanced with faster shutdown process. ; */ Func _ExitController() $g_bShuttingDown = True _WriteLog("INFO: Controller shutting down...") ; Immediate cleanup for faster exit _UnregisterAllMasterAdlibs() _StopAllWorkers() _CleanupAllRegistryKeys() _WriteLog("INFO: Controller shutdown complete.") Exit EndFunc ;==>_ExitController ;/** ; * @brief Enhanced function to unregister all possible Adlib callbacks. ; * Now includes protection for Au3Stripper and handles empty arrays. ; */ Func _UnregisterAllMasterAdlibs() AdlibUnRegister("_CheckStatus_Adlib") AdlibUnRegister("_CheckForConfigChanges_Adlib") ; Unregister all master worker functions (protected from Au3Stripper) For $i = 0 To UBound($g_aMasterWorkers) - 1 AdlibUnRegister($g_aMasterWorkers[$i][2]) Next EndFunc ;==>_UnregisterAllMasterAdlibs ;/** ; * @brief Deletes the entire registry key used by the application for IPC. ; */ Func _CleanupAllRegistryKeys() RegDelete($g_sRegBase) If @error Then _WriteLog("WARN: Registry key cleanup - base key may not exist: " & $g_sRegBase) EndFunc ;==>_CleanupAllRegistryKeys ;/** ; * @brief Enhanced function to find array index with bounds checking. ; * @param $nID The tray item ID to find. ; * @param $aTrayItems The array of tray item IDs to search in. ; * @return The array index if found, otherwise -1. ; */ Func _GetIndexFromTrayID($nID, ByRef $aTrayItems) Local $iUBound = UBound($aTrayItems) If $iUBound = 0 Then Return -1 For $i = 0 To $iUBound - 1 If $aTrayItems[$i] = $nID Then Return $i Next Return -1 EndFunc ;==>_GetIndexFromTrayID ;/** ; * @brief Enhanced logging function with better performance and error handling. ; * @param $sMessage The message to log. ; */ Func _WriteLog($sMessage) Local $sTimeStamp = @YEAR & "/" & @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC Local $sFormattedMessage = $sTimeStamp & " - " & $sMessage & @CRLF ConsoleWrite($sFormattedMessage) ; Performance optimization: Non-blocking file write Local $hFile = FileOpen($g_sLogFile, $FO_APPEND + $FO_UTF8_NOBOM) If $hFile <> -1 Then FileWrite($hFile, $sFormattedMessage) FileClose($hFile) EndIf EndFunc ;==>_WriteLog ; =============================================================================================================================== ; --- WORKER / TASK SUB-PROCESS EXECUTION ; =============================================================================================================================== ;/** ; * @brief Initializes the script when run as a Worker sub-process. ; * @param $sInternalName The internal name of the worker to run. ; * @param $iControllerPID The PID of the parent Controller process to monitor. ; */ Func _RunAsWorker($sInternalName, $iControllerPID) $g_sWorkerInternalName = $sInternalName $g_iControllerPID = $iControllerPID $g_sWorkerRegKey = $g_sRegBase & "\" & $sInternalName RegDelete($g_sWorkerRegKey) Local $sFunction = "" For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][0] = $sInternalName Then $sFunction = $g_aMasterWorkers[$i][2] ExitLoop EndIf Next If $sFunction = "" Then Exit ; Performance optimization: Longer intervals for worker processes AdlibRegister($sFunction, 1000) AdlibRegister("_WorkerHeartbeat_Adlib", 3000) ; Reduced frequency While 1 Sleep(200) ; Slightly longer sleep for worker processes WEnd EndFunc ;==>_RunAsWorker ;/** ; * @brief Enhanced Adlib function for workers to monitor the Controller and exit signals. ; */ Func _WorkerHeartbeat_Adlib() ; Check if controller is still running If $g_iControllerPID > 0 And Not ProcessExists($g_iControllerPID) Then _WorkerExitGracefully() Return EndIf ; Check for exit signal from registry Local $sSignal = RegRead($g_sWorkerRegKey, "Signal") If @error Then _WorkerExitGracefully() Return EndIf If $sSignal = "exit" Then _WorkerExitGracefully() EndIf EndFunc ;==>_WorkerHeartbeat_Adlib ;/** ; * @brief Enhanced cleanup function for Workers before exit. ; */ Func _WorkerExitGracefully() AdlibUnRegister("_WorkerHeartbeat_Adlib") ; Find and unregister the specific worker function Local $sFunction = "" For $i = 0 To UBound($g_aMasterWorkers) - 1 If $g_aMasterWorkers[$i][0] = $g_sWorkerInternalName Then $sFunction = $g_aMasterWorkers[$i][2] ExitLoop EndIf Next If $sFunction <> "" Then AdlibUnRegister($sFunction) RegDelete($g_sWorkerRegKey) Exit EndFunc ;==>_WorkerExitGracefully ;/** ; * @brief Initializes the script when run as a Task sub-process. ; * @param $sInternalName The internal name of the task to run. ; */ Func _RunAsTask($sInternalName) Local $sFunction, $sDisplayName For $i = 0 To UBound($g_aMasterTasks) - 1 If $g_aMasterTasks[$i][0] = $sInternalName Then $sDisplayName = $g_aMasterTasks[$i][1] $sFunction = $g_aMasterTasks[$i][2] ExitLoop EndIf Next If $sFunction = "" Then Exit Call($sFunction, $sDisplayName) Exit EndFunc ;==>_RunAsTask ; =============================================================================================================================== ; --- SPECIFIC WORKER/TASK FUNCTIONS (IMPLEMENTATION) ; --- These functions are protected by Au3Stripper directives ; =============================================================================================================================== Func Worker1Function() _WriteLog("Worker 'Check Resources' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker1Function Func Worker2Function() _WriteLog("Worker 'Sync Files' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker2Function Func Worker3Function() _WriteLog("Worker 'Monitor Network' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker3Function Func Worker4Function() _WriteLog("Worker 'Backup DB' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker4Function Func Worker5Function() _WriteLog("Worker 'Cleanup Logs' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker5Function Func Worker6Function() _WriteLog("Worker 'Process Emails' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker6Function Func Worker7Function() _WriteLog("Worker 'Auto Screenshot' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker7Function Func Worker8Function() _WriteLog("Worker 'Monitor Temp' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker8Function Func Worker9Function() _WriteLog("Worker 'Check Updates' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker9Function Func Worker10Function() _WriteLog("Worker 'Security Scan' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>Worker10Function Func TaskA_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(2000) EndFunc ;==>TaskA_Function Func TaskB_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(3000) EndFunc ;==>TaskB_Function Func TaskC_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here EndFunc ;==>TaskC_Function Func TaskD_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskD_Function Func TaskE_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskE_Function Func TaskF_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskF_Function Func TaskG_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskG_Function Func TaskH_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskH_Function Func TaskI_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskI_Function Func TaskJ_Function($sName) _WriteLog("Task '" & $sName & "' is running...") ; This is PlaceHoder - Your Code Replace Here Sleep(1000) EndFunc ;==>TaskJ_Function Eg: Configs.ini expandcollapse popup; ====================================================================== ; Configuration file for the Controller-Worker-Task Manager ; ; Instructions: ; - To enable an option, remove the semicolon (;) from the beginning of the line. ; - Changes made to this file will be automatically detected and ; applied by the program without requiring a restart. ; ====================================================================== [Settings] ; The application name that will be displayed in the system tray tooltip. AppName=My Custom Automation Suite ; The interval (in milliseconds) for checking this configuration file for changes. ; The default value in the script is 15000 (which is 15 seconds). CheckConfigInterval=15000 [Workers] ; List the InternalName of the Workers you want to DISABLE, separated by commas (with no spaces). ; Even if a Worker is set to "On" in the script's master array, listing it here ; will prevent it from being loaded and appearing in the tray menu. ; ; Example: Disable=SyncFiles,CleanupLogs ; ; Available Workers (InternalName from the script): ; CheckResources, SyncFiles, MonitorNetwork, BackupDB, CleanupLogs, ProcessEmails, ; AutoScreenshot, MonitorTemp, CheckUpdates, SecurityScan ; ; This example will disable the "Backup DB" and "Process Emails" workers. Disable=BackupDB,ProcessEmails [Tasks] ; List the InternalName of the Tasks you want to DISABLE, separated by commas. ; Similar to Workers, any Task listed here will be disabled. ; ; Available Tasks (InternalName from the script): ; QuickCleanup, GenerateReport, SendNotification, TaskC, TaskD, TaskE, ; TaskF, TaskG, TaskH, TaskI ; ; This example will disable "Run Task F" and "Run Task H". Disable=TaskF,TaskH Edited August 16 by Trong fix Au3Stripper directive ioa747 and WildByDesign 2 Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal
WildByDesign Posted August 15 Posted August 15 This is a beautiful, intelligent design structure. I will keep this in mind for whenever I start a new project. I have a question about this: ; Au3Stripper protection directives #Au3Stripper_DoNotStrip_Func=Worker1Function,Worker2Function,Worker3Function,Worker4Function,Worker5Function,Worker6Function,Worker7Function,Worker8Function,Worker9Function,Worker10Function #Au3Stripper_DoNotStrip_Func=TaskA_Function,TaskB_Function,TaskC_Function,TaskD_Function,TaskE_Function,TaskF_Function,TaskG_Function,TaskH_Function,TaskI_Function,TaskJ_Function #Au3Stripper_DoNotStrip_Func=_CheckStatus_Adlib,_CheckForConfigChanges_Adlib,_WorkerHeartbeat_Adlib #Au3Stripper_DoNotStrip_Var=$g_aMasterWorkers,$g_aMasterTasks,$g_aWorkers,$g_aTasks,$g_aWorkerTrayItems,$g_aTaskTrayItems Where do these #Au3Stripper_DoNotStrip_* come from? The docs (Au3Stripper) show #Au3Stripper_Ignore_Funcs and #Au3Stripper_Ignore_Variables but the naming is different. Are the docs outdated and the ones you are using newer? I am not very familiar with Au3Stripper and only recently started using it in my projects.
Trong Posted August 16 Author Posted August 16 12 hours ago, WildByDesign said: The docs (Au3Stripper) show #Au3Stripper_Ignore_Funcs and #Au3Stripper_Ignore_Variables but the naming is different. Are the docs outdated and the ones you are using newer? That name is wrong. Your document is absolutely correct, it's just something I added when I couldn't remember exactly what it was! It's just a quick edit, just search and replace to use the code quickly! WildByDesign 1 Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal
Trong Posted October 9 Author Posted October 9 Simple version: expandcollapse popup#NoTrayIcon #include <TrayConstants.au3> #include <MsgBoxConstants.au3> #include <Process.au3> #include <File.au3> #include <Misc.au3> ; ============================================ ; AutoIt Advanced Framework v2.1 FINAL ; ============================================ ; A comprehensive framework for managing multiple types of functions: ; ; FEATURES: ; - In-Process Functions: Run continuously in the main process with dynamic Adlib ; - Separate Process Functions: Run in independent child processes with cross-monitoring ; - One-Time Functions: Execute once when triggered ; - Auto-Restart: Automatic retry with exponential backoff when child process crashes ; - State Management: Struct-based state management for maintainability ; - Logging: Multi-level logging (INFO/WARN/ERROR) with rotation ; - Mutex/Lock: Re-entrancy protection for all functions ; - Singleton: Prevents multiple instances of the framework ; ; ARCHITECTURE: ; - Parent Process: Main framework with tray menu and hotkeys ; - Child Processes: Independent processes spawned via command line args ; - Cross-Monitoring: Parent monitors children, children monitor parent ; - Graceful Shutdown: Multiple fallback mechanisms for process termination ; ; USAGE: ; - Run directly: Main framework with GUI ; - Command line: /func5 <ParentPID> or /func6 <ParentPID> for child process mode ; ; AUTHOR: Dao Van Trong - TRONG.PRO ; LICENSE: Open Source ; ============================================ ; Ensure only one instance of the framework runs ; Uses mutex to prevent multiple instances from conflicting _Singleton("AutoItFrameworkV2_" & @ScriptName, 0) #Region === CONSTANTS & CONFIGURATION === ; Version information Global Const $VERSION = "2.1" ; ===== TIMING CONFIGURATION ===== ; Intervals for continuous functions (milliseconds) Global Const $FUNC1_INTERVAL = 1000 ; Function 1 runs every 1 second Global Const $FUNC2_INTERVAL = 2000 ; Function 2 runs every 2 seconds ; Process monitoring interval Global Const $PROCESS_CHECK_INTERVAL = 3000 ; Check child processes every 3 seconds (reduced for CPU efficiency) ; ===== AUTO-STOP CONFIGURATION ===== ; Maximum run count before auto-stopping (for demo purposes) Global Const $FUNC1_AUTO_STOP = 10 ; Function 1 stops after 10 runs Global Const $FUNC2_AUTO_STOP = 15 ; Function 2 stops after 15 runs ; ===== PROCESS MANAGEMENT ===== ; Timeouts for graceful process shutdown Global Const $PROCESS_SHUTDOWN_TIMEOUT = 3000 ; Wait 3 seconds before force kill Global Const $PROCESS_KILL_DELAY = 500 ; Delay after force kill command ; ===== AUTO-RESTART / RETRY CONFIGURATION ===== ; Controls automatic restart behavior when child processes crash Global Const $AUTO_RESTART_ENABLED = True ; Enable/disable auto-restart feature Global Const $MAX_RETRY_COUNT = 3 ; Maximum retry attempts (0-3) Global Const $RETRY_RESET_TIMEOUT = 300000 ; Reset retry counter after 5 minutes of stable running Global Const $RETRY_BASE_DELAY = 2000 ; Base delay between retries (2 seconds) Global Const $RETRY_EXPONENTIAL = True ; Use exponential backoff (2s, 4s, 8s, 16s...) Global Const $RETRY_CHECK_INTERVAL = 500 ; Check for manual disable every 500ms during retry delay ; ===== LOGGING CONFIGURATION ===== ; Log file size and rotation settings Global Const $MAX_LOG_SIZE = 1048576 ; Maximum log file size (1MB) Global Const $MAX_LOG_BACKUPS = 3 ; Number of backup log files to keep ; Log levels for filtering messages Global Enum $LOG_INFO = 1, $LOG_WARN = 2, $LOG_ERROR = 3 ; ===== HOTKEY DEFINITIONS ===== ; Keyboard shortcuts for quick access Global Const $HOTKEY_FUNC1 = "^!1" ; Ctrl+Alt+1: Toggle Function 1 Global Const $HOTKEY_FUNC2 = "^!2" ; Ctrl+Alt+2: Toggle Function 2 Global Const $HOTKEY_FUNC3 = "^!3" ; Ctrl+Alt+3: Execute Function 3 Global Const $HOTKEY_FUNC4 = "^!4" ; Ctrl+Alt+4: Execute Function 4 Global Const $HOTKEY_FUNC5 = "^!5" ; Ctrl+Alt+5: Toggle Function 5 Process Global Const $HOTKEY_FUNC6 = "^!6" ; Ctrl+Alt+6: Toggle Function 6 Process Global Const $HOTKEY_DEBUG = "^!d" ; Ctrl+Alt+D: Toggle debug mode Global Const $HOTKEY_EXIT = "^!q" ; Ctrl+Alt+Q: Exit framework #EndRegion #Region === GLOBAL VARIABLES === ; ===== FRAMEWORK STATE ===== Global $g_bRunning = True ; Main loop control flag Global $g_bDebugMode = True ; Enable/disable debug logging Global $g_iLogLevel = $LOG_INFO ; Current log level filter ; ===== STRUCT-BASED STATE MANAGEMENT ===== ; Array indices for process state arrays (for code readability) Global Enum $STATE_ENABLED = 0, _ ; Boolean: Is the process enabled? $STATE_PID, _ ; Integer: Process ID of child process $STATE_RETRY, _ ; Integer: Current retry count $STATE_STARTTIME, _ ; Float: Timer value when process started (for uptime calculation) $STATE_MANUAL, _ ; Boolean: Was it manually disabled by user? $STATE_MENUID ; Integer: Tray menu item ID for UI updates ; Process state arrays: [Enabled, PID, RetryCount, StartTime, ManualDisable, MenuID] Global $g_aFunc5State = [False, 0, 0, 0, False, 0] ; State for Function 5 (child process) Global $g_aFunc6State = [False, 0, 0, 0, False, 0] ; State for Function 6 (child process) ; ===== IN-PROCESS FUNCTION STATE ===== ; Simple boolean flags for functions running in main process Global $g_bFunc1_Enabled = False ; Is Function 1 enabled? Global $g_bFunc2_Enabled = False ; Is Function 2 enabled? ; Track Adlib registration to avoid duplicate registration Global $g_bFunc1_AdlibRegistered = False ; Is Adlib registered for Function 1? Global $g_bFunc2_AdlibRegistered = False ; Is Adlib registered for Function 2? ; ===== MUTEX / LOCK VARIABLES ===== ; Prevent re-entrancy (function called while already executing) Global $g_bFunc1_Running = False ; Is Function 1 currently executing? Global $g_bFunc2_Running = False ; Is Function 2 currently executing? Global $g_bFunc3_Running = False ; Is Function 3 currently executing? Global $g_bFunc4_Running = False ; Is Function 4 currently executing? ; ===== COUNTERS & TIMERS ===== ; For demo auto-stop and accurate timing Global $g_iFunc1_RunCount = 0 ; Number of times Function 1 has run Global $g_iFunc2_RunCount = 0 ; Number of times Function 2 has run Global $g_hFunc1_Timer = 0 ; Timer for Function 1 (for accurate timing) Global $g_hFunc2_Timer = 0 ; Timer for Function 2 (for accurate timing) ; ===== TRAY MENU IDS ===== ; Store menu item IDs for UI updates Global $idFunc1Toggle, $idFunc2Toggle ; Toggle items for in-process functions Global $idFunc3Execute, $idFunc4Execute ; Execute items for one-time functions Global $idFunc5ProcessToggle, $idFunc6ProcessToggle ; Toggle items for process functions Global $idDebugToggle, $idExit ; Debug toggle and exit items #EndRegion #Region === UTILITY FUNCTIONS === ; ============================================ ; Debug logging with timestamp and level filtering ; ============================================ ; Logs messages to console with timestamp and level prefix ; Messages are filtered based on $g_iLogLevel ; ; Parameters: ; $sMessage - String: The message to log ; $iLevel - Integer: Log level ($LOG_INFO, $LOG_WARN, or $LOG_ERROR) ; ; Returns: None ; ============================================ Func DebugLog($sMessage, $iLevel = $LOG_INFO) ; Skip if debug mode is off If Not $g_bDebugMode Then Return ; Filter by log level (only show messages at or above current level) If $iLevel < $g_iLogLevel Then Return ; Determine prefix based on level Local $sPrefix = "" Switch $iLevel Case $LOG_INFO $sPrefix = "[INFO] " Case $LOG_WARN $sPrefix = "[WARN] " Case $LOG_ERROR $sPrefix = "[ERROR] " EndSwitch ; Format timestamp Local $sTime = @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC ; Output to console ConsoleWrite("[" & $sTime & "] " & $sPrefix & $sMessage & @CRLF) EndFunc ; ============================================ ; Safely close a process with multiple fallback methods ; ============================================ ; Attempts to close a process gracefully, then uses increasingly ; forceful methods if needed: ; 1. ProcessClose() - Graceful shutdown request ; 2. WinKill() - Close process windows ; 3. taskkill /F - Force terminate ; ; Parameters: ; $iPID - Integer: Process ID to close ; $iTimeout - Integer: Milliseconds to wait for graceful shutdown ; ; Returns: ; Boolean: True if process was successfully closed, False otherwise ; ============================================ Func SafeProcessClose($iPID, $iTimeout = 3000) ; Validate PID and check if process exists If $iPID <= 0 Or Not ProcessExists($iPID) Then Return True DebugLog("SafeProcessClose: Attempting to close PID " & $iPID, $LOG_INFO) ; ===== METHOD 1: ProcessClose (Graceful) ===== ProcessClose($iPID) ; Wait for process to exit gracefully Local $iElapsed = 0 While ProcessExists($iPID) And $iElapsed < $iTimeout Sleep(100) $iElapsed += 100 WEnd ; Check if process closed gracefully If Not ProcessExists($iPID) Then DebugLog("SafeProcessClose: ✓ Process closed gracefully", $LOG_INFO) Return True EndIf ; ===== METHOD 2: WinKill (Close Windows) ===== DebugLog("SafeProcessClose: Attempting WinKill...", $LOG_WARN) WinKill("[PID:" & $iPID & "]") Sleep(1000) ; Check if WinKill worked If Not ProcessExists($iPID) Then DebugLog("SafeProcessClose: ✓ WinKill successful", $LOG_INFO) Return True EndIf ; ===== METHOD 3: taskkill /F (Force Kill) ===== DebugLog("SafeProcessClose: Attempting taskkill /F...", $LOG_WARN) RunWait(@ComSpec & ' /c taskkill /F /PID ' & $iPID & ' >nul 2>&1', "", @SW_HIDE) Sleep($PROCESS_KILL_DELAY) ; Check if force kill worked If Not ProcessExists($iPID) Then DebugLog("SafeProcessClose: ✓ Force kill successful", $LOG_INFO) Return True EndIf ; ===== ALL METHODS FAILED ===== DebugLog("SafeProcessClose: ❌ All methods failed for PID " & $iPID, $LOG_ERROR) TrayTip("Process Kill Failed", _ "Cannot close PID " & $iPID & @CRLF & _ "May require admin rights", 5, 3) Return False EndFunc ; ============================================ ; Rotate log file if it exceeds maximum size ; ============================================ ; Moves old log files to .1, .2, .3 etc. and creates new log file ; Automatically deletes oldest backup when limit is reached ; ; Parameters: ; $sLogPath - String: Full path to log file ; ; Returns: None ; ============================================ Func RotateLogFile($sLogPath) ; Check if file exists and is over size limit If Not FileExists($sLogPath) Then Return Local $iSize = FileGetSize($sLogPath) If $iSize < $MAX_LOG_SIZE Then Return DebugLog("RotateLogFile: Rotating " & Round($iSize/1024, 2) & " KB", $LOG_INFO) ; Delete oldest backup if it exists Local $sOldest = $sLogPath & "." & $MAX_LOG_BACKUPS If FileExists($sOldest) Then FileDelete($sOldest) ; Shift all backups up by one number (.1 -> .2, .2 -> .3, etc.) For $i = $MAX_LOG_BACKUPS - 1 To 1 Step -1 Local $sOld = $sLogPath & "." & $i Local $sNew = $sLogPath & "." & ($i + 1) If FileExists($sOld) Then FileMove($sOld, $sNew, 1) Next ; Move current log to .1 FileMove($sLogPath, $sLogPath & ".1", 1) EndFunc ; ============================================ ; Safely open log file with comprehensive error handling ; ============================================ ; Attempts to create/open log file with fallback to temp directory ; Shows detailed error messages to user if all attempts fail ; ; Parameters: ; $sLogPath - String: Desired path for log file ; $iMode - Integer: File open mode (default: 2 = write, erase existing) ; ; Returns: ; Handle: File handle if successful, -1 if failed ; ============================================ Func SafeLogFileOpen($sLogPath, $iMode = 2) ; Rotate if needed before opening RotateLogFile($sLogPath) ; Attempt to open file at specified path Local $hFile = FileOpen($sLogPath, $iMode) If $hFile = -1 Then ; First attempt failed - try temp directory DebugLog("SafeLogFileOpen: ❌ Failed to open " & $sLogPath, $LOG_ERROR) ; Extract filename and try in temp directory $sLogPath = @TempDir & "\" & StringRegExpReplace($sLogPath, "^.*\\", "") $hFile = FileOpen($sLogPath, $iMode) If $hFile = -1 Then ; Both attempts failed - show detailed error to user DebugLog("SafeLogFileOpen: ❌ Complete failure!", $LOG_ERROR) ; Show tray notification TrayTip("Log File Error", _ "Cannot create log file!" & @CRLF & _ "Possible causes:" & @CRLF & _ "- Disk full" & @CRLF & _ "- Permission denied", 10, 3) ; Show detailed message box in debug mode If $g_bDebugMode Then MsgBox($MB_ICONERROR + $MB_TOPMOST, "Log File Error", _ "Failed to create log file!" & @CRLF & @CRLF & _ "Path: " & $sLogPath & @CRLF & @CRLF & _ "Possible causes:" & @CRLF & _ "- Disk full" & @CRLF & _ "- Insufficient permissions" & @CRLF & _ "- Folder locked by another process", 10) EndIf Return -1 EndIf ; Temp directory succeeded DebugLog("SafeLogFileOpen: ✓ Using temp directory", $LOG_WARN) TrayTip("Log Warning", _ "Log file created in temp:" & @CRLF & $sLogPath, 5, 2) EndIf Return $hFile EndFunc ; ============================================ ; Clear all tooltip displays ; ============================================ Func ClearAllTooltips() ToolTip("") EndFunc ; ============================================ ; Validate that a PID is valid (positive integer) ; ============================================ Func IsValidPID($iPID) Return $iPID > 0 And IsInt($iPID) EndFunc ; ============================================ ; Build command line to run script (supports both .au3 and .exe) ; ============================================ ; Constructs proper command line whether running as compiled .exe ; or as .au3 script (which requires AutoIt3.exe) ; ; Parameters: ; $sArgs - String: Optional command line arguments to append ; ; Returns: ; String: Complete command line with quotes and arguments ; ============================================ Func GetScriptCommand($sArgs = "") Local $sCmd = "" ; Check if running as compiled executable If @Compiled Then ; Running as .exe - just quote the path $sCmd = '"' & @ScriptFullPath & '"' Else ; Running as .au3 script - need AutoIt3.exe $sCmd = '"' & @AutoItExe & '" "' & @ScriptFullPath & '"' EndIf ; Append arguments if provided If $sArgs <> "" Then $sCmd &= " " & $sArgs Return $sCmd EndFunc ; ============================================ ; Calculate retry delay with exponential backoff ; ============================================ ; Returns delay in milliseconds for retry attempts ; If exponential backoff is enabled: 2s, 4s, 8s, 16s, 32s (capped at 30s) ; If disabled: constant 2s delay ; ; Parameters: ; $iRetryCount - Integer: Current retry attempt number (0-based) ; ; Returns: ; Integer: Delay in milliseconds ; ============================================ Func GetRetryDelay($iRetryCount) ; Use constant delay if exponential backoff disabled If Not $RETRY_EXPONENTIAL Then Return $RETRY_BASE_DELAY ; Calculate exponential delay: base * (2 ^ attempt) Local $iDelay = $RETRY_BASE_DELAY * (2 ^ $iRetryCount) ; Cap at 30 seconds to prevent excessive delays If $iDelay > 30000 Then $iDelay = 30000 Return $iDelay EndFunc ; ============================================ ; Check if retry counter should be reset based on uptime ; ============================================ ; If process has been running stably for RETRY_RESET_TIMEOUT, ; reset the retry counter to give it fresh retry attempts ; ; Parameters: ; $iStartTime - Float: TimerInit() value when process started ; ; Returns: ; Boolean: True if counter should be reset, False otherwise ; ============================================ Func ShouldResetRetryCounter($iStartTime) ; Can't reset if no start time recorded If $iStartTime = 0 Then Return False ; Calculate uptime and compare to threshold Return TimerDiff($iStartTime) >= $RETRY_RESET_TIMEOUT EndFunc #EndRegion #Region === RETRY LOGIC FUNCTIONS === ; ============================================ ; Check and reset retry counter if process ran long enough ; ============================================ ; Resets retry counter to 0 if process has been running ; stably for RETRY_RESET_TIMEOUT milliseconds ; ; Parameters: ; $aState - Array (ByRef): Process state array ; $sName - String: Function name for logging ; ; Returns: ; Boolean: True if counter was reset, False otherwise ; ============================================ Func ResetRetryCounterIfNeeded(ByRef $aState, $sName) ; Check if process has run long enough to reset counter If ShouldResetRetryCounter($aState[$STATE_STARTTIME]) Then DebugLog($sName & " ran stable > " & Round($RETRY_RESET_TIMEOUT/60000, 1) & "min, resetting retry counter", $LOG_INFO) $aState[$STATE_RETRY] = 0 Return True EndIf Return False EndFunc ; ============================================ ; Attempt to restart crashed process with retry logic ; ============================================ ; Implements intelligent retry with: ; - Maximum retry count check ; - Manual disable detection ; - Exponential backoff delay ; - Continuous monitoring during delay (fixes race condition) ; ; Parameters: ; $sName - String: Function name for logging and UI ; $aState - Array (ByRef): Process state array ; $fnSetState - String: Name of state management function to call ; ; Returns: ; Boolean: True if restart was attempted, False if skipped ; ============================================ Func TryRestartProcess($sName, ByRef $aState, $fnSetState) ; Check if auto-restart is enabled globally If Not $AUTO_RESTART_ENABLED Then DebugLog($sName & " Auto-restart disabled in config", $LOG_WARN) $aState[$STATE_RETRY] = 0 Return False EndIf ; Don't restart if user manually disabled If $aState[$STATE_MANUAL] Then DebugLog($sName & " Skipping restart (manual disable)", $LOG_INFO) $aState[$STATE_RETRY] = 0 Return False EndIf ; Check if max retry count reached If $aState[$STATE_RETRY] >= $MAX_RETRY_COUNT Then DebugLog("❌ " & $sName & " exceeded max retry count: " & $MAX_RETRY_COUNT, $LOG_ERROR) TrayTip($sName & " Auto-Restart FAILED", _ "Reached maximum retry count: " & $MAX_RETRY_COUNT, 5, 3) $aState[$STATE_RETRY] = 0 Return False EndIf ; Increment retry counter $aState[$STATE_RETRY] += 1 Local $iDelay = GetRetryDelay($aState[$STATE_RETRY] - 1) ; Log and notify user DebugLog("🔄 " & $sName & " Auto-Restart attempt " & $aState[$STATE_RETRY] & "/" & $MAX_RETRY_COUNT, $LOG_WARN) TrayTip($sName & " Auto-Restart", _ "Retry " & $aState[$STATE_RETRY] & "/" & $MAX_RETRY_COUNT & @CRLF & _ "Waiting " & Round($iDelay/1000, 1) & " seconds...", 3, 2) ; Wait with continuous manual-disable checking (fixes race condition) ; Instead of Sleep($iDelay), we check every RETRY_CHECK_INTERVAL Local $iElapsed = 0 While $iElapsed < $iDelay ; Check if user manually disabled during wait If $aState[$STATE_MANUAL] Then DebugLog(" Restart cancelled (manual disable during wait)", $LOG_WARN) $aState[$STATE_RETRY] = 0 Return False EndIf ; Sleep in small increments for responsiveness Sleep($RETRY_CHECK_INTERVAL) $iElapsed += $RETRY_CHECK_INTERVAL WEnd ; Attempt restart DebugLog(" Initiating restart...", $LOG_INFO) Call($fnSetState, True, "Auto-Restart") Return True EndFunc ; ============================================ ; Handle process crash with retry logic ; ============================================ ; Called when a child process is detected as crashed/dead ; Coordinates the crash response: ; 1. Reset retry counter if process ran long enough ; 2. Disable the crashed process ; 3. Attempt auto-restart if applicable ; ; Parameters: ; $sName - String: Function name for logging ; $aState - Array (ByRef): Process state array ; $fnSetState - String: Name of state management function ; ; Returns: None ; ============================================ Func HandleProcessCrash($sName, ByRef $aState, $fnSetState) ; Log crash with PID for debugging DebugLog("⚠⚠⚠ " & $sName & " CRASHED (PID:" & $aState[$STATE_PID] & ")", $LOG_ERROR) ; Reset retry counter if process ran long enough ResetRetryCounterIfNeeded($aState, $sName) ; Disable the crashed process (updates UI and state) Call($fnSetState, False, "Auto-Detect (Process Died)") ; Attempt auto-restart with retry logic TryRestartProcess($sName, $aState, $fnSetState) EndFunc #EndRegion #Region === PROCESS MANAGEMENT FUNCTIONS === ; ============================================ ; Create child process with logging ; ============================================ ; Spawns a new child process using Run() ; Logs command and result for debugging ; ; Parameters: ; $sName - String: Function name for logging ; $sArgs - String: Command line arguments (e.g., "/func5 1234") ; ; Returns: ; Integer: Process ID if successful, 0 if failed ; ============================================ Func CreateProcess($sName, $sArgs = "") ; Build full command line Local $sCmd = GetScriptCommand($sArgs) DebugLog($sName & " Creating child process...", $LOG_INFO) ; Spawn process (hidden window) Local $iPID = Run($sCmd, @ScriptDir, @SW_HIDE) ; Log result If $iPID > 0 Then DebugLog(" ✓ Process created with PID: " & $iPID, $LOG_INFO) Else DebugLog(" ❌ Process creation failed", $LOG_ERROR) EndIf Return $iPID EndFunc ; ============================================ ; Update tray menu UI when process is enabled ; ============================================ ; Updates menu item text to show PID and retry info ; Shows tray tip notification ; ; Parameters: ; $idMenu - Integer: Menu item ID ; $sName - String: Function name ; $iPID - Integer: Process ID ; $iRetry - Integer: Current retry count ; $sSource - String: Source of enable action ; ; Returns: None ; ============================================ Func UpdateProcessUI_Enable($idMenu, $sName, $iPID, $iRetry, $sSource) ; Build retry info string if applicable Local $sInfo = $iRetry > 0 ? (@CRLF & "Retry: " & $iRetry & "/" & $MAX_RETRY_COUNT) : "" ; Update menu item to show enabled state and PID TrayItemSetText($idMenu, "☑ " & $sName & " [PID:" & $iPID & "]") ; Show notification TrayTip($sName & " ENABLED", _ "Source: " & $sSource & @CRLF & _ "PID: " & $iPID & $sInfo, 3, 1) EndFunc ; ============================================ ; Update tray menu UI when process is disabled ; ============================================ ; Updates menu item text to show disabled state ; Shows tray tip notification ; ; Parameters: ; $idMenu - Integer: Menu item ID ; $sName - String: Function name ; $sHotkey - String: Hotkey string for display ; $sSource - String: Source of disable action ; ; Returns: None ; ============================================ Func UpdateProcessUI_Disable($idMenu, $sName, $sHotkey, $sSource) ; Update menu item to show disabled state TrayItemSetText($idMenu, "☐ " & $sName & " [" & $sHotkey & "]") ; Show notification TrayTip($sName & " DISABLED", _ "Source: " & $sSource, 2, 2) EndFunc ; ============================================ ; Check if disable action was manual (user-initiated) ; ============================================ ; Determines if the disable came from user interaction ; (tray menu click or hotkey) vs automatic (crash detection) ; ; Parameters: ; $sSource - String: Source identifier ; $sHotkey - String: Hotkey string to check against ; ; Returns: ; Boolean: True if manual, False if automatic ; ============================================ Func IsManualDisable($sSource, $sHotkey) Return $sSource = "TrayMenu" Or $sSource = "HotKey " & $sHotkey EndFunc #EndRegion #Region === COMMAND LINE MODE === ; ============================================ ; Check command line arguments and enter process mode if applicable ; ============================================ ; Examines command line arguments to determine if this instance ; should run as a child process instead of the main framework ; ; Command line format: ; MyScript.exe /func5 <ParentPID> ; MyScript.exe /func6 <ParentPID> ; ; If process mode is detected, this function enters the process ; loop and never returns (exits when done) ; ; Returns: None (returns to main program if not in process mode) ; ============================================ Func CheckCommandLineMode() ; No arguments = run as main framework If $CmdLine[0] = 0 Then Return ; Parse command line arguments Local $sMode = StringLower($CmdLine[1]) ; Mode switch (/func5 or /func6) ; Extract and validate parent PID (second argument) Local $iParentPID = 0 If $CmdLine[0] >= 2 And IsInt($CmdLine[2]) Then $iParentPID = Int($CmdLine[2]) EndIf ; Check mode and enter appropriate process loop Switch $sMode Case "/func5", "-func5" DebugLog("=== CHILD PROCESS MODE: FUNC5 ===", $LOG_INFO) RunProcessMode("Func5", $iParentPID, 1000) ; Run Func5 loop Exit ; Never reached unless error Case "/func6", "-func6" DebugLog("=== CHILD PROCESS MODE: FUNC6 ===", $LOG_INFO) RunProcessMode("Func6", $iParentPID, 2000) ; Run Func6 loop Exit ; Never reached unless error EndSwitch ; Not a recognized process mode - continue to main framework EndFunc ; ============================================ ; Run in child process mode (continuous loop) ; ============================================ ; This is the main loop for child processes ; Features: ; - Validates parent PID and exits early if invalid ; - Continuously monitors parent process ; - Logs to separate file ; - Executes function-specific logic ; - Graceful shutdown support via stop file ; ; Parameters: ; $sName - String: Function name (e.g., "Func5") ; $iParentPID - Integer: Parent process ID to monitor ; $iInterval - Integer: Loop interval in milliseconds ; ; Returns: None (exits when done) ; ============================================ Func RunProcessMode($sName, $iParentPID, $iInterval) ; ===== SETUP LOGGING ===== ; Create separate log file for this child process Local $sLogPath = @ScriptDir & "\" & StringLower($sName) & "_process.log" Local $hLog = SafeLogFileOpen($sLogPath, 2) ; Log startup information DebugLog($sName & " child process starting...", $LOG_INFO) DebugLog(" Mode: " & (@Compiled ? "COMPILED (.exe)" : "SCRIPT (.au3)"), $LOG_INFO) DebugLog(" Child PID: " & @AutoItPID, $LOG_INFO) DebugLog(" Parent PID: " & $iParentPID, $LOG_INFO) DebugLog(" Interval: " & $iInterval & "ms", $LOG_INFO) ; Write header to log file If $hLog <> -1 Then FileWriteLine($hLog, "=== " & $sName & " PROCESS START ===") FileWriteLine($hLog, "Mode: " & (@Compiled ? "EXE" : "SCRIPT")) FileWriteLine($hLog, "Child PID: " & @AutoItPID & " | Parent PID: " & $iParentPID) FileWriteLine($hLog, "Interval: " & $iInterval & "ms") FileWriteLine($hLog, "========================================") EndIf ; ===== VALIDATE PARENT PID ===== ; CRITICAL: Exit early if parent PID is invalid ; This prevents orphan processes from running indefinitely If Not IsValidPID($iParentPID) Then DebugLog("❌ FATAL: Invalid parent PID: " & $iParentPID, $LOG_ERROR) If $hLog <> -1 Then FileWriteLine($hLog, "FATAL ERROR: Invalid parent PID - Exiting") FileClose($hLog) EndIf ; Show error to user (child processes can show UI) MsgBox($MB_ICONERROR, $sName & " Process Error", _ "Invalid parent PID: " & $iParentPID & @CRLF & _ "Child process cannot start.", 10) Exit 1 ; Exit with error code EndIf ; Check if parent process exists If Not ProcessExists($iParentPID) Then DebugLog("❌ FATAL: Parent process not found - Exiting", $LOG_ERROR) If $hLog <> -1 Then FileWriteLine($hLog, "FATAL ERROR: Parent process dead - Exiting") FileClose($hLog) EndIf Exit 1 EndIf DebugLog("✓ Parent process validated successfully", $LOG_INFO) ; ===== MAIN PROCESS LOOP ===== Local $iLoop = 0 ; Loop iteration counter Local $iFlush = 0 ; Flush counter (for periodic log flushing) While True $iLoop += 1 $iFlush += 1 ; Build log message with timestamp and iteration Local $sLog = "[" & @HOUR & ":" & @MIN & ":" & @SEC & "] #" & $iLoop & " | " ; ===== CHECK PARENT PROCESS ===== ; This is critical for preventing orphan processes ; If parent died, child should exit immediately If Not ProcessExists($iParentPID) Then $sLog &= "❌ PARENT DIED - EXITING" ConsoleWrite($sLog & @CRLF) If $hLog <> -1 Then FileWriteLine($hLog, $sLog) FileClose($hLog) EndIf DebugLog($sName & " exiting (parent process died)", $LOG_WARN) Exit 0 ; Clean exit EndIf ; Parent is alive - continue processing $sLog &= $sName & " running | Parent OK" ; ===== FUNCTION-SPECIFIC LOGIC ===== ; Add custom logic based on function name Switch $sName Case "Func5" ; Example: Monitor a file If FileExists(@ScriptDir & "\target.txt") Then $sLog &= " | target.txt: EXISTS" EndIf ; Example: Monitor a window If WinExists("[CLASS:Notepad]") Then $sLog &= " | Notepad: OPEN" EndIf Case "Func6" ; Example: Monitor clipboard Local $sClip = ClipGet() If $sClip <> "" Then $sLog &= " | Clipboard: " & StringLen($sClip) & " chars" Else $sLog &= " | Clipboard: EMPTY" EndIf EndSwitch ; Output to console and log file ConsoleWrite($sLog & @CRLF) If $hLog <> -1 Then FileWriteLine($hLog, $sLog) ; Flush log every 10 iterations to balance performance and safety If Mod($iFlush, 10) = 0 Then FileFlush($hLog) $iFlush = 0 EndIf EndIf ; ===== CHECK FOR GRACEFUL SHUTDOWN REQUEST ===== ; Parent can request graceful shutdown by creating stop file Local $sStop = @ScriptDir & "\stop_" & StringLower($sName) & "_process.txt" If FileExists($sStop) Then DebugLog($sName & " received graceful shutdown signal", $LOG_INFO) If $hLog <> -1 Then FileWriteLine($hLog, "Graceful shutdown requested via stop file") FileClose($hLog) EndIf ; Clean up stop file and exit cleanly FileDelete($sStop) Exit 0 EndIf ; Wait before next iteration Sleep($iInterval) WEnd ; This line should never be reached (infinite loop) ; If we get here, something went wrong If $hLog <> -1 Then FileClose($hLog) Exit 1 EndFunc #EndRegion #Region === HOTKEYS & TIMERS === ; ============================================ ; Register all hotkeys for keyboard shortcuts ; ============================================ Func SetupHotKeys() HotKeySet($HOTKEY_FUNC1, "HotKey_ToggleFunc1") HotKeySet($HOTKEY_FUNC2, "HotKey_ToggleFunc2") HotKeySet($HOTKEY_FUNC3, "HotKey_ExecuteFunc3") HotKeySet($HOTKEY_FUNC4, "HotKey_ExecuteFunc4") HotKeySet($HOTKEY_FUNC5, "HotKey_ToggleFunc5") HotKeySet($HOTKEY_FUNC6, "HotKey_ToggleFunc6") HotKeySet($HOTKEY_DEBUG, "HotKey_ToggleDebug") HotKeySet($HOTKEY_EXIT, "HotKey_Exit") DebugLog("Hotkeys registered successfully", $LOG_INFO) EndFunc ; ============================================ ; Unregister all hotkeys (cleanup) ; ============================================ Func UnregisterHotKeys() HotKeySet($HOTKEY_FUNC1) HotKeySet($HOTKEY_FUNC2) HotKeySet($HOTKEY_FUNC3) HotKeySet($HOTKEY_FUNC4) HotKeySet($HOTKEY_FUNC5) HotKeySet($HOTKEY_FUNC6) HotKeySet($HOTKEY_DEBUG) HotKeySet($HOTKEY_EXIT) EndFunc ; ============================================ ; Register Adlib timers ; ============================================ ; Only registers CheckProcessStatus globally ; Function-specific timers are registered dynamically ; when functions are enabled (for CPU efficiency) ; ============================================ Func RegisterTimers() ; Only register process monitoring timer ; In-process function timers are registered dynamically AdlibRegister("CheckProcessStatus", $PROCESS_CHECK_INTERVAL) DebugLog("Timers registered successfully", $LOG_INFO) EndFunc #EndRegion #Region === TRAY MENU === ; ============================================ ; Initialize system tray menu ; ============================================ ; Creates tray icon and menu structure with: ; - In-process function toggles ; - Separate process function toggles ; - One-time function executes ; - Debug mode toggle ; - Exit option ; ============================================ Func InitTrayMenu() ; Configure tray menu mode Opt("TrayMenuMode", 3) ; No default menu, custom only Opt("TrayOnEventMode", 1) ; Use OnEvent mode for menu items ; Set tray icon and tooltip TraySetIcon(@SystemDir & "\shell32.dll", 16) TraySetToolTip("AutoIt Framework v" & $VERSION) ; ===== IN-PROCESS FUNCTIONS SECTION ===== TrayCreateItem("=== In-Process Functions ===") TrayItemSetState(-1, $TRAY_DISABLE) ; Header item (disabled) $idFunc1Toggle = TrayCreateItem("☐ Function 1 [" & $HOTKEY_FUNC1 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleFunc1") $idFunc2Toggle = TrayCreateItem("☐ Function 2 [" & $HOTKEY_FUNC2 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleFunc2") TrayCreateItem("") ; Separator ; ===== SEPARATE PROCESS FUNCTIONS SECTION ===== TrayCreateItem("=== Separate Process Functions ===") TrayItemSetState(-1, $TRAY_DISABLE) $idFunc5ProcessToggle = TrayCreateItem("☐ Function 5 Process [" & $HOTKEY_FUNC5 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleFunc5") $idFunc6ProcessToggle = TrayCreateItem("☐ Function 6 Process [" & $HOTKEY_FUNC6 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleFunc6") TrayCreateItem("") ; ===== ONE-TIME FUNCTIONS SECTION ===== TrayCreateItem("=== One-Time Functions ===") TrayItemSetState(-1, $TRAY_DISABLE) $idFunc3Execute = TrayCreateItem("▶ Execute Function 3 [" & $HOTKEY_FUNC3 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ExecuteFunc3") $idFunc4Execute = TrayCreateItem("▶ Execute Function 4 [" & $HOTKEY_FUNC4 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ExecuteFunc4") TrayCreateItem("") ; ===== SYSTEM OPTIONS ===== $idDebugToggle = TrayCreateItem("☑ Debug Mode [" & $HOTKEY_DEBUG & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleDebug") TrayCreateItem("") $idExit = TrayCreateItem("Exit Framework [" & $HOTKEY_EXIT & "]") TrayItemSetOnEvent(-1, "TrayMenu_Exit") ; Show tray icon TraySetState($TRAY_ICONSTATE_SHOW) ; Store menu IDs in state arrays for UI updates $g_aFunc5State[$STATE_MENUID] = $idFunc5ProcessToggle $g_aFunc6State[$STATE_MENUID] = $idFunc6ProcessToggle DebugLog("Tray menu initialized successfully", $LOG_INFO) EndFunc #EndRegion #Region === STATE MANAGEMENT === ; ============================================ ; Manage state for Function 1 (in-process) ; ============================================ ; Toggles Function 1 on/off with: ; - Dynamic Adlib registration (CPU efficient) ; - Timer initialization for accurate timing ; - Counter reset ; - UI updates ; ; Parameters: ; $bNew - Boolean: New state (True = enable, False = disable) ; $sSrc - String: Source of state change (for logging) ; ; Returns: None ; ============================================ Func SetFunc1State($bNew, $sSrc = "Unknown") ; Skip if state unchanged If $g_bFunc1_Enabled = $bNew Then Return $g_bFunc1_Enabled = $bNew If $bNew Then ; ===== ENABLE FUNCTION 1 ===== ; Reset counter and initialize timer $g_iFunc1_RunCount = 0 $g_hFunc1_Timer = TimerInit() ; Register Adlib timer (dynamic for CPU efficiency) ; Only register if not already registered If Not $g_bFunc1_AdlibRegistered Then AdlibRegister("ContinuousFunc1", $FUNC1_INTERVAL) $g_bFunc1_AdlibRegistered = True DebugLog("Function 1: Adlib timer registered", $LOG_INFO) EndIf ; Update UI TrayItemSetText($idFunc1Toggle, "☑ Function 1 [" & $HOTKEY_FUNC1 & "]") TrayTip("Function 1 ENABLED", "Source: " & $sSrc, 2, 1) DebugLog("Function 1 enabled from " & $sSrc, $LOG_INFO) Else ; ===== DISABLE FUNCTION 1 ===== ; Unregister Adlib timer to save CPU If $g_bFunc1_AdlibRegistered Then AdlibUnRegister("ContinuousFunc1") $g_bFunc1_AdlibRegistered = False DebugLog("Function 1: Adlib timer unregistered", $LOG_INFO) EndIf ; Update UI and clear tooltips TrayItemSetText($idFunc1Toggle, "☐ Function 1 [" & $HOTKEY_FUNC1 & "]") TrayTip("Function 1 DISABLED", "Source: " & $sSrc, 2, 2) DebugLog("Function 1 disabled from " & $sSrc, $LOG_INFO) ClearAllTooltips() EndIf EndFunc ; ============================================ ; Manage state for Function 2 (in-process) ; ============================================ ; Same as SetFunc1State but for Function 2 ; See SetFunc1State documentation for details ; ============================================ Func SetFunc2State($bNew, $sSrc = "Unknown") If $g_bFunc2_Enabled = $bNew Then Return $g_bFunc2_Enabled = $bNew If $bNew Then $g_iFunc2_RunCount = 0 $g_hFunc2_Timer = TimerInit() If Not $g_bFunc2_AdlibRegistered Then AdlibRegister("ContinuousFunc2", $FUNC2_INTERVAL) $g_bFunc2_AdlibRegistered = True DebugLog("Function 2: Adlib timer registered", $LOG_INFO) EndIf TrayItemSetText($idFunc2Toggle, "☑ Function 2 [" & $HOTKEY_FUNC2 & "]") TrayTip("Function 2 ENABLED", "Source: " & $sSrc, 2, 1) DebugLog("Function 2 enabled from " & $sSrc, $LOG_INFO) Else If $g_bFunc2_AdlibRegistered Then AdlibUnRegister("ContinuousFunc2") $g_bFunc2_AdlibRegistered = False DebugLog("Function 2: Adlib timer unregistered", $LOG_INFO) EndIf TrayItemSetText($idFunc2Toggle, "☐ Function 2 [" & $HOTKEY_FUNC2 & "]") TrayTip("Function 2 DISABLED", "Source: " & $sSrc, 2, 2) DebugLog("Function 2 disabled from " & $sSrc, $LOG_INFO) ClearAllTooltips() EndIf EndFunc ; ============================================ ; Manage state for Function 5 (separate process) ; ============================================ ; Uses struct-based state management ($g_aFunc5State) ; Enables/disables child process with: ; - Process creation/termination ; - Retry counter management ; - Manual disable tracking ; - UI updates ; ; Parameters: ; $bNew - Boolean: New state (True = enable, False = disable) ; $sSrc - String: Source of state change ; ; Returns: None ; ============================================ Func SetFunc5ProcessState($bNew, $sSrc = "Unknown") ; Skip if state unchanged If $g_aFunc5State[$STATE_ENABLED] = $bNew Then Return If $bNew Then ; ===== ENABLE FUNCTION 5 PROCESS ===== ; Create child process with parent PID Local $iPID = CreateProcess("Func5", "/func5 " & @AutoItPID) If $iPID > 0 Then ; Process created successfully - update all state $g_aFunc5State[$STATE_ENABLED] = True $g_aFunc5State[$STATE_PID] = $iPID $g_aFunc5State[$STATE_STARTTIME] = TimerInit() ; For uptime tracking $g_aFunc5State[$STATE_MANUAL] = False ; Not manually disabled ; Reset retry counter unless this is an auto-restart ; (preserve retry count during auto-restart sequence) If $sSrc <> "Auto-Restart" Then $g_aFunc5State[$STATE_RETRY] = 0 DebugLog("Func5: Retry counter reset", $LOG_INFO) EndIf ; Update UI with current state UpdateProcessUI_Enable($g_aFunc5State[$STATE_MENUID], _ "Func5", $iPID, $g_aFunc5State[$STATE_RETRY], $sSrc) DebugLog("Func5 enabled from " & $sSrc & _ " | PID:" & $iPID & _ " | Retry:" & $g_aFunc5State[$STATE_RETRY], $LOG_INFO) Else ; Process creation failed TrayTip("Func5 Process ERROR", _ "Failed to create child process" & @CRLF & _ "Check logs for details", 3, 3) DebugLog("❌ Func5 process creation failed", $LOG_ERROR) EndIf Else ; ===== DISABLE FUNCTION 5 PROCESS ===== $g_aFunc5State[$STATE_ENABLED] = False ; Track if this was a manual disable ; Manual disables prevent auto-restart If IsManualDisable($sSrc, $HOTKEY_FUNC5) Then $g_aFunc5State[$STATE_MANUAL] = True $g_aFunc5State[$STATE_RETRY] = 0 DebugLog("Func5: Manually disabled (auto-restart blocked)", $LOG_INFO) EndIf ; Terminate child process gracefully (multiple fallback methods) SafeProcessClose($g_aFunc5State[$STATE_PID], $PROCESS_SHUTDOWN_TIMEOUT) ; Update UI UpdateProcessUI_Disable($g_aFunc5State[$STATE_MENUID], _ "Func5", $HOTKEY_FUNC5, $sSrc) DebugLog("Func5 disabled from " & $sSrc, $LOG_INFO) $g_aFunc5State[$STATE_PID] = 0 ; Clear PID EndIf EndFunc ; ============================================ ; Manage state for Function 6 (separate process) ; ============================================ ; Same as SetFunc5ProcessState but for Function 6 ; Uses struct-based state management ($g_aFunc6State) ; See SetFunc5ProcessState documentation for details ; ============================================ Func SetFunc6ProcessState($bNew, $sSrc = "Unknown") If $g_aFunc6State[$STATE_ENABLED] = $bNew Then Return If $bNew Then Local $iPID = CreateProcess("Func6", "/func6 " & @AutoItPID) If $iPID > 0 Then $g_aFunc6State[$STATE_ENABLED] = True $g_aFunc6State[$STATE_PID] = $iPID $g_aFunc6State[$STATE_STARTTIME] = TimerInit() $g_aFunc6State[$STATE_MANUAL] = False If $sSrc <> "Auto-Restart" Then $g_aFunc6State[$STATE_RETRY] = 0 DebugLog("Func6: Retry counter reset", $LOG_INFO) EndIf UpdateProcessUI_Enable($g_aFunc6State[$STATE_MENUID], _ "Func6", $iPID, $g_aFunc6State[$STATE_RETRY], $sSrc) DebugLog("Func6 enabled from " & $sSrc & _ " | PID:" & $iPID & _ " | Retry:" & $g_aFunc6State[$STATE_RETRY], $LOG_INFO) Else TrayTip("Func6 Process ERROR", _ "Failed to create child process", 3, 3) DebugLog("❌ Func6 process creation failed", $LOG_ERROR) EndIf Else $g_aFunc6State[$STATE_ENABLED] = False If IsManualDisable($sSrc, $HOTKEY_FUNC6) Then $g_aFunc6State[$STATE_MANUAL] = True $g_aFunc6State[$STATE_RETRY] = 0 DebugLog("Func6: Manually disabled (auto-restart blocked)", $LOG_INFO) EndIf SafeProcessClose($g_aFunc6State[$STATE_PID], $PROCESS_SHUTDOWN_TIMEOUT) UpdateProcessUI_Disable($g_aFunc6State[$STATE_MENUID], _ "Func6", $HOTKEY_FUNC6, $sSrc) DebugLog("Func6 disabled from " & $sSrc, $LOG_INFO) $g_aFunc6State[$STATE_PID] = 0 EndIf EndFunc ; ============================================ ; Monitor child processes and handle crashes ; ============================================ ; Called periodically by Adlib timer ; Checks if child processes are still running ; Triggers crash handling and auto-restart if needed ; ============================================ Func CheckProcessStatus() ; Check Function 5 process If $g_aFunc5State[$STATE_ENABLED] And Not ProcessExists($g_aFunc5State[$STATE_PID]) Then ; Process is enabled but PID doesn't exist = crash detected HandleProcessCrash("Func5", $g_aFunc5State, "SetFunc5ProcessState") EndIf ; Check Function 6 process If $g_aFunc6State[$STATE_ENABLED] And Not ProcessExists($g_aFunc6State[$STATE_PID]) Then HandleProcessCrash("Func6", $g_aFunc6State, "SetFunc6ProcessState") EndIf EndFunc #EndRegion #Region === EVENT HANDLERS === ; ============================================ ; Tray Menu Event Handlers ; ============================================ ; These functions are called when user clicks tray menu items Func TrayMenu_ToggleFunc1() SetFunc1State(Not $g_bFunc1_Enabled, "TrayMenu") EndFunc Func TrayMenu_ToggleFunc2() SetFunc2State(Not $g_bFunc2_Enabled, "TrayMenu") EndFunc Func TrayMenu_ToggleFunc5() SetFunc5ProcessState(Not $g_aFunc5State[$STATE_ENABLED], "TrayMenu") EndFunc Func TrayMenu_ToggleFunc6() SetFunc6ProcessState(Not $g_aFunc6State[$STATE_ENABLED], "TrayMenu") EndFunc Func TrayMenu_ExecuteFunc3() ExecuteFunc3("TrayMenu") EndFunc Func TrayMenu_ExecuteFunc4() ExecuteFunc4("TrayMenu") EndFunc Func TrayMenu_ToggleDebug() $g_bDebugMode = Not $g_bDebugMode TrayItemSetText($idDebugToggle, _ ($g_bDebugMode ? "☑" : "☐") & " Debug Mode [" & $HOTKEY_DEBUG & "]") DebugLog("Debug mode " & ($g_bDebugMode ? "ENABLED" : "DISABLED"), $LOG_INFO) EndFunc Func TrayMenu_Exit() ExitProgram("TrayMenu") EndFunc ; ============================================ ; Hotkey Event Handlers ; ============================================ ; These functions are called when user presses hotkey combinations Func HotKey_ToggleFunc1() SetFunc1State(Not $g_bFunc1_Enabled, "HotKey " & $HOTKEY_FUNC1) EndFunc Func HotKey_ToggleFunc2() SetFunc2State(Not $g_bFunc2_Enabled, "HotKey " & $HOTKEY_FUNC2) EndFunc Func HotKey_ToggleFunc5() SetFunc5ProcessState(Not $g_aFunc5State[$STATE_ENABLED], "HotKey " & $HOTKEY_FUNC5) EndFunc Func HotKey_ToggleFunc6() SetFunc6ProcessState(Not $g_aFunc6State[$STATE_ENABLED], "HotKey " & $HOTKEY_FUNC6) EndFunc Func HotKey_ExecuteFunc3() ExecuteFunc3("HotKey " & $HOTKEY_FUNC3) EndFunc Func HotKey_ExecuteFunc4() ExecuteFunc4("HotKey " & $HOTKEY_FUNC4) EndFunc Func HotKey_ToggleDebug() TrayMenu_ToggleDebug() EndFunc Func HotKey_Exit() ExitProgram("HotKey " & $HOTKEY_EXIT) EndFunc #EndRegion #Region === CONTINUOUS FUNCTIONS (IN-PROCESS) === ; ============================================ ; Function 1 - Continuous execution (in-process) ; ============================================ ; Called by Adlib timer when enabled ; Features: ; - Re-entrancy protection (mutex) ; - Accurate timing measurement with TimerDiff ; - Auto-stop after configured run count ; - Tooltip display with status ; ; Returns: None ; ============================================ Func ContinuousFunc1() ; Check if function is enabled If Not $g_bFunc1_Enabled Then ClearAllTooltips() Return EndIf ; Re-entrancy protection: skip if already running ; This prevents the function from being called again ; while a previous call is still executing If $g_bFunc1_Running Then DebugLog("⚠ Func1 skipped (already running)", $LOG_WARN) Return EndIf ; Set mutex lock $g_bFunc1_Running = True $g_iFunc1_RunCount += 1 ; Calculate accurate elapsed time since last run ; This accounts for any drift in Adlib timing Local $iElapsed = TimerDiff($g_hFunc1_Timer) DebugLog("Func1 execution #" & $g_iFunc1_RunCount & _ " (elapsed: " & Round($iElapsed) & "ms)", $LOG_INFO) ; Build status display Local $sStatus = "Function 1 (In-Process)" & @CRLF $sStatus &= "Runs: " & $g_iFunc1_RunCount & "/" & $FUNC1_AUTO_STOP & @CRLF $sStatus &= "Elapsed: " & Round($iElapsed) & "ms" ; Check for auto-stop condition If $g_iFunc1_RunCount >= $FUNC1_AUTO_STOP Then $sStatus &= @CRLF & ">>> AUTO-STOPPING <<<" ToolTip($sStatus, 0, 0, "Func1 - Stopping", 2) ; Auto-disable function SetFunc1State(False, "Auto-Stop (max runs)") $g_bFunc1_Running = False Return EndIf ; Display status tooltip ToolTip($sStatus, 0, 0, "Function 1", 1) ; Reset timer for next iteration (ensures accurate timing) $g_hFunc1_Timer = TimerInit() ; Release mutex lock $g_bFunc1_Running = False EndFunc ; ============================================ ; Function 2 - Continuous execution (in-process) ; ============================================ ; Same as ContinuousFunc1 but with different interval and auto-stop ; See ContinuousFunc1 documentation for details ; ============================================ Func ContinuousFunc2() If Not $g_bFunc2_Enabled Then ClearAllTooltips() Return EndIf If $g_bFunc2_Running Then DebugLog("⚠ Func2 skipped (already running)", $LOG_WARN) Return EndIf $g_bFunc2_Running = True $g_iFunc2_RunCount += 1 Local $iElapsed = TimerDiff($g_hFunc2_Timer) DebugLog("Func2 execution #" & $g_iFunc2_RunCount & _ " (elapsed: " & Round($iElapsed) & "ms)", $LOG_INFO) Local $sStatus = "Function 2 (In-Process)" & @CRLF $sStatus &= "Runs: " & $g_iFunc2_RunCount & "/" & $FUNC2_AUTO_STOP & @CRLF $sStatus &= "Elapsed: " & Round($iElapsed) & "ms" If $g_iFunc2_RunCount >= $FUNC2_AUTO_STOP Then $sStatus &= @CRLF & ">>> AUTO-STOPPING <<<" ToolTip($sStatus, 0, 70, "Func2 - Stopping", 2) SetFunc2State(False, "Auto-Stop (max runs)") $g_bFunc2_Running = False Return EndIf ToolTip($sStatus, 0, 70, "Function 2", 1) $g_hFunc2_Timer = TimerInit() $g_bFunc2_Running = False EndFunc #EndRegion #Region === ONE-TIME FUNCTIONS === ; ============================================ ; Function 3 - Execute once ; ============================================ ; Triggered by tray menu or hotkey ; Features: ; - Re-entrancy protection ; - Example: Display clipboard content ; ; Parameters: ; $sSrc - String: Source of execution (for logging) ; ; Returns: None ; ============================================ Func ExecuteFunc3($sSrc = "Unknown") ; Re-entrancy protection If $g_bFunc3_Running Then DebugLog("⚠ Func3 skipped (already running)", $LOG_WARN) TrayTip("Function 3", "Already executing...", 1, 2) Return EndIf ; Set mutex and log execution $g_bFunc3_Running = True DebugLog("Func3 executed from " & $sSrc, $LOG_INFO) TrayTip("Function 3", "Executing from " & $sSrc, 2, 1) ; Simulate some processing time Sleep(1000) ; Example functionality: Display clipboard content Local $sClip = ClipGet() If $sClip <> "" Then ; Show first 100 characters of clipboard MsgBox($MB_ICONINFORMATION, "Function 3 - Clipboard", _ "Clipboard content:" & @CRLF & _ StringLeft($sClip, 100) & @CRLF & @CRLF & _ "Source: " & $sSrc, 5) Else MsgBox($MB_ICONINFORMATION, "Function 3", _ "Executed successfully!" & @CRLF & _ "Source: " & $sSrc, 3) EndIf ; Release mutex $g_bFunc3_Running = False EndFunc ; ============================================ ; Function 4 - Execute once ; ============================================ ; Similar to Function 3 ; Example: Display system information ; ; Parameters: ; $sSrc - String: Source of execution ; ; Returns: None ; ============================================ Func ExecuteFunc4($sSrc = "Unknown") If $g_bFunc4_Running Then DebugLog("⚠ Func4 skipped (already running)", $LOG_WARN) TrayTip("Function 4", "Already executing...", 1, 2) Return EndIf $g_bFunc4_Running = True DebugLog("Func4 executed from " & $sSrc, $LOG_INFO) TrayTip("Function 4", "Executing from " & $sSrc, 2, 1) Sleep(800) ; Example functionality: Display system info Local $sInfo = "System Information:" & @CRLF & @CRLF $sInfo &= "Computer: " & @ComputerName & @CRLF $sInfo &= "User: " & @UserName & @CRLF $sInfo &= "OS: " & @OSVersion & @CRLF $sInfo &= "Script: " & @ScriptName & @CRLF & @CRLF $sInfo &= "Executed from: " & $sSrc MsgBox($MB_ICONINFORMATION, "Function 4 - System Info", $sInfo, 5) $g_bFunc4_Running = False EndFunc #EndRegion #Region === EXIT & MAIN LOOP === ; ============================================ ; Gracefully exit the framework ; ============================================ ; Cleanup sequence: ; 1. Unregister hotkeys ; 2. Close all child processes (with multiple fallback methods) ; 3. Clear UI elements ; 4. Exit program ; ; Parameters: ; $sSrc - String: Source of exit request (for logging) ; ; Returns: None (exits program) ; ============================================ Func ExitProgram($sSrc = "Unknown") DebugLog("========================================", $LOG_INFO) DebugLog("Framework exiting from " & $sSrc, $LOG_INFO) DebugLog("========================================", $LOG_INFO) ; Unregister all hotkeys UnregisterHotKeys() ; Close all child processes with graceful shutdown If $g_aFunc5State[$STATE_ENABLED] Or ProcessExists($g_aFunc5State[$STATE_PID]) Then DebugLog("Closing Func5 process (PID:" & $g_aFunc5State[$STATE_PID] & ")", $LOG_INFO) SafeProcessClose($g_aFunc5State[$STATE_PID], $PROCESS_SHUTDOWN_TIMEOUT) EndIf If $g_aFunc6State[$STATE_ENABLED] Or ProcessExists($g_aFunc6State[$STATE_PID]) Then DebugLog("Closing Func6 process (PID:" & $g_aFunc6State[$STATE_PID] & ")", $LOG_INFO) SafeProcessClose($g_aFunc6State[$STATE_PID], $PROCESS_SHUTDOWN_TIMEOUT) EndIf ; Clear UI elements $g_bRunning = False ClearAllTooltips() DebugLog("Framework exit complete", $LOG_INFO) Exit EndFunc ; ============================================ ; Main program loop ; ============================================ ; Simple loop that keeps program running ; All work is done by Adlib timers and event handlers ; ============================================ Func MainLoop() While $g_bRunning Sleep(100) WEnd EndFunc #EndRegion #Region === MAIN PROGRAM === ; ============================================ ; Framework startup sequence ; ============================================ DebugLog("========================================", $LOG_INFO) DebugLog("AutoIt Framework v" & $VERSION & " STARTING", $LOG_INFO) DebugLog("========================================", $LOG_INFO) DebugLog("Process ID: " & @AutoItPID, $LOG_INFO) DebugLog("Run mode: " & (@Compiled ? "COMPILED (.exe)" : "SCRIPT (.au3)"), $LOG_INFO) ; Check if running in child process mode ; If command line arguments detected, enter process mode and never return CheckCommandLineMode() ; Main framework mode - setup UI and enter main loop SetupHotKeys() RegisterTimers() InitTrayMenu() ; Show welcome notification Local $sWelcome = "AutoIt Framework v" & $VERSION & @CRLF & @CRLF $sWelcome &= "Mode: " & (@Compiled ? "COMPILED" : "SCRIPT") & @CRLF & @CRLF $sWelcome &= "In-Process: " & $HOTKEY_FUNC1 & "/" & $HOTKEY_FUNC2 & @CRLF $sWelcome &= "Process: " & $HOTKEY_FUNC5 & "/" & $HOTKEY_FUNC6 & @CRLF $sWelcome &= "One-Time: " & $HOTKEY_FUNC3 & "/" & $HOTKEY_FUNC4 & @CRLF & @CRLF $sWelcome &= "Right-click tray icon for menu" TrayTip("Framework Ready!", $sWelcome, 10, 1) DebugLog("Framework ready - entering main loop", $LOG_INFO) ; Enter main loop (exits only when $g_bRunning = False) MainLoop() #EndRegion ! Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal
Trong Posted October 9 Author Posted October 9 Mini ver: expandcollapse popup#NoTrayIcon #include <TrayConstants.au3> #include <MsgBoxConstants.au3> #include <Misc.au3> ; AutoIt Framework v2.1 - Simplified (No Auto-Stop) ; Features: In-process functions, one-time functions, logging, hotkeys, tray menu ; Author: Dao Van Trong - TRONG.PRO ; License: Open Source _Singleton("AutoItFrameworkV2_" & @ScriptName, 0) #Region === CONSTANTS & CONFIG === Global Const $VERSION = "2.1" Global Const $FUNC1_INTERVAL = 1000 Global Const $FUNC2_INTERVAL = 2000 Global Const $MAX_LOG_SIZE = 1048576 Global Const $MAX_LOG_BACKUPS = 3 Global Enum $LOG_INFO = 1, $LOG_WARN = 2, $LOG_ERROR = 3 Global Const $HOTKEY_FUNC1 = "^!1" Global Const $HOTKEY_FUNC2 = "^!2" Global Const $HOTKEY_FUNC3 = "^!3" Global Const $HOTKEY_FUNC4 = "^!4" Global Const $HOTKEY_DEBUG = "^!d" Global Const $HOTKEY_EXIT = "^!q" #EndRegion #Region === GLOBAL VARIABLES === Global $g_bRunning = True Global $g_bDebugMode = True Global $g_iLogLevel = $LOG_INFO Global $g_bFunc1_Enabled = False Global $g_bFunc2_Enabled = False Global $g_bFunc1_AdlibRegistered = False Global $g_bFunc2_AdlibRegistered = False Global $g_bFunc1_Running = False Global $g_bFunc2_Running = False Global $g_bFunc3_Running = False Global $g_bFunc4_Running = False Global $g_iFunc1_RunCount = 0 Global $g_iFunc2_RunCount = 0 Global $g_hFunc1_Timer = 0 Global $g_hFunc2_Timer = 0 Global $idFunc1Toggle, $idFunc2Toggle, $idFunc3Execute, $idFunc4Execute, $idDebugToggle, $idExit #EndRegion #Region === UTILITY FUNCTIONS === Func DebugLog($sMessage, $iLevel = $LOG_INFO) If Not $g_bDebugMode Or $iLevel < $g_iLogLevel Then Return Local $sPrefix = $iLevel = $LOG_INFO ? "[INFO] " : $iLevel = $LOG_WARN ? "[WARN] " : "[ERROR] " Local $sTime = @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC ConsoleWrite("[" & $sTime & "] " & $sPrefix & $sMessage & @CRLF) EndFunc Func RotateLogFile($sLogPath) If Not FileExists($sLogPath) Then Return Local $iSize = FileGetSize($sLogPath) If $iSize < $MAX_LOG_SIZE Then Return DebugLog("RotateLogFile: Rotating " & Round($iSize/1024, 2) & " KB", $LOG_INFO) Local $sOldest = $sLogPath & "." & $MAX_LOG_BACKUPS If FileExists($sOldest) Then FileDelete($sOldest) For $i = $MAX_LOG_BACKUPS - 1 To 1 Step -1 Local $sOld = $sLogPath & "." & $i Local $sNew = $sLogPath & "." & ($i + 1) If FileExists($sOld) Then FileMove($sOld, $sNew, 1) Next FileMove($sLogPath, $sLogPath & ".1", 1) EndFunc Func SafeLogFileOpen($sLogPath, $iMode = 2) RotateLogFile($sLogPath) Local $hFile = FileOpen($sLogPath, $iMode) If $hFile = -1 Then DebugLog("SafeLogFileOpen: Failed to open " & $sLogPath, $LOG_ERROR) $sLogPath = @TempDir & "\" & StringRegExpReplace($sLogPath, "^.*\\", "") $hFile = FileOpen($sLogPath, $iMode) If $hFile = -1 Then DebugLog("SafeLogFileOpen: Complete failure!", $LOG_ERROR) TrayTip("Log File Error", "Cannot create log file! Possible causes: Disk full, Permission denied", 10, 3) Return -1 EndIf DebugLog("SafeLogFileOpen: Using temp directory", $LOG_WARN) EndIf Return $hFile EndFunc Func UpdateTooltip($sFuncName, $sStatus, $iX, $iY, $iIcon) If ($sFuncName = "Function 1" And $g_bFunc1_Enabled) Or ($sFuncName = "Function 2" And $g_bFunc2_Enabled) Then ToolTip($sStatus, $iX, $iY, $sFuncName, $iIcon) Else ToolTip("", $iX, $iY, $sFuncName) EndIf EndFunc #EndRegion #Region === HOTKEYS === Func SetupHotKeys() HotKeySet($HOTKEY_FUNC1, "HotKey_ToggleFunc1") HotKeySet($HOTKEY_FUNC2, "HotKey_ToggleFunc2") HotKeySet($HOTKEY_FUNC3, "HotKey_ExecuteFunc3") HotKeySet($HOTKEY_FUNC4, "HotKey_ExecuteFunc4") HotKeySet($HOTKEY_DEBUG, "HotKey_ToggleDebug") HotKeySet($HOTKEY_EXIT, "HotKey_Exit") DebugLog("Hotkeys registered", $LOG_INFO) EndFunc Func UnregisterHotKeys() HotKeySet($HOTKEY_FUNC1) HotKeySet($HOTKEY_FUNC2) HotKeySet($HOTKEY_FUNC3) HotKeySet($HOTKEY_FUNC4) HotKeySet($HOTKEY_DEBUG) HotKeySet($HOTKEY_EXIT) EndFunc #EndRegion #Region === TRAY MENU === Func InitTrayMenu() Opt("TrayMenuMode", 3) Opt("TrayOnEventMode", 1) TraySetIcon(@SystemDir & "\shell32.dll", 16) TraySetToolTip("AutoIt Framework v" & $VERSION) TrayCreateItem("=== In-Process Functions ===") TrayItemSetState(-1, $TRAY_DISABLE) $idFunc1Toggle = TrayCreateItem("☐ Function 1 [" & $HOTKEY_FUNC1 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleFunc1") $idFunc2Toggle = TrayCreateItem("☐ Function 2 [" & $HOTKEY_FUNC2 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleFunc2") TrayCreateItem("") TrayCreateItem("=== One-Time Functions ===") TrayItemSetState(-1, $TRAY_DISABLE) $idFunc3Execute = TrayCreateItem("▶ Execute Function 3 [" & $HOTKEY_FUNC3 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ExecuteFunc3") $idFunc4Execute = TrayCreateItem("▶ Execute Function 4 [" & $HOTKEY_FUNC4 & "]") TrayItemSetOnEvent(-1, "TrayMenu_ExecuteFunc4") TrayCreateItem("") $idDebugToggle = TrayCreateItem("☑ Debug Mode [" & $HOTKEY_DEBUG & "]") TrayItemSetOnEvent(-1, "TrayMenu_ToggleDebug") TrayCreateItem("") $idExit = TrayCreateItem("Exit Framework [" & $HOTKEY_EXIT & "]") TrayItemSetOnEvent(-1, "TrayMenu_Exit") TraySetState($TRAY_ICONSTATE_SHOW) DebugLog("Tray menu initialized", $LOG_INFO) EndFunc #EndRegion #Region === STATE MANAGEMENT === Func SetFunc1State($bNew, $sSrc = "Unknown") If $g_bFunc1_Enabled = $bNew Then Return $g_bFunc1_Enabled = $bNew If $bNew Then $g_iFunc1_RunCount = 0 $g_hFunc1_Timer = TimerInit() If Not $g_bFunc1_AdlibRegistered Then AdlibRegister("ContinuousFunc1", $FUNC1_INTERVAL) $g_bFunc1_AdlibRegistered = True DebugLog("Function 1: Adlib timer registered", $LOG_INFO) EndIf TrayItemSetText($idFunc1Toggle, "☑ Function 1 [" & $HOTKEY_FUNC1 & "]") TrayTip("Function 1 ENABLED", "Source: " & $sSrc, 2, 1) DebugLog("Function 1 enabled from " & $sSrc, $LOG_INFO) Else If $g_bFunc1_AdlibRegistered Then AdlibUnRegister("ContinuousFunc1") $g_bFunc1_AdlibRegistered = False DebugLog("Function 1: Adlib timer unregistered", $LOG_INFO) EndIf TrayItemSetText($idFunc1Toggle, "☐ Function 1 [" & $HOTKEY_FUNC1 & "]") TrayTip("Function 1 DISABLED", "Source: " & $sSrc, 2, 2) DebugLog("Function 1 disabled from " & $sSrc, $LOG_INFO) UpdateTooltip("Function 1", "", 0, 0, 1) EndIf EndFunc Func SetFunc2State($bNew, $sSrc = "Unknown") If $g_bFunc2_Enabled = $bNew Then Return $g_bFunc2_Enabled = $bNew If $bNew Then $g_iFunc2_RunCount = 0 $g_hFunc2_Timer = TimerInit() If Not $g_bFunc2_AdlibRegistered Then AdlibRegister("ContinuousFunc2", $FUNC2_INTERVAL) $g_bFunc2_AdlibRegistered = True DebugLog("Function 2: Adlib timer registered", $LOG_INFO) EndIf TrayItemSetText($idFunc2Toggle, "☑ Function 2 [" & $HOTKEY_FUNC2 & "]") TrayTip("Function 2 ENABLED", "Source: " & $sSrc, 2, 1) DebugLog("Function 2 enabled from " & $sSrc, $LOG_INFO) Else If $g_bFunc2_AdlibRegistered Then AdlibUnRegister("ContinuousFunc2") $g_bFunc2_AdlibRegistered = False DebugLog("Function 2: Adlib timer unregistered", $LOG_INFO) EndIf TrayItemSetText($idFunc2Toggle, "☐ Function 2 [" & $HOTKEY_FUNC2 & "]") TrayTip("Function 2 DISABLED", "Source: " & $sSrc, 2, 2) DebugLog("Function 2 disabled from " & $sSrc, $LOG_INFO) UpdateTooltip("Function 2", "", 0, 70, 1) EndIf EndFunc #EndRegion #Region === EVENT HANDLERS === Func TrayMenu_ToggleFunc1() SetFunc1State(Not $g_bFunc1_Enabled, "TrayMenu") EndFunc Func TrayMenu_ToggleFunc2() SetFunc2State(Not $g_bFunc2_Enabled, "TrayMenu") EndFunc Func TrayMenu_ExecuteFunc3() ExecuteFunc3("TrayMenu") EndFunc Func TrayMenu_ExecuteFunc4() ExecuteFunc4("TrayMenu") EndFunc Func TrayMenu_ToggleDebug() $g_bDebugMode = Not $g_bDebugMode TrayItemSetText($idDebugToggle, ($g_bDebugMode ? "☑" : "☐") & " Debug Mode [" & $HOTKEY_DEBUG & "]") DebugLog("Debug mode " & ($g_bDebugMode ? "ENABLED" : "DISABLED"), $LOG_INFO) EndFunc Func TrayMenu_Exit() ExitProgram("TrayMenu") EndFunc Func HotKey_ToggleFunc1() SetFunc1State(Not $g_bFunc1_Enabled, "HotKey " & $HOTKEY_FUNC1) EndFunc Func HotKey_ToggleFunc2() SetFunc2State(Not $g_bFunc2_Enabled, "HotKey " & $HOTKEY_FUNC2) EndFunc Func HotKey_ExecuteFunc3() ExecuteFunc3("HotKey " & $HOTKEY_FUNC3) EndFunc Func HotKey_ExecuteFunc4() ExecuteFunc4("HotKey " & $HOTKEY_FUNC4) EndFunc Func HotKey_ToggleDebug() TrayMenu_ToggleDebug() EndFunc Func HotKey_Exit() ExitProgram("HotKey " & $HOTKEY_EXIT) EndFunc #EndRegion #Region === CONTINUOUS FUNCTIONS === Func ContinuousFunc1() If Not $g_bFunc1_Enabled Then UpdateTooltip("Function 1", "", 0, 0, 1) Return EndIf If $g_bFunc1_Running Then DebugLog("Func1 skipped (already running)", $LOG_WARN) Return EndIf $g_bFunc1_Running = True $g_iFunc1_RunCount += 1 Local $iElapsed = TimerDiff($g_hFunc1_Timer) DebugLog("Func1 execution #" & $g_iFunc1_RunCount & " (elapsed: " & Round($iElapsed) & "ms)", $LOG_INFO) Local $sStatus = "Function 1 (In-Process)" & @CRLF & "Runs: " & $g_iFunc1_RunCount & @CRLF & "Elapsed: " & Round($iElapsed) & "ms" UpdateTooltip("Function 1", $sStatus, 0, 0, 1) $g_hFunc1_Timer = TimerInit() $g_bFunc1_Running = False EndFunc Func ContinuousFunc2() If Not $g_bFunc2_Enabled Then UpdateTooltip("Function 2", "", 0, 70, 1) Return EndIf If $g_bFunc2_Running Then DebugLog("Func2 skipped (already running)", $LOG_WARN) Return EndIf $g_bFunc2_Running = True $g_iFunc2_RunCount += 1 Local $iElapsed = TimerDiff($g_hFunc2_Timer) DebugLog("Func2 execution #" & $g_iFunc2_RunCount & " (elapsed: " & Round($iElapsed) & "ms)", $LOG_INFO) Local $sStatus = "Function 2 (In-Process)" & @CRLF & "Runs: " & $g_iFunc2_RunCount & @CRLF & "Elapsed: " & Round($iElapsed) & "ms" UpdateTooltip("Function 2", $sStatus, 0, 70, 1) $g_hFunc2_Timer = TimerInit() $g_bFunc2_Running = False EndFunc #EndRegion #Region === ONE-TIME FUNCTIONS === Func ExecuteFunc3($sSrc = "Unknown") If $g_bFunc3_Running Then DebugLog("Func3 skipped (already running)", $LOG_WARN) TrayTip("Function 3", "Already executing...", 1, 2) Return EndIf $g_bFunc3_Running = True DebugLog("Func3 executed from " & $sSrc, $LOG_INFO) TrayTip("Function 3", "Executing from " & $sSrc, 2, 1) Sleep(1000) Local $sClip = ClipGet() If $sClip <> "" Then MsgBox($MB_ICONINFORMATION, "Function 3 - Clipboard", "Clipboard content:" & @CRLF & StringLeft($sClip, 100) & @CRLF & @CRLF & "Source: " & $sSrc, 5) Else MsgBox($MB_ICONINFORMATION, "Function 3", "Executed successfully!" & @CRLF & "Source: " & $sSrc, 3) EndIf $g_bFunc3_Running = False EndFunc Func ExecuteFunc4($sSrc = "Unknown") If $g_bFunc4_Running Then DebugLog("Func4 skipped (already running)", $LOG_WARN) TrayTip("Function 4", "Already executing...", 1, 2) Return EndIf $g_bFunc4_Running = True DebugLog("Func4 executed from " & $sSrc, $LOG_INFO) TrayTip("Function 4", "Executing from " & $sSrc, 2, 1) Sleep(800) Local $sInfo = "System Information:" & @CRLF & @CRLF & "Computer: " & @ComputerName & @CRLF & "User: " & @UserName & @CRLF & "OS: " & @OSVersion & @CRLF & "Script: " & @ScriptName & @CRLF & @CRLF & "Executed from: " & $sSrc MsgBox($MB_ICONINFORMATION, "Function 4 - System Info", $sInfo, 5) $g_bFunc4_Running = False EndFunc #EndRegion #Region === EXIT & MAIN LOOP === Func ExitProgram($sSrc = "Unknown") DebugLog("Framework exiting from " & $sSrc, $LOG_INFO) UnregisterHotKeys() If $g_bFunc1_AdlibRegistered Then AdlibUnRegister("ContinuousFunc1") If $g_bFunc2_AdlibRegistered Then AdlibUnRegister("ContinuousFunc2") $g_bRunning = False UpdateTooltip("Function 1", "", 0, 0, 1) UpdateTooltip("Function 2", "", 0, 70, 1) DebugLog("Framework exit complete", $LOG_INFO) Exit EndFunc Func MainLoop() While $g_bRunning Sleep(100) WEnd EndFunc #EndRegion #Region === MAIN PROGRAM === DebugLog("AutoIt Framework v" & $VERSION & " STARTING", $LOG_INFO) DebugLog("Process ID: " & @AutoItPID, $LOG_INFO) SetupHotKeys() InitTrayMenu() Local $sWelcome = "AutoIt Framework v" & $VERSION & @CRLF & @CRLF & "Mode: " & (@Compiled ? "COMPILED" : "SCRIPT") & @CRLF & @CRLF & "In-Process: " & $HOTKEY_FUNC1 & "/" & $HOTKEY_FUNC2 & @CRLF & "One-Time: " & $HOTKEY_FUNC3 & "/" & $HOTKEY_FUNC4 & @CRLF & @CRLF & "Right-click tray icon for menu" TrayTip("Framework Ready!", $sWelcome, 10, 1) DebugLog("Framework ready - entering main loop", $LOG_INFO) MainLoop() #EndRegion Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now