Jump to content

Recommended Posts

Posted (edited)

I want to share a framework that I am using in my "Taskbar Program" project.

; ===============================================================================================================================
; 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):

; ===============================================================================================================================
; 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 by Trong

Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal

  • 3 weeks later...
Posted (edited)

v2: Added AutoStart functionality

; ===============================================================================================================================
; 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

; ======================================================================
; 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 by Trong
fix Au3Stripper directive

Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal

Posted

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.

Posted
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!

Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal

  • 1 month later...
Posted

Simple version:

#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

Posted

Mini ver:

#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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...