Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

SendInput in Unicode mode


  • Please log in to reply
53 replies to this topic
shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005
The Windows API function, SendInput, permits sending Unicode characters (or a stream of characters) to the active window. The following code includes the function, SendInputU, which encapsulates and simplifies the use of SendInput, as well as, a demonstration (i.e., F1 hotkey). SendInput in Unicode mode is supported by Windows 2000 and XP. The SendInputU function has been tested with Notepad and Word 2003 on Windows XP.

pro: events are inserted serially (i.e., without interruption) into the target window's queue
con: events are context sensitive (i.e, they are affected by other input events)

note: respond if you understand the encoded message

F1::
	SendInputU( "041F044004380432043504420020041C043804400021" )
return

EncodeInteger( p_value, p_size, p_address, p_offset )
{
	loop, %p_size%
		DllCall( "RtlFillMemory"
			, "uint", p_address+p_offset+A_Index-1
			, "uint", 1
			, "uchar", ( p_value >> ( 8*( A_Index-1 ) ) ) & 0xFF )
}

SendInputU( p_text )
{
	StringLen, len, p_text

	INPUT_size = 28
	
	event_count := ( len//4 )*2
	VarSetCapacity( events, INPUT_size*event_count, 0 )

	loop, % event_count//2
	{
		StringMid, code, p_text, ( A_Index-1 )*4+1, 4
		
		base := ( ( A_Index-1 )*2 )*INPUT_size+4
			EncodeInteger( 1, 4, &events, base-4 )
			EncodeInteger( "0x" code, 2, &events, base+2 )
			EncodeInteger( 4, 4, &events, base+4 ) ; KEYEVENTF_UNICODE

		base += INPUT_size
			EncodeInteger( 1, 4, &events, base-4 )
			EncodeInteger( "0x" code, 2, &events, base+2 )
			EncodeInteger( 2|4, 4, &events, base+4 ) ; KEYEVENTF_KEYUP|KEYEVENTF_UNICODE 
	}
	
	result := DllCall( "SendInput", "uint", event_count, "uint", &events, "int", INPUT_size )
	if ( ErrorLevel or result < event_count )
	{
		MsgBox, [SendInput] failed: EL = %ErrorLevel% ~ %result% of %event_count%
		return, false
	}
	
	return, true
}


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Fantastic! Thank you for figuring this out!

A few questions:
- If I replace the HotKey with: ^F1, !F1, !z or ^z the Russian Hello World! (Привет Мир!) message is not received by Ms Word. Others, like: F1, +F1, ^!F1, ^!z, #z work perfectly. What are the restrictions? How can I reset the context, so any HotKey works? (Sending Alt-Down/Up does not work.)
- What do I have to modify for sending ANSI coded strings, like äößü, each character encoded with a single byte? (AHK sends them as ALT-Numpad number combinations, which are interruptible, and ugly side effects can result. Not Unicode aware applications cannot handle the SendInputU Unicode stream.)
- The magic string contains 16-bit Unicodes "041F 0440 0438 0432 0435 0442 0020 041C 0438 0440 0021", which I can find with the Windows Character Map (or by Insert Symbol in Word). Are there any restrictions?

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

Fantastic! Thank you for figuring this out!


Sure.

If I replace the HotKey with...


I think the following method would be the simplest to ensure a known context:

^F1::
	KeyWait, Ctrl
	SendInputU( ... )
return

What do I have to modify for sending ANSI coded strings, like äößü... Not Unicode aware applications cannot handle the SendInputU Unicode stream.


SendInputU should work with strictly ANSI applications. What is an example of an application where this method fails?

The system does automatic two-way translation [ANSI <-> Unicode]


SendInputU( "00E4" ) ; ä

SendInput accepts specification of input with virtual key (VKC) and/or scan (SC) codes. Support for ANSI text would require conversion of ANSI characters to their corresponding VKC and/or SC, and a correct context.

Are there any restrictions?


There are no documented restrictions beyond those mentioned in my post.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
(I should have posted this earlier)
Nice work. Something like this should probably be integrated into the Send command soon, since it seems of high value to many users. Thanks for your R&D.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
ANSI code: Yes, it works in not Unicode aware applications I tried so far. The only difference I found is that sending "0008" in MS Word and Notepad works as BackSpace (deleting characters to left), but in MultiEdit it produces an unknown character placeholder, in NotePad2: the bold, inverted letters "BS". It probably has nothing to do with ANSI- or Unicode, just how an application handles non-printable characters. I just hoped a simulated Unicode hotstring could contain the right number of BackSpace characters encoded in the replacement string, but this does not seem to work universally.

In the mean time, I tried SendInputU in my keyboard remapping scripts. It works as expected, but a little too slow, due to the 20 DllCalls for each character plus one DllCall for send. I made the following modifications for a 5-fold speedup (in the cost of readability)
SendInputU( p_text ) ; 4 DllCalls/char + 1, reduced from 20/char + 1
{
   event_count := ( StrLen(p_text)//4 )*2
   VarSetCapacity( events, 28*event_count, 0 )
   base = 0
   Loop % event_count//2
   {
      StringMid code, p_text, 4*A_Index-3, 4
      code = 0x4%code%

      DllCall("RtlFillMemory", "uint", &events + base, "uint",1, "uint", 1)
      DllCall("ntoskrnl.exe\RtlFillMemoryUlong", "uint",&events+base+6, "uint",4, "uint",code)
      base += 28

      DllCall("RtlFillMemory", "uint", &events + base, "uint",1, "uint", 1)
      DllCall("ntoskrnl.exe\RtlFillMemoryUlong", "uint",&events+base+6, "uint",4, "uint",code|(2<<16))
      base += 28
   }
   result := DllCall( "SendInput", "uint", event_count, "uint", &events, "int", 28 )
   if ( ErrorLevel or result < event_count )
      MsgBox SendInput failed`nErrorLevel = %ErrorLevel%`n%result% events of %event_count%
}
If only a single Unicode character is to be sent (like in keyboard remapping) a further factor of two speedup is possible (and desirable). The data structure is set up in the beginning of the script just once with a single call of SendUinit(), and subsequent SendU(Unicode) calls just overwrite the necessary part.
SendUinit()
{
   Global SendUbuf, SendU4, SendU6

   DllCall("LoadLibrary", "str", "ntoskrnl.exe")
   SendU4 := 4 << 16  ; KEYEVENTF_UNICODE
   SendU6 := 6 << 16  ; KEYEVENTF_KEYUP|KEYEVENTF_UNICODE
   VarSetCapacity( SendUbuf, 56, 0 )
   DllCall("RtlFillMemory", "uint",&SendUbuf,    "uint",1, "uint", 1)
   DllCall("RtlFillMemory", "uint",&SendUbuf+28, "uint",1, "uint", 1)
}

SendU( p_uchar )     ; 3 DllCalls, needs one prior call to SendUinit()
{
   Global SendUbuf, SendU4, SendU6
   DllCall("ntoskrnl.exe\RtlFillMemoryUlong", "uint",&SendUbuf+6,  "uint",4, "uint", p_uchar | SendU4)
   DllCall("ntoskrnl.exe\RtlFillMemoryUlong", "uint",&SendUbuf+34, "uint",4, "uint", p_uchar | SendU6)
   Return DllCall( "SendInput", "uint", 2, "uint", &SendUbuf, "int", 28 )
}


shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005
to Chris:

Thanks. SendInputU implements a subset of the functionality available through the the SendInput function. However, I thought the integrated Unicode support would have the highest value.

to Laszlo:

EncodeInteger( 0x800000001, 5, &events, 0 )


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

to Laszlo:

EncodeInteger( 0x800000001, 5, &events, 0 )

Beg your pardon?

Edit: I got it: it makes the character 8 to work as a BS in every application. Thanks! It is great!
(You seem to have an answer for everything. What byte do I have to change, so my next paycheck will show the decimal point one position to the right?)

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

Beg your pardon?... it makes the character 8 to work as a BS


What was your first impression?

What byte do I have to change, so my next paycheck will show the decimal point one position to the right?


That would require subverting the system, which would be contrary to my principles. Although, there are conventional means to accomplish the same, which mostly depend on time, 1.0 => 10 years, and effort, 10.0% => 100%, resulting in a paycheck of $10,000 => $100,000. It's actually quite straightforward, and there are shortcuts, but they are few and far between.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

What was your first impression?

0. My reward as the first responder understanding the encoded message in the original post.
1. Secret message, which I have to decode to get the $10,000 first price.
2. A bug in my short and ugly version of your code.
3. Patch, to make SendInput context insensitive.

But, seriously, what does this magic string really do? Can I send this way DEL, CR, LF?

You mentioned the possibility to send scan codes. How? It would be useful if we could send ESC, Ctrl-C, Alt-Space, F11 or similar.

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

My reward...


Actually, the code was the reward. It wouldn't have any value if you didn't understand the result.

... the $10,000 first price.


Sorry. I preside in a state where lotteries are illegal.

my short and ugly version of your code


I don't comment on aesthetics.

Patch, to make SendInput context insensitive.


Close, but not quite. However, it does exemplify a means to that end.

what does this magic string really do? Can I send this way DEL, CR, LF?


It simulates sending as input by specifying its virtual key code. As it happens, it is sufficient to only send that key's pressed state in order to be correctly recognized.

EncodeInteger( 0x1B00000001, 5, &events, 0 ) ; Esc

Yes. The array of inputs (i.e., events) can be constructed with differing specifications, which enables mixing ANSI and Unicode character events in a single stream. This can be accomplished in a straightforward manner by enhancing the p_text parser or combining events from SendInputA and SendInputU before calling SendInput.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
If I understand right:

- SendInput needs 2 consecutive blocks of data for each character it processes, 28 bytes each.
- Each block starts with 01 00 00 00
- If, in the first block, the next 2 bytes are 00 00, the 2 bytes after them are taken as Unicode. In this case, the second block has to have the same data up to here.
- If, in the first block, bytes 4 and 5 are not zero, they are taken as scan code. The following 4 bytes and all these 6 locations in the second block do not matter.
- The first block contains 04 in the byte position 8 (meaning key down).
- The second block contains 06 in the byte position 8 (meaning key up).
- All other bytes are 0.

Please correct where I am wrong.

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

Please correct where I am wrong.


EncodeInteger( 4, 4, &events, base+4 ) ; KEYEVENTF_UNICODE
EncodeInteger( 2|4, 4, &events, base+4 ) ; KEYEVENTF_KEYUP|KEYEVENTF_UNICODE

The documentation for SendInput can be found at MSDN. The constants are defined in the header files, which are include with the SDK.

Lester Wong
  • Guests
  • Last active:
  • Joined: --
So why doesn't it work in Skype?

TAB4217
  • Members
  • 31 posts
  • Last active: Nov 21 2019 06:24 PM
  • Joined: 13 Sep 2007
[code=auto:0]
^F1::
KeyWait, Ctrl
SendInputU( ... )
return

***Why is KeyWait, Ctrl necessary? (An explanation would help me to understand the workings of AHK.)
Thank you.
TAB

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The SendInputU function does not change the state of the modifier keys. If you use it in a hotkey subroutine, like Ctrl-F1, the sent keys will all behave as Ctrl-key combinations, while the user holds the Ctrl key down. You have to do something to fake a release of the Ctrl key, or wait until it is released by the user (KeyWait Ctrl).