Jump to content

Get list of currently pressed keyboard buttons


Recommended Posts

Hello.

Spoiler

I'm on a journey to create a GUI hotkey input control that would work with all possible combinations supported by HotKeySet() 

Available UDFs that use msctls_hotkey32 are not good enough, they don't support Win key and other combinations.

I'm using WM_KEYBOARD event to track key presses, where on keydown I store pressed key in a map and remove it on keyup event.

Spoiler
#include <WindowsConstants.au3>
#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <WinAPI.au3>

Opt("GUIOnEventMode", 1)
OnAutoItExitRegister("onExit")

Global $hGui = GUICreate("test", 200, 50)
GUISetOnEvent($GUI_EVENT_CLOSE, "onClose")
Global $gEdit = GUICtrlCreateEdit("", -1, -1, -1, -1, $ES_READONLY)
GUISetState()

Global $pStub_KeyProc = DllCallbackRegister("_KeyProc", "int", "int;ptr;ptr")
Global $hHookKeyboard = _WinAPI_SetWindowsHookEx($WH_KEYBOARD, DllCallbackGetPtr($pStub_KeyProc), 0, _WinAPI_GetCurrentThreadId())

While 1
    Sleep(10)
WEnd

Func _KeyProc($nCode, $wParam, $lParam)
    Local Static $mKeys[], $mData[]
    local static $bIsUpPrev, $bIsUp, $iCount, $iCode, $bExtended, $iReserved, $bAlt, $aKeys, $sOut, $sEdit, $key
    If $nCode >= 0 Then
        ; https://learn.microsoft.com/en-us/windows/win32/winmsg/keyboardproc#lparam-in
        $bExtended = BitAND($lParam, 0x1000000) ? 1 : 0 ;24
        $iCode = BitShift(BitAND($lParam, 0xFF0000), 16) ;16-23
        $bIsUpPrev = BitAND($lParam, 0x40000000) ? 1 : 0 ;30
        $bIsUp = BitAND($lParam, 0x80000000) ? 1 : 0 ;31
        $key = $iCode & "|" & $bExtended
        If $bIsUpPrev == $bIsUp Then ;ignore repeated events
            If $bIsUp Then
                MapRemove($mKeys, $key)
            Else
                $mData.wParam = $wParam
                $mData.lParam = $lParam
                $mKeys[$key] = $mData
            EndIf

            $aKeys = MapKeys($mKeys)
            $sOut = ""
            $sEdit = ""
            For $key In $aKeys
                $data = $mKeys[$key]
                $iCount = BitAND($data.lParam, 0xFFFF) ;0-15
                $iCode = BitShift(BitAND($data.lParam, 0xFF0000), 16) ;16-23
                $bExtended = BitAND($data.lParam, 0x1000000) ? 1 : 0 ;24
                $iReserved = BitShift(BitAND($data.lParam, 0x1E000000), 25) ;25-28
                $bAlt = BitAND($data.lParam, 0x20000000) ? 1 : 0 ;29
                $sOut &= ($sOut ? " + " : "") & "code[" & $iCode & "] ext[" & $bExtended & "] reserved[" & $iReserved & "] alt[" & $bAlt & "] count[" & $iCount & "] wParam[" & $wParam & "] lParam[" & $lParam & "]"
                $sEdit &= ($sEdit ? " + " : "") & Number($data.wParam) & "[" & $bExtended & "]"
            Next
            GUICtrlSetData($gEdit, $sEdit)
            ConsoleWrite($sOut & @CRLF)
        EndIf
    EndIf
    Return _WinAPI_CallNextHookEx($hHookKeyboard, $nCode, $wParam, $lParam)
EndFunc   ;==>_KeyProc

Func onClose()
    Exit
EndFunc   ;==>onClose

Func onExit()
    DllCallbackFree($pStub_KeyProc)
    _WinAPI_UnhookWindowsHookEx($hHookKeyboard)
EndFunc   ;==>onExit

It works, but with 2 issues:

  1. When pressed ShiftNum nn and then first release Shift and then Num nn, It doesn't properly clears the map
  2. When GUI looses focus while a key is down and then the key released, the script doesn't receive keyup event, hence doesn't clear the map

#1 - can't figure this one out

#2 - I could think only of 2 options:

  1. track key presses globally not just for the GUI (undesirable, potentially can be considered as keylogger)
    Spoiler
    #include <WindowsConstants.au3>
    #include <GUIConstantsEx.au3>
    #include <EditConstants.au3>
    #include <WinAPI.au3>
    
    Opt("GUIOnEventMode", 1)
    OnAutoItExitRegister("onExit")
    
    Global $hGui = GUICreate("test", 200, 50)
    Global $gEdit = GUICtrlCreateEdit("", -1, -1, -1, -1, $ES_READONLY)
    Global $hEdit = GUICtrlGetHandle($gEdit)
    GUISetOnEvent($GUI_EVENT_CLOSE, "onClose")
    GUISetState()
    
    Global $pStub_KeyProc = DllCallbackRegister("_KeyProc", "int", "int;ptr;ptr")
    Global $hHookKeyboard = _WinAPI_SetWindowsHookEx($WH_KEYBOARD_LL, DllCallbackGetPtr($pStub_KeyProc), _WinAPI_GetModuleHandle(0), 0)
    
    While 1
        Sleep(10)
    WEnd
    
    Func _KeyProc($nCode, $wParam, $lParam)
        Local Static $mKeys[], $mKey[]
        Local Static $tStruct, $key, $prev, $cur
        If Not $nCode Then
            ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-kbdllhookstruct
            $tStruct = DllStructCreate("dword vkCode;dword scanCode;dword flags;dword time;ptr dwExtraInfo", $lParam)
            $mKey.vkCode = DllStructGetData($tStruct, "vkCode")
            $mKey.scanCode = DllStructGetData($tStruct, "scanCode")
            $mKey.flags = DllStructGetData($tStruct, "flags")
            $mKey.time = DllStructGetData($tStruct, "time")
            $mKey.dwExtraInfo = DllStructGetData($tStruct, "dwExtraInfo")
            Switch $wParam
                Case $WM_KEYUP, $WM_SYSKEYUP
                    MapRemove($mKeys, $mKey.scanCode)
    
                Case $WM_KEYDOWN, $WM_SYSKEYDOWN
                    If ControlGetHandle($hGui, "", ControlGetFocus($hGui)) == $hEdit Then
                        $mKeys[$mKey.scanCode] = $mKey
                    EndIf
            EndSwitch
            Local $aKeys = MapKeys($mKeys)
            Local $sOut = ""
            Local $sEdit = ""
            For $key In $aKeys
                $mKey = $mKeys[$key]
                $sOut &= ($sOut ? " + " : "") & "scanCode[" & $mKey.scanCode & "] vkCode[" & $mKey.vkCode & "] flags[" & $mKey.flags & "] time[" & $mKey.time & "] dwExtraInfo[" & Number($mKey.dwExtraInfo) & "]"
                $sEdit &= ($sEdit ? " + " : "") & $mKey.scanCode & "[" & $mKey.flags & "]"
            Next
            $cur = $mKey.scanCode & "|" & $mKey.flags ;prevent repeat
            If $prev <> $cur Then
                $prev = $cur
                GUICtrlSetData($gEdit, $sEdit)
                ConsoleWrite($sOut & @CRLF)
            EndIf
        EndIf
        Return _WinAPI_CallNextHookEx($hHookKeyboard, $nCode, $wParam, $lParam)
    EndFunc   ;==>_KeyProc
    
    Func onClose()
        Exit
    EndFunc   ;==>onClose
    
    Func onExit()
        DllCallbackFree($pStub_KeyProc)
        _WinAPI_UnhookWindowsHookEx($hHookKeyboard)
    EndFunc   ;==>onExit

     

     

  2. get a list of pressed keys on demand. I only know GetKeyboardState method, but it doesn't work with ShiftNum nn keys, so it's bust:
    Spoiler
    #include <GUIConstantsEx.au3>
    #include <EditConstants.au3>
    
    Opt("GUIOnEventMode", 1)
    OnAutoItExitRegister("onExit")
    Global $hUser32 = DllOpen("user32.dll")
    
    Global $hGui = GUICreate("test", 200, 50)
    Global $gEdit = GUICtrlCreateEdit("", -1, -1, -1, -1, $ES_READONLY)
    GUISetOnEvent($GUI_EVENT_CLOSE, "onClose")
    GUISetState()
    
    Local $sPrev
    While 1
        $sResult = _WhatPressed()
        If $sPrev <> $sResult Then
            $sPrev = $sResult
            GUICtrlSetData($gEdit, $sResult)
        EndIf
        Sleep(10)
    WEnd
    
    Func _WhatPressed()
        Local Static $tOut = DllStructCreate("byte[256]")
        Local Static $iState
        DllCall($hUser32, "int", "GetKeyboardState", "ptr", DllStructGetPtr($tOut))
        Local $sResult = ""
        For $i = 1 To 256
            $iState = DllStructGetData($tOut, 1, $i)
            If BitAND($iState, 128) Then
                $sResult = $sResult & ($sResult ? " + " : "") & $i - 1 & "[" & $iState & "]"
            EndIf
        Next
        Return $sResult
    EndFunc   ;==>_WhatPressed
    
    Func onClose()
        Exit
    EndFunc   ;==>onClose
    
    Func onExit()
        DllClose($hUser32)
    EndFunc   ;==>onExit

 

Maybe someone could help me figure out how to fix these issues?

Thank you

Link to comment
Share on other sites

For #1 here my take on shift + num 0 (both are hardcoded but you will get the idea) :

Func WH_KEYBOARD($iMsg, $wParam, $lParam)
  Local Static $bShiftDown = False
  If $iMsg Then Return _WinAPI_CallNextHookEx($hHook, $iMsg, $wParam, $lParam)
  Local $bUP = BitAND($lParam, 0x80000000) <> 0
  Local $iScanCode = BitAND(_WinAPI_HiWord($lParam), 0x00FF)
  Local $bExt = BitAND($lParam, 0x01000000) <> 0
  Local $bState = BitAND($lParam, 0x40000000) <> 0

  If $wParam = 16 Then
    If $bUP Then
      $bShiftDown = False
      ConsoleWrite("shift up" & @CRLF)
    Else
      If Not $bShiftDown Then
        $bShiftDown = True
        ConsoleWrite("shift down" & @CRLF)
      EndIf
    EndIf
  EndIf
  If $wParam = 45 And Not $bUP Then
    $bShiftDown = True
    ConsoleWrite("shift down" & @CRLF)
    ConsoleWrite("shift is pressed on num 0" & @CRLF)
  EndIf

  ConsoleWrite( _
    "Virtual Key = " & $wParam & @TAB & _
    "Scan Code = " & $iScanCode & @TAB & _
    "Is Up = " & $bUP & @TAB & _
    "State = " & $bState & @TAB & _
    "Is Ext = " & $bExt & @CRLF)
  Return _WinAPI_CallNextHookEx($hHook, $iMsg, $wParam, $lParam)
EndFunc   ;==>MyProc

ps. as I said on another thread, rely on vkey to determine if shift is pressed or not with num keys.

Link to comment
Share on other sites

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
 Share

  • Recently Browsing   0 members

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