Jump to content

Keypress simulation with WinAPI functions directly


Go to solution Solved by mistersquirrle,

Recommended Posts

Hi guys I share with you this code the purpose is to send the letter "a" to notepad interacting with WinAPI. I'm not getting it to work and I don't see the error. If someone can see the error I would appreciate it.Maybe something needs to be imported ?

$hWnd = WinGetHandle("[CLASS:Notepad]")
WinActivate($hWnd)
Const $KEYEVENTF_UNICODE = 4
Const $INPUT_KEYBOARD = 1
Const $iInputSize = 28
Const $tagKEYBDINPUT = _
 'word wVk;' & _
 'word wScan;' & _
 'dword dwFlags;' & _
 'dword time;' & _
 'ulong_ptr dwExtraInfo'
Const $tagINPUT = _
 'dword type;' & _
 $tagKEYBDINPUT & _
 ';dword pad;'
$tINPUTs = DllStructCreate($tagINPUT)
$pINPUTs = DllStructGetPtr($tINPUTs)
$iINPUTs = 1
$Key = AscW('a')
DllStructSetData($tINPUTs, 1, $INPUT_KEYBOARD)
DllStructSetData($tINPUTs, 3, $Key)
DllStructSetData($tINPUTs, 4, $KEYEVENTF_UNICODE)
DllCall('user32.dll', 'uint', 'SendInput', 'uint', $iINPUTs, _
 'ptr', $pINPUTs, 'int', $iInputSize)

 

Link to comment
Share on other sites

  • Solution

I also tried some things with SendInput, and in the end I decided that it wasn't worth it, as I'm pretty sure that AutoIt's Send uses it (though I haven't confirmed, so if someone with more knowledge wants to correct me...). The main problem, and likely that problem that you're having is that SendInput doesn't work (with what you have here) in x64 mode. Your code works for me if you make sure it's running as x86 by putting this at the top of your script:

#AutoIt3Wrapper_UseX64=n

I messed around with SendInput for a while trying to get it work with 64-bit but couldn't. I got it to the point where DllCall did not return any errors, so it seemed like it and SendInput were happy with everything, but nothing happened.

Maybe someone knows how to get SendInput working with 64-bit, but it wasn't worth it to me.

Also, keep in mind that with your SendInput call, you're not sending the key back UP, it's only being pressed down. You should send the key back up as well:

Global Const $KEYEVENTF_KEYUP = 0x0002

And from the Microsoft page on SendInput:

This function is subject to UIPI [User Interface Privilege Isolation]. Applications are permitted to inject input only into applications that are at an equal or lesser integrity level

Meaning you may want/need to add #RequireAdmin to your script as well.

We ought not to misbehave, but we should look as though we could.

Link to comment
Share on other sites

2 hours ago, TheXman said:

The example below works in 32 bit and 64 bit environments....

There are alignment issues that have to be taken into account when running 32 bit or 64 bit.  The example shows one way of handling it.

Awesome, I had tried messing around with padding and alignment of structs, but I'm not very good with DllStructs and that type of thing. So I never got it working, but I had tried some similar methods. Thank you for that example, for sure!

19 minutes ago, Dsmoeg999 said:
#AutoIt3Wrapper_UseX64=n

I don't quite understand what the wrapper is for.

This tells AutoIt if it should run your script under x86 (32-bit) or x64 (64-bit). By default when you install AutoIt it uses x86, though while installing you can change the default runtype to x64. So it sounds like that you've installed AutoIt to use x64 by default, and to get the script running as x86/32-bit, that "wrapper" line is needed. It just tells AutoIt to run as x86 (n) or x64 (y).

Edited by mistersquirrle

We ought not to misbehave, but we should look as though we could.

Link to comment
Share on other sites

  • 1 month later...
@TheXman great example in your script above, it explains a lot, thanks :)
I would like to discuss the part where "you're sending 'C' using a scan code" with these lines :

$tKeyInput.wScan   = AscW("C")
$tKeyInput.dwFlags = $KEYEVENTF_UNICODE

We could also use the scan code for another purpose, as read on msdn Keyboard Input Overview (scan codes paragraph) which stipulates :

The scan code is the value that the system generates when the user presses a key. It is a value that identifies the key pressed regardless of the active keyboard layout, as opposed to the character represented by the key.

For example, if we replace array[6] and array[7] in your script with these ones...

;Load input array[6]
$tKeyInput = DllStructCreate($tagKeyInput, DllStructGetPtr($tInputArray) + (6 * DllStructGetSize($tKeyInput)))
$tKeyInput.type    = $INPUT_KEYBOARD
$tKeyInput.wScan   = 0x10 ; key location #17 (q on Qwerty kbd, a on Azerty kbd etc...) got a Scan 1 Make = 0x10
$tKeyInput.dwFlags = $KEYEVENTF_SCANCODE

;Load input array[7]
$tKeyInput = DllStructCreate($tagKeyInput, DllStructGetPtr($tInputArray) + (7 * DllStructGetSize($tKeyInput)))
$tKeyInput.type    = $INPUT_KEYBOARD
$tKeyInput.wScan   = 0x10 ; key location #17 (reuse Scan 1 Make, no need of deprecated Scan 1 Break)
$tKeyInput.dwFlags = $KEYEVENTF_SCANCODE + $KEYEVENTF_KEYUP

...then a letter (not depending on the keyboard layout) will be displayed in NotePad. It may be a 'q' (if qwerty keyboard) or an 'a' (my azerty keyboard) or whatever. Key location #17 can be found on the keyboard picture at the msdn link above, with its corresponding "Scan 1 Make". We just need to indicate the correct flag $KEYEVENTF_SCANCODE in the structure.

Maybe this could answer the question asked in this topic ?
Edited by pixelsearch
no need of deprecated Scan 1 Break in array[7], let's reuse Scan 1 Make
Link to comment
Share on other sites

41 minutes ago, pixelsearch said:

Maybe this could answer the question asked in this topic ?

@pixelsearch

I saw the topic that you referenced above.  I couldn't tell whether the OP wanted to be able to send the same character regardless of the locale or if he wanted to always send the character from the same physical key on the keyboard, regardless of the locale.  I see conflicting information between the title of the post, the images of the keyboards, and some of the posts.  I try not to answer questions based on my assumptions.  I prefer to just ask OP's for clarification in order to not waste unnecessary time and effort.  Without a definitive answer to what the OP is actually trying to achieve, I really can't answer your question. 

For the record, I understood it as wanting to send the same character regardless of the locale.  If that's true, then Send() or the SendInput API could just send a "C".

Edited by TheXman
Link to comment
Share on other sites

@TheXman Great example, thanks a lot!

Quote

I did not include what the MOUSEINPUT and HARDWAREINPUT unions would look like.

It would be great if you could post examples on these too! Maybe directly to "Examples"?

Link to comment
Share on other sites

Thanks @KaFu.

I'll leave the creation of additional examples to others.  If someone has questions or problems with the creation of those examples, then as always Semper volens auxilium (I'm always willing to help).  Of course that assumes he or she is actively trying to learn, not just passively trying to get solutions or answers (and not on my list of members to ignore).  😉

Edited by TheXman
Link to comment
Share on other sites

I modified my example above : when $KEYEVENTF_SCANCODE is applied in array[7] , there's no need to use "Scan 1 Break" any more as it's deprecated. Reusing twice the same "Scan 1 Make" for the same key physical location does the job, in array[6] and array[7]

Could you imagine MS modified all the "Scan codes" paragraph on their web page Keyboard Input Overview ?
I downloaded their web page on October 9, 2022 and all "Scan 1 Break" codes were indicated in a separate column. Now this column has disappeared in 2023. The rest of the web page (before and after the "Scan codes" paragraph) is exactly the same, as checked with Beyond Compare, not a single byte has changed !

Here is a part of the 2022 web page, referring to older keyboards :

The following table lists the full set of Scan Codes as presently recognized by Windows. The US Key assignments are for reference to a type 101/102 Enhanced keyboard.

The "Scan 1 make" code is delivered in WM_KEYDOWN/WM_SYSKEYDOWN messages, while the "Scan 1 break" code is delivered in WM_KEYUP/WM_SYSKEYUP messages.

...and here is a part of the 2023 actual web page :

Modern keyboards are using Human Interface Devices (HID) specification to communicate with a computer. Keyboard driver converts reported HID Usage values sent from the keyboard to scan codes and passes them on to applications.

The Scan 1 Make code is delivered in WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUP and WM_INPUT messages

Edited by pixelsearch
msdn indications that "Scan 1 Make" should be used everywhere, bye bye "Scan 1 break"
Link to comment
Share on other sites

Still a keyboard example, based on @TheXman script above.
It shows clearly that if you try to press keys while the 5.000 "a" are being displayed in Notepad, all your key presses will be buffered, until the compact block of 5.000 "a" is fully displayed. Only then your buffered keys will be displayed.

It's a big difference with the Send() function where everything would be intermingled even if you Send() a unique string of 5.000 "a" at once (tested). This confirms what we can read on msdn :

The SendInput function works by injecting a series of simulated input events into a device's input stream. The effect is similar to calling the keybd_event or mouse_event function repeatedly, except that the system ensures that no other input events intermingle with the simulated events. When the call completes, the return value indicates the number of input events successfully played. If this value is zero, then input was blocked.

Before running the script, a blank NotePad should be opened, with its Wrap option ticked (not mandatory but it will be more 'enjoyable' to display a block of "a" appearing on different 'lines') . Anyway, wrapped or not, no big deal :)
#AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d

;~ #AutoIt3Wrapper_UseX64=N

#include <Constants.au3>
#include <Array.au3>
#include <WinAPIDiag.au3>

Const $INPUT_KEYBOARD = 1
Const $VK_A = 0x41

Const $tagKeyInput = _
    "dword type;"              & _
    (@AutoItX64 ? "byte alignment_pad[4];" : "") & _
    "struct;"                  & _
    "  word wVk;"              & _
    "  word wScan;"            & _
    "  dword dwFlags;"         & _
    "  dword time;"            & _
    "  ulong_ptr dwExtraInfo;" & _
    "  byte  reserved[8];"     & _
    "endstruct;"

example()

Func example()

    #cs ==========================================================
        Using the SendInput win32 API, this example sends a string
        of "a" to the last active notepad instance (if it exists).

        Note:
        If user press other keys while the string of "a" is sent,
        they are buffered and displayed AFTER the string of "a"

        This is a huge difference with the Send() function where
        the display would be intermingled when you send for ex.
        a string of 5.000 "a" with a single Send() instruction.
    #ce ==========================================================

    ; Line moved inside the function, or the script won't run with AutoIt 3.3.14.5 ("includes chain" changed from 3.3.14.5 to 3.3.16.1)
    Local Enum Step *2 _
    $KEYEVENTF_EXTENDEDKEY = 1, _
    $KEYEVENTF_KEYUP, _
    $KEYEVENTF_UNICODE, _
    $KEYEVENTF_SCANCODE

    Local $tKeyInput, _
          $tInputArray

    Local $aResult

    Local $iNumberOfInputs = 5000 * 2 ; => 10000 inputs (press/release) => 5000 chars displayed
    If $iNumberOfInputs < 2 Or (($iNumberOfInputs / 2) <> Int($iNumberOfInputs / 2)) Then _
        Exit MsgBox($MB_ICONERROR, "$iNumberOfInputs", "It has to be an even number, greater or equal to 2")

    ;Create a struct to get size
    $tKeyInput = DllStructCreate($tagKeyInput)

    ;Create an inputs array buffer
    $tInputArray = DllStructCreate(StringFormat("byte bytes[%i];", DllStructGetSize($tKeyInput) * $iNumberOfInputs))

    ; Local $hTimer = TimerInit()
    
    For $i = 0 To $iNumberOfInputs - 1 Step 2

        ;Load input array[$i]
        $tKeyInput = DllStructCreate($tagKeyInput, DllStructGetPtr($tInputArray) + ($i * DllStructGetSize($tKeyInput)))
        $tKeyInput.type    = $INPUT_KEYBOARD
        $tKeyInput.wVk     = $VK_A

        ;Load input array[$i + 1]
        $tKeyInput = DllStructCreate($tagKeyInput, DllStructGetPtr($tInputArray) + (($i + 1) * DllStructGetSize($tKeyInput)))
        $tKeyInput.type    = $INPUT_KEYBOARD
        $tKeyInput.wvk     = $VK_A
        $tKeyInput.dwFlags = $KEYEVENTF_KEYUP

    Next

    ; ConsoleWrite($iNumberOfInputs & " Inputs array buffer prepared in " & Int(TimerDiff($htimer)) & " ms" & @crlf)
    ; ConsoleWrite(DllStructGetSize($tKeyInput) & @crlf)   ; 28
    ; ConsoleWrite(DllStructGetSize($tInputArray) & @crlf) ; 28 * $iNumberOfInputs (280.000 if $iNumberOfInputs = 10.000)

    ;Activate notepad window
    If Not WinActivate("[CLASS:Notepad]") Then Exit MsgBox($MB_ICONERROR, "WinActivate Error", "Notepad window not found.")
    
    ;Send input
    $aResult = DllCall('user32.dll', 'uint', 'SendInput', _
        'uint'   , $iNumberOfInputs, _
        'struct*', $tInputArray, _
        'int'    , DllStructGetSize($tKeyInput))

    If $aResult[0] = 0 Then
        MsgBox(0, "GetLastError", _WinAPI_GetLastErrorMessage())
    ElseIf $aResult[0] <> $iNumberOfInputs Then
        MsgBox(0, "Warning", $aResult[0] & " keyboard events returned (it should have been " & $iNumberOfInputs & ")")
    EndIf
EndFunc

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