Jump to content

Recommended Posts

Posted

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

Posted

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.

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
×
×
  • Create New...