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]

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