Jump to content

Recommended Posts

Posted (edited)

UDF:

#include-once
#include <Array.au3>
#include <Misc.au3>

; #INDEX# =======================================================================================================================
; Title .........: Enhanced Hotkey UDF
; AutoIt Version : 3.3.16.1+
; Description ...: non-blocking hotkey system
; Author(s) .....: Dao Van Trong - TRONG.PRO
; Version .......: 1.0
; ===============================================================================================================================

; #CURRENT# =====================================================================================================================
; _HotkeySet
; _HotkeyCheck
; _HotkeyRemove
; _HotkeyClearAll
; _HotkeyGetRegistered
; _HotkeySetDebugMode
; _HotkeyGetStats
; _HotkeySetMultiPressWindow
; ===============================================================================================================================

; #CONSTANTS# ===================================================================================================================
Global Const $HOTKEY_FLAG_REPEAT = 1
Global Const $HOTKEY_FLAG_RELEASE = 2
Global Const $HOTKEY_FLAG_HOLD = 4
Global Const $HOTKEY_MOD_CTRL = 1
Global Const $HOTKEY_MOD_ALT = 2
Global Const $HOTKEY_MOD_SHIFT = 4
Global Const $HOTKEY_MOD_WIN = 8
; ===============================================================================================================================

; #VARIABLES# ===================================================================================================================
Global $g_aHotkey_Map[0][6]                 ; [HashKey, Hotkey, Function, Flags, LastTrigger, HoldStart]
Global $g_aHotkey_TrackedVKs[0]             ; Only VK codes being monitored
Global $g_aHotkey_VKStates[256]             ; Current key states
Global $g_aHotkey_VKPressTimes[256]         ; Key press timestamps
Global $g_aHotkey_ModStates[4]              ; [Ctrl, Alt, Shift, Win]
Global $g_aHotkey_HexCache[256]             ; Pre-computed hex strings
Global $g_aHotkey_PressHistory[0][11]       ; [VK][0-10 timestamps]
Global $g_iHotkey_LastCleanup = 0
Global $g_iHotkey_CleanupInterval = 1000
Global $g_iHotkey_HoldThreshold = 500
Global $g_iHotkey_RepeatDelay = 100
Global $g_iHotkey_MultiPressWindow = 800    ; Increased for better usability
Global $g_bHotkey_DebugMode = False
Global $g_iHotkey_CheckCount = 0
Global $g_iHotkey_TriggerCount = 0
; ===============================================================================================================================

; #INITIALIZATION# ==============================================================================================================
For $i = 0 To 255
    $g_aHotkey_VKStates[$i] = False
    $g_aHotkey_VKPressTimes[$i] = 0
    $g_aHotkey_HexCache[$i] = Hex($i, 2)
Next
For $i = 0 To 3
    $g_aHotkey_ModStates[$i] = False
Next
; ===============================================================================================================================

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeySet
; Description ...: Registers a non-blocking hotkey
; Syntax ........: _HotkeySet($sHotkey, $sFunction[, $iFlags = 0[, $iMultiPress = 1]])
; Parameters ....: $sHotkey     - Hotkey string (e.g., "^a", "!{F4}", "{SPACE}")
;                  $sFunction   - Function name to call when triggered
;                  $iFlags      - [optional] Combination of HOTKEY_FLAG_* constants (default: 0)
;                  $iMultiPress - [optional] Number of press-release cycles required (default: 1)
; Return values .: Success - True
;                  Failure - False, @error set:
;                            |1 - Invalid hotkey format
;                            |2 - Invalid parameters
;                            |3 - Invalid virtual key code
; ===============================================================================================================================
Func _HotkeySet($sHotkey, $sFunction, $iFlags = 0, $iMultiPress = 1)
    If Not IsString($sHotkey) Or $sHotkey = "" Then Return SetError(1, 0, False)
    If Not IsString($sFunction) Or $sFunction = "" Then Return SetError(2, 0, False)
    If $iMultiPress < 1 Or $iMultiPress > 10 Then Return SetError(2, 0, False)

    Local $iModifiers = __Hotkey_ParseModifiers($sHotkey)
    Local $sKey = __Hotkey_StripModifiers($sHotkey)
    Local $iVK = __Hotkey_GetVirtualKeyCode($sKey)

    If $iVK = 0 Then Return SetError(3, 0, False)

    Local $sHashKey = __Hotkey_GetHashKey($iVK, $iModifiers, $iMultiPress)

    If _ArraySearch($g_aHotkey_TrackedVKs, $iVK) = -1 Then
        _ArrayAdd($g_aHotkey_TrackedVKs, $iVK)
        __Hotkey_InitPressHistory($iVK)
    EndIf

    Local $iIndex = __Hotkey_FindInMap($sHashKey)
    If $iIndex >= 0 Then
        $g_aHotkey_Map[$iIndex][1] = $sHotkey
        $g_aHotkey_Map[$iIndex][2] = $sFunction
        $g_aHotkey_Map[$iIndex][3] = $iFlags
        $g_aHotkey_Map[$iIndex][4] = TimerInit()
        $g_aHotkey_Map[$iIndex][5] = 0
    Else
        ReDim $g_aHotkey_Map[UBound($g_aHotkey_Map) + 1][6]
        Local $iNewIndex = UBound($g_aHotkey_Map) - 1
        $g_aHotkey_Map[$iNewIndex][0] = $sHashKey
        $g_aHotkey_Map[$iNewIndex][1] = $sHotkey
        $g_aHotkey_Map[$iNewIndex][2] = $sFunction
        $g_aHotkey_Map[$iNewIndex][3] = $iFlags
        $g_aHotkey_Map[$iNewIndex][4] = TimerInit()
        $g_aHotkey_Map[$iNewIndex][5] = 0
    EndIf

    If $g_bHotkey_DebugMode Then
        Local $sMulti = ($iMultiPress > 1) ? " (" & $iMultiPress & "x)" : ""
        ConsoleWrite("[HotkeyUDF] Registered: " & $sHotkey & $sMulti & " -> " & $sFunction & " (VK=0x" & $g_aHotkey_HexCache[$iVK] & ")" & @CRLF)
    EndIf

    Return True
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeyCheck
; Description ...: Checks and triggers registered hotkeys (call in main loop)
; Syntax ........: _HotkeyCheck()
; Return values .: Number of hotkeys triggered
; ===============================================================================================================================
Func _HotkeyCheck()
    If UBound($g_aHotkey_TrackedVKs) = 0 Then Return 0

    $g_iHotkey_CheckCount += 1
    Local $iTriggered = 0
    Local $iNow = TimerInit()

    $g_aHotkey_ModStates[0] = _IsPressed("11")
    $g_aHotkey_ModStates[1] = _IsPressed("12")
    $g_aHotkey_ModStates[2] = _IsPressed("10")
    $g_aHotkey_ModStates[3] = _IsPressed("5B")

    For $i = 0 To UBound($g_aHotkey_TrackedVKs) - 1
        Local $iVK = $g_aHotkey_TrackedVKs[$i]
        Local $bNowPressed = _IsPressed($g_aHotkey_HexCache[$iVK])
        Local $bWasPressed = $g_aHotkey_VKStates[$iVK]

        Local $bJustPressed = $bNowPressed And Not $bWasPressed
        Local $bJustReleased = Not $bNowPressed And $bWasPressed

        If $bJustPressed Then
            $g_aHotkey_VKPressTimes[$iVK] = $iNow
        EndIf

        If $bJustReleased Then
            __Hotkey_RecordPressRelease($iVK, $iNow)
        EndIf

        $g_aHotkey_VKStates[$iVK] = $bNowPressed

        $iTriggered += __Hotkey_CheckTriggersForVK($iVK, $bNowPressed, $bJustPressed, $bJustReleased, $iNow)
    Next

    If TimerDiff($g_iHotkey_LastCleanup) > $g_iHotkey_CleanupInterval Then
        __Hotkey_CleanupHistory($iNow)
        $g_iHotkey_LastCleanup = $iNow
    EndIf

    Return $iTriggered
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeyRemove
; Description ...: Removes a registered hotkey
; ===============================================================================================================================
Func _HotkeyRemove($sHotkey, $iMultiPress = 1)
    Local $iModifiers = __Hotkey_ParseModifiers($sHotkey)
    Local $sKey = __Hotkey_StripModifiers($sHotkey)
    Local $iVK = __Hotkey_GetVirtualKeyCode($sKey)

    If $iVK = 0 Then Return False

    Local $sHashKey = __Hotkey_GetHashKey($iVK, $iModifiers, $iMultiPress)
    Return __Hotkey_RemoveFromMap($sHashKey)
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeyClearAll
; Description ...: Clears all registered hotkeys
; ===============================================================================================================================
Func _HotkeyClearAll()
    Local $iCount = UBound($g_aHotkey_Map)

    ReDim $g_aHotkey_Map[0][6]
    ReDim $g_aHotkey_TrackedVKs[0]
    ReDim $g_aHotkey_PressHistory[0][11]

    For $i = 0 To 255
        $g_aHotkey_VKStates[$i] = False
        $g_aHotkey_VKPressTimes[$i] = 0
    Next

    If $g_bHotkey_DebugMode Then
        ConsoleWrite("[HotkeyUDF] Cleared all hotkeys (" & $iCount & " removed)" & @CRLF)
    EndIf

    Return $iCount
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeyGetRegistered
; Description ...: Returns array of all registered hotkey strings
; ===============================================================================================================================
Func _HotkeyGetRegistered()
    Local $aHotkeys[UBound($g_aHotkey_Map)]
    For $i = 0 To UBound($g_aHotkey_Map) - 1
        $aHotkeys[$i] = $g_aHotkey_Map[$i][1]
    Next
    Return $aHotkeys
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeySetDebugMode
; Description ...: Enables or disables debug logging
; ===============================================================================================================================
Func _HotkeySetDebugMode($bEnabled = True)
    Local $bPrevious = $g_bHotkey_DebugMode
    $g_bHotkey_DebugMode = $bEnabled
    ConsoleWrite("[HotkeyUDF] Debug mode " & ($bEnabled ? "enabled" : "disabled") & @CRLF)
    Return $bPrevious
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeyGetStats
; Description ...: Returns performance statistics
; ===============================================================================================================================
Func _HotkeyGetStats()
    Local $sStats = ""
    $sStats &= "Registered Hotkeys: " & UBound($g_aHotkey_Map) & @CRLF
    $sStats &= "Tracked VKs: " & UBound($g_aHotkey_TrackedVKs) & @CRLF
    $sStats &= "Total Checks: " & $g_iHotkey_CheckCount & @CRLF
    $sStats &= "Total Triggers: " & $g_iHotkey_TriggerCount & @CRLF
    Local $fAvg = ($g_iHotkey_CheckCount > 0) ? Round($g_iHotkey_TriggerCount / $g_iHotkey_CheckCount, 4) : 0
    $sStats &= "Avg Triggers/Check: " & $fAvg & @CRLF
    $sStats &= "Multi-Press Window: " & $g_iHotkey_MultiPressWindow & "ms"
    Return $sStats
EndFunc

; #FUNCTION# ====================================================================================================================
; Name ..........: _HotkeySetMultiPressWindow
; Description ...: Sets the time window for multi-press detection
; ===============================================================================================================================
Func _HotkeySetMultiPressWindow($iMilliseconds)
    If $iMilliseconds < 100 Or $iMilliseconds > 2000 Then Return SetError(1, 0, False)
    $g_iHotkey_MultiPressWindow = $iMilliseconds
    If $g_bHotkey_DebugMode Then
        ConsoleWrite("[HotkeyUDF] Multi-press window set to " & $iMilliseconds & "ms" & @CRLF)
    EndIf
    Return True
EndFunc

; #INTERNAL_USE_ONLY# ===========================================================================================================
Func __Hotkey_FindInMap($sHashKey)
    For $i = 0 To UBound($g_aHotkey_Map) - 1
        If $g_aHotkey_Map[$i][0] = $sHashKey Then Return $i
    Next
    Return -1
EndFunc

Func __Hotkey_RemoveFromMap($sHashKey)
    Local $iIndex = __Hotkey_FindInMap($sHashKey)
    If $iIndex >= 0 Then
        _ArrayDelete($g_aHotkey_Map, $iIndex)
        Return True
    EndIf
    Return False
EndFunc

Func __Hotkey_InitPressHistory($iVK)
    Local $iSize = UBound($g_aHotkey_PressHistory)
    ReDim $g_aHotkey_PressHistory[$iSize + 1][11]
    $g_aHotkey_PressHistory[$iSize][0] = $iVK
    For $i = 1 To 10
        $g_aHotkey_PressHistory[$iSize][$i] = 0
    Next
EndFunc

Func __Hotkey_GetPressHistoryIndex($iVK)
    For $i = 0 To UBound($g_aHotkey_PressHistory) - 1
        If $g_aHotkey_PressHistory[$i][0] = $iVK Then Return $i
    Next
    Return -1
EndFunc

Func __Hotkey_RecordPressRelease($iVK, $iNow)
    Local $iHistIndex = __Hotkey_GetPressHistoryIndex($iVK)
    If $iHistIndex < 0 Then Return

    Local $iEmptySlot = -1
    For $i = 1 To 10
        If $g_aHotkey_PressHistory[$iHistIndex][$i] = 0 Then
            $iEmptySlot = $i
            ExitLoop
        EndIf
    Next

    If $iEmptySlot = -1 Then
        For $i = 1 To 9
            $g_aHotkey_PressHistory[$iHistIndex][$i] = $g_aHotkey_PressHistory[$iHistIndex][$i + 1]
        Next
        $iEmptySlot = 10
    EndIf

    $g_aHotkey_PressHistory[$iHistIndex][$iEmptySlot] = $iNow

    If $g_bHotkey_DebugMode Then
        Local $iCount = __Hotkey_CountRecentCycles($iVK, $iNow)
        ConsoleWrite("[HotkeyUDF] Recorded press-release: VK=0x" & $g_aHotkey_HexCache[$iVK] & _
                     " | Recent cycles: " & $iCount & " | Slot: " & $iEmptySlot & @CRLF)
    EndIf
EndFunc

Func __Hotkey_CountRecentCycles($iVK, $iNow)
    Local $iHistIndex = __Hotkey_GetPressHistoryIndex($iVK)
    If $iHistIndex < 0 Then Return 0

    Local $iValidCount = 0

    For $i = 1 To 10
        Local $iTimestamp = $g_aHotkey_PressHistory[$iHistIndex][$i]
        If $iTimestamp > 0 Then
            Local $iAge = TimerDiff($iTimestamp)
            If $iAge <= $g_iHotkey_MultiPressWindow Then
                $iValidCount += 1
            EndIf
        EndIf
    Next

    Return $iValidCount
EndFunc

Func __Hotkey_ClearPressHistory($iVK, $iClearCount = 0)
    Local $iHistIndex = __Hotkey_GetPressHistoryIndex($iVK)
    If $iHistIndex < 0 Then Return

    If $iClearCount = 0 Then
        For $i = 1 To 10
            $g_aHotkey_PressHistory[$iHistIndex][$i] = 0
        Next

        If $g_bHotkey_DebugMode Then
            ConsoleWrite("[HotkeyUDF] Cleared ALL history: VK=0x" & $g_aHotkey_HexCache[$iVK] & @CRLF)
        EndIf
    Else
        Local $aSorted[10]
        Local $iCount = 0

        For $i = 1 To 10
            If $g_aHotkey_PressHistory[$iHistIndex][$i] > 0 Then
                $aSorted[$iCount] = $g_aHotkey_PressHistory[$iHistIndex][$i]
                $iCount += 1
            EndIf
        Next

        If $iCount > 0 Then
            _ArraySort($aSorted, 0, 0, $iCount - 1)

            Local $iDeleteCount = ($iClearCount > $iCount) ? $iCount : $iClearCount
            For $i = 0 To $iDeleteCount - 1
                Local $iOldest = $aSorted[$i]
                For $j = 1 To 10
                    If $g_aHotkey_PressHistory[$iHistIndex][$j] = $iOldest Then
                        $g_aHotkey_PressHistory[$iHistIndex][$j] = 0
                        ExitLoop
                    EndIf
                Next
            Next

            If $g_bHotkey_DebugMode Then
                ConsoleWrite("[HotkeyUDF] Cleared " & $iDeleteCount & " oldest timestamps: VK=0x" & $g_aHotkey_HexCache[$iVK] & @CRLF)
            EndIf
        EndIf
    EndIf
EndFunc

Func __Hotkey_CleanupHistory($iNow)
    Local $iCleaned = 0

    For $i = 0 To UBound($g_aHotkey_PressHistory) - 1
        For $j = 1 To 10
            If $g_aHotkey_PressHistory[$i][$j] > 0 Then
                Local $iAge = TimerDiff($g_aHotkey_PressHistory[$i][$j])
                If $iAge > $g_iHotkey_MultiPressWindow Then
                    $g_aHotkey_PressHistory[$i][$j] = 0
                    $iCleaned += 1
                EndIf
            EndIf
        Next
    Next

    If $g_bHotkey_DebugMode And $iCleaned > 0 Then
        ConsoleWrite("[HotkeyUDF] Cleanup: Removed " & $iCleaned & " old timestamps" & @CRLF)
    EndIf
EndFunc

Func __Hotkey_CheckTriggersForVK($iVK, $bNowPressed, $bJustPressed, $bJustReleased, $iNow)
    Local $iTriggered = 0

    Local $iCurrentMods = 0
    If $g_aHotkey_ModStates[0] Then $iCurrentMods = BitOR($iCurrentMods, 1)
    If $g_aHotkey_ModStates[1] Then $iCurrentMods = BitOR($iCurrentMods, 2)
    If $g_aHotkey_ModStates[2] Then $iCurrentMods = BitOR($iCurrentMods, 4)
    If $g_aHotkey_ModStates[3] Then $iCurrentMods = BitOR($iCurrentMods, 8)

    For $iMulti = 1 To 10
        Local $sHashKey = __Hotkey_GetHashKey($iVK, $iCurrentMods, $iMulti)
        Local $iMapIndex = __Hotkey_FindInMap($sHashKey)

        If $iMapIndex >= 0 Then
            Local $sHotkey = $g_aHotkey_Map[$iMapIndex][1]
            Local $sFunction = $g_aHotkey_Map[$iMapIndex][2]
            Local $iFlags = $g_aHotkey_Map[$iMapIndex][3]
            Local $iLastTrigger = $g_aHotkey_Map[$iMapIndex][4]

            Local $bShouldTrigger = False
            Local $sTriggerReason = ""

            If $iMulti > 1 Then
                If $bJustReleased Then
                    Local $iCycleCount = __Hotkey_CountRecentCycles($iVK, $iNow)

                    If $g_bHotkey_DebugMode Then
                        ConsoleWrite("[HotkeyUDF] Multi-press check: " & $sHotkey & _
                                     " | VK=0x" & $g_aHotkey_HexCache[$iVK] & _
                                     " | Cycles=" & $iCycleCount & "/" & $iMulti & @CRLF)
                    EndIf

                    If $iCycleCount >= $iMulti Then
                        $bShouldTrigger = True
                        $sTriggerReason = "multi-press " & $iMulti & "x (" & $iCycleCount & " cycles)"
                        __Hotkey_ClearPressHistory($iVK, $iMulti)
                    EndIf
                EndIf
            ElseIf BitAND($iFlags, $HOTKEY_FLAG_HOLD) Then
                If $bNowPressed Then
                    Local $iHoldTime = TimerDiff($g_aHotkey_VKPressTimes[$iVK])
                    If $iHoldTime >= $g_iHotkey_HoldThreshold And TimerDiff($iLastTrigger) > $g_iHotkey_RepeatDelay Then
                        $bShouldTrigger = True
                        $sTriggerReason = "hold " & Round($iHoldTime) & "ms"
                    EndIf
                EndIf
            ElseIf BitAND($iFlags, $HOTKEY_FLAG_RELEASE) Then
                If $bJustReleased Then
                    $bShouldTrigger = True
                    $sTriggerReason = "release"
                EndIf
            Else
                If $bJustPressed Then
                    $bShouldTrigger = True
                    $sTriggerReason = "press"
                ElseIf BitAND($iFlags, $HOTKEY_FLAG_REPEAT) And $bNowPressed And TimerDiff($iLastTrigger) > $g_iHotkey_RepeatDelay Then
                    $bShouldTrigger = True
                    $sTriggerReason = "repeat"
                EndIf
            EndIf

            If $bShouldTrigger Then
                If $g_bHotkey_DebugMode Then
                    ConsoleWrite("[HotkeyUDF] >>> TRIGGERED: '" & $sHotkey & "' (" & $sTriggerReason & ")" & @CRLF)
                EndIf

                $g_aHotkey_Map[$iMapIndex][4] = $iNow

                Call($sFunction)
                If Not @error Then
                    $iTriggered += 1
                    $g_iHotkey_TriggerCount += 1
                Else
                    ConsoleWrite("[HotkeyUDF] ERROR calling '" & $sFunction & "' (@error=" & @error & ")" & @CRLF)
                EndIf
            EndIf
        EndIf
    Next

    Return $iTriggered
EndFunc

Func __Hotkey_GetHashKey($iVK, $iModifiers, $iMultiPress)
    Return "VK" & $iVK & "_M" & $iModifiers & "_MP" & $iMultiPress
EndFunc

Func __Hotkey_ParseModifiers($sKey)
    Local $iModifiers = 0
    Local $sTemp = $sKey

    While StringLen($sTemp) > 0
        Local $sChar = StringLeft($sTemp, 1)
        If Not StringRegExp($sChar, "[!^+#]") Then ExitLoop

        Switch $sChar
            Case "^"
                $iModifiers = BitOR($iModifiers, 1)
            Case "!"
                $iModifiers = BitOR($iModifiers, 2)
            Case "+"
                $iModifiers = BitOR($iModifiers, 4)
            Case "#"
                $iModifiers = BitOR($iModifiers, 8)
        EndSwitch

        $sTemp = StringTrimLeft($sTemp, 1)
    WEnd

    Return $iModifiers
EndFunc

Func __Hotkey_StripModifiers($sKey)
    Local $sResult = $sKey
    While StringLen($sResult) > 0 And StringRegExp(StringLeft($sResult, 1), "[!^+#]")
        $sResult = StringTrimLeft($sResult, 1)
    WEnd
    Return $sResult
EndFunc

Func __Hotkey_GetVirtualKeyCode($sKey)
    If $sKey = "" Then Return 0

    If StringLeft($sKey, 1) = "{" And StringRight($sKey, 1) = "}" Then
        Local $sSpecialKey = StringUpper(StringMid($sKey, 2, StringLen($sKey) - 2))
        If $sSpecialKey = "" Then Return 0

        Static Local $aKeyMap[][2] = [ _
            ["F1", 0x70], ["F2", 0x71], ["F3", 0x72], ["F4", 0x73], _
            ["F5", 0x74], ["F6", 0x75], ["F7", 0x76], ["F8", 0x77], _
            ["F9", 0x78], ["F10", 0x79], ["F11", 0x7A], ["F12", 0x7B], _
            ["ESC", 0x1B], ["ESCAPE", 0x1B], ["SPACE", 0x20], _
            ["ENTER", 0x0D], ["RETURN", 0x0D], ["TAB", 0x09], _
            ["BACKSPACE", 0x08], ["BS", 0x08], ["DELETE", 0x2E], ["DEL", 0x2E], _
            ["INSERT", 0x2D], ["INS", 0x2D], ["HOME", 0x24], ["END", 0x23], _
            ["PGUP", 0x21], ["PGDN", 0x22], ["UP", 0x26], ["DOWN", 0x28], _
            ["LEFT", 0x25], ["RIGHT", 0x27], ["PRINTSCREEN", 0x2C], _
            ["PAUSE", 0x13], ["CAPSLOCK", 0x14], ["NUMLOCK", 0x90], _
            ["SCROLLLOCK", 0x91], ["LWIN", 0x5B], ["RWIN", 0x5C], _
            ["APPS", 0x5D], ["CTRL", 0x11], ["ALT", 0x12], ["SHIFT", 0x10], _
            ["NUMPAD0", 0x60], ["NUMPAD1", 0x61], ["NUMPAD2", 0x62], _
            ["NUMPAD3", 0x63], ["NUMPAD4", 0x64], ["NUMPAD5", 0x65], _
            ["NUMPAD6", 0x66], ["NUMPAD7", 0x67], ["NUMPAD8", 0x68], _
            ["NUMPAD9", 0x69], ["MULTIPLY", 0x6A], ["ADD", 0x6B], _
            ["SUBTRACT", 0x6D], ["DECIMAL", 0x6E], ["DIVIDE", 0x6F] _
        ]

        For $i = 0 To UBound($aKeyMap) - 1
            If $aKeyMap[$i][0] = $sSpecialKey Then Return $aKeyMap[$i][1]
        Next

        Return 0
    EndIf

    If StringLen($sKey) = 1 Then
        Local $sChar = StringUpper($sKey)
        Local $iAscii = Asc($sChar)

        If ($iAscii >= 65 And $iAscii <= 90) Or ($iAscii >= 48 And $iAscii <= 57) Then
            Return $iAscii
        EndIf

        Static Local $aPunct[][2] = [ _
            [" ", 0x20], ["`", 0xC0], ["-", 0xBD], ["=", 0xBB], _
            ["[", 0xDB], ["]", 0xDD], ["\", 0xDC], [";", 0xBA], _
            ["'", 0xDE], [",", 0xBC], [".", 0xBE], ["/", 0xBF] _
        ]

        For $i = 0 To UBound($aPunct) - 1
            If $aPunct[$i][0] = $sKey Then Return $aPunct[$i][1]
        Next
    EndIf

    Return 0
EndFunc
; Dao Van Trong - TRONG.PRO

EG 1:

#include "HotkeyUDF.au3"

ConsoleWrite("Press Ctrl+A FOR test hotkey (ESC for Exit)" & @CRLF & @CRLF)

_HotkeySet("^a", "TestFunction")
_HotkeySet("{F1}", "F1")
_HotkeySet("{F2}", "F2")
_HotkeySet("{F3}", "F3")

While Not _IsPressed("1B")  ; Loop Until ESC
    _HotkeyCheck()
    Sleep(10)
WEnd

Func TestFunction()
    ConsoleWrite("Ctrl+A Is Pressed!" & @CRLF)
EndFunc   ;==>TestFunction

Func F1()
    ConsoleWrite("F1 Is Pressed!" & @CRLF)
EndFunc   ;==>F1

Func F2()
    ConsoleWrite("F2 Is Pressed!" & @CRLF)
EndFunc   ;==>F2

Func F3()
    ConsoleWrite("F3 Is Pressed!" & @CRLF)
EndFunc   ;==>F3

EG 2:

#include "HotkeyUDF.au3"

; ============================================================================
; VÍ DỤ NÂNG CAO - NHIỀU LOẠI HOTKEY
; ============================================================================

Global $iBasicCount = 0
Global $iHoldCount = 0
Global $iReleaseCount = 0
Global $iDoubleCount = 0

ConsoleWrite("============================================" & @CRLF)
ConsoleWrite("  ADVANCED HOTKEY EXAMPLE" & @CRLF)
ConsoleWrite("============================================" & @CRLF & @CRLF)

; Đăng ký nhiều loại hotkey khác nhau
_HotkeySet("^a", "OnBasicPress")                          ; Basic
_HotkeySet("^{SPACE}", "OnHold", $HOTKEY_FLAG_HOLD)       ; Hold
_HotkeySet("{TAB}", "OnRelease", $HOTKEY_FLAG_RELEASE)    ; Release
_HotkeySet("^{CTRL}", "OnDouble", 0, 2)                   ; Double press

ConsoleWrite("Hotkeys:" & @CRLF)
ConsoleWrite("  Ctrl+A       - Basic press" & @CRLF)
ConsoleWrite("  Ctrl+Space   - Hold detection (giữ 500ms)" & @CRLF)
ConsoleWrite("  Tab          - Release detection (thả phím)" & @CRLF)
ConsoleWrite("  Double Ctrl  - Multi-press (bấm 2 lần)" & @CRLF & @CRLF)
ConsoleWrite("Bấm ESC để thoát" & @CRLF)
ConsoleWrite("============================================" & @CRLF & @CRLF)

While Not _IsPressed("1B")
    _HotkeyCheck()
    Sleep(10)
WEnd

ConsoleWrite(@CRLF & "Thống kê:" & @CRLF)
ConsoleWrite("  Basic: " & $iBasicCount & @CRLF)
ConsoleWrite("  Hold: " & $iHoldCount & @CRLF)
ConsoleWrite("  Release: " & $iReleaseCount & @CRLF)
ConsoleWrite("  Double: " & $iDoubleCount & @CRLF)

Func OnBasicPress()
    $iBasicCount += 1
    ConsoleWrite("[BASIC] Ctrl+A - Count: " & $iBasicCount & @CRLF)
EndFunc

Func OnHold()
    $iHoldCount += 1
    ConsoleWrite("[HOLD] Ctrl+Space được giữ - Count: " & $iHoldCount & @CRLF)
EndFunc

Func OnRelease()
    $iReleaseCount += 1
    ConsoleWrite("[RELEASE] Tab được thả - Count: " & $iReleaseCount & @CRLF)
EndFunc

Func OnDouble()
    $iDoubleCount += 1
    ConsoleWrite(@CRLF & "*** [DOUBLE] Ctrl x2 phát hiện! Count: " & $iDoubleCount & " ***" & @CRLF & @CRLF)
    Beep(1000, 100)
    Beep(1200, 100)
EndFunc

 

 

 

Edited by Trong
High performance letter removal.

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

Posted (edited)

Hi @Trong 👋 ,

thank you for sharing your UDF. I didn't tried your examples (and UDF) yet, but I will.

Can you please explain a bit more what makes this UDF "high-performance"? No offence in any way, but I don't get it. Pressing a key or multiple keys is already applied few times in the community and already fast. Did you do a compariison between your approach and the already existing ones? I'm just curious 😀 .

Regarding the set of functions that you provide I can say I like it. I personally would not use much of them, but it can be helpful for some other people - thank you.

Best regards
Sven

Edited by SOLVE-SMART

==> AutoIt related: 🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet🔗 autoit-webdriver-boilerplate

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Posted
20 hours ago, SOLVE-SMART said:

Did you do a compariison between your approach and the already existing ones?

Sorry I forgot to introduce UDF:

Enhanced Hotkey UDF – Non-blocking Hotkey System for AutoIt

The built-in HotKeySet function in AutoIt uses system hooks that can sometimes introduce lag or block the main thread. Enhanced Hotkey UDF by Trong operates entirely non-blocking, handling all key detection in your main script loop through a single, explicit call.

In just one line, you can integrate the hotkey system:

While True
    _HotkeyCheck()
    ; ... your code ...
WEnd

 

Key Features

  • Non-blocking architecture without Windows hooks or extra threads

  • Multi-press support (1–10 presses) with customizable time window

  • Flexible trigger flags:

    • HOTKEY_FLAG_REPEAT for auto-repeat while holding

    • HOTKEY_FLAG_RELEASE for trigger on key release

    • HOTKEY_FLAG_HOLD for trigger after holding a threshold

  • Debug mode and performance stats (checks, triggers, average triggers per check)

  • Easy management: register, remove, clear all, list registered hotkeys

Quick Comparison

 
| Criterion                                  | HotKeySet (AutoIt)                 | Enhanced Hotkey UDF                             |
|-----------------------------------------|-----------------------------------------|---------------------------------------------------------|
| Architecture                           | System-level hook, blocking | Polling-based, non-blocking                    |
| Impact on main thread        | May cause lag                         | Zero blocking, full control                        |
| Multi-press (double, etc.)     | Not supported                       | Supported (1–10 presses)                         |
| Hold detection                       | Not supported                       | Supported with configurable threshold |
| Release detection                  | Not supported                       | Supported                                                   |
| Auto-repeat while holding   | Not supported                       | Supported (`HOTKEY_FLAG_REPEAT`)     |
| Debug and stats                    | No                                            | Yes                                                                |
 

Conclusion

If you need a powerful hotkey manager for scripts, games, or real-time applications that avoids blocking your main thread while offering press, release, hold, repeat, and multi-press detection, the Enhanced Hotkey UDF is the tool of choice. Simply call _HotkeyCheck in your loop and let the UDF handle the rest.

PS: This article was analyzed and created by AI (Copilot).

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

  • Trong changed the title to HotkeyUDF [non-blocking hotkey]
  • 3 weeks later...
Posted

Has this been tested in windows 11, as I have found, not fully tested, that hotkeys running in an app, located in the program files directory, do not pickup, but if I move that same app outside of the program folder, say to the desktop, they work.

All by me:

"Sometimes you have to go back to where you started, to get to where you want to go." 

"Everybody catches up with everyone, eventually" 

"As you teach others, you are really teaching yourself."

From my dad

"Do not worry about yesterday, as the only thing that you can control is tomorrow."

 

WindowsError.gif

WIKI | Tabs; | Arrays; | Strings | Wiki Arrays | How to ask a Question | Forum Search | FAQ | Tutorials | Original FAQ | ONLINE HELP | UDF's Wiki | AutoIt PDF

AutoIt Snippets | Multple Guis | Interrupting a running function | Another Send

StringRegExp | StringRegExp Help | RegEXTester | REG TUTOR | Reg TUTOT 2

AutoItSetOption | Macros | AutoIt Snippets | Wrapper | Autoit  Docs

SCITE | SciteJump | BB | MyTopics | Programming | UDFs | AutoIt 123 | UDFs Form | UDF

Learning to script | Tutorials | Documentation | IE.AU3 | Games? | FreeSoftware | Path_Online | Core Language

Programming Tips

Excel Changes

ControlHover.UDF

GDI_Plus

Draw_On_Screen

GDI Basics

GDI_More_Basics

GDI Rotate

GDI Graph

GDI  CheckExistingItems

GDI Trajectory

Replace $ghGDIPDll with $__g_hGDIPDll

DLL 101?

Array via Object

GDI Swimlane

GDI Plus French 101 Site

GDI Examples UEZ

GDI Basic Clock

GDI Detection

Ternary operator

Posted
On 10/24/2025 at 9:43 AM, nitekram said:

Has this been tested in windows 11, as I have found, not fully tested, that hotkeys running in an app, located in the program files directory, do not pickup, but if I move that same app outside of the program folder, say to the desktop, they work.

I have found that it was not hotkeys that was causing the issue in program folders, but the screencapture that is not allowed, unless you are running as an admin

All by me:

"Sometimes you have to go back to where you started, to get to where you want to go." 

"Everybody catches up with everyone, eventually" 

"As you teach others, you are really teaching yourself."

From my dad

"Do not worry about yesterday, as the only thing that you can control is tomorrow."

 

WindowsError.gif

WIKI | Tabs; | Arrays; | Strings | Wiki Arrays | How to ask a Question | Forum Search | FAQ | Tutorials | Original FAQ | ONLINE HELP | UDF's Wiki | AutoIt PDF

AutoIt Snippets | Multple Guis | Interrupting a running function | Another Send

StringRegExp | StringRegExp Help | RegEXTester | REG TUTOR | Reg TUTOT 2

AutoItSetOption | Macros | AutoIt Snippets | Wrapper | Autoit  Docs

SCITE | SciteJump | BB | MyTopics | Programming | UDFs | AutoIt 123 | UDFs Form | UDF

Learning to script | Tutorials | Documentation | IE.AU3 | Games? | FreeSoftware | Path_Online | Core Language

Programming Tips

Excel Changes

ControlHover.UDF

GDI_Plus

Draw_On_Screen

GDI Basics

GDI_More_Basics

GDI Rotate

GDI Graph

GDI  CheckExistingItems

GDI Trajectory

Replace $ghGDIPDll with $__g_hGDIPDll

DLL 101?

Array via Object

GDI Swimlane

GDI Plus French 101 Site

GDI Examples UEZ

GDI Basic Clock

GDI Detection

Ternary operator

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...