Reputation: 12420
I am looking for a way to simulate input of non-ASCII characters (e.g. CJK characters) using Windows APIs. keybd_event
or SendInput
won't work because the input characters may not be valid virtual keys.
I want to simulate input of Unicode character(s) by using Windows API, without needing to write an IME or TSF provider.
I've tried to send the WM_IME_CHAR
message to the currently focused window:
// Gets the currently focused input control (if any)
internal static IntPtr GetActiveWindowControl()
{
IntPtr activeWin = GetForegroundWindow();
if (activeWin == IntPtr.Zero)
{
return IntPtr.Zero;
}
uint threadId = GetWindowThreadProcessId(activeWin, IntPtr.Zero);
GUITHREADINFO guiThreadInfo = new GUITHREADINFO();
guiThreadInfo.cbSize = Marshal.SizeOf(guiThreadInfo);
GetGUIThreadInfo(threadId, ref guiThreadInfo);
if (guiThreadInfo.hwndFocus == IntPtr.Zero)
{
return activeWin; // Example: console
}
else
{
return guiThreadInfo.hwndFocus;
}
}
internal void SimulateInput()
{
// Encoding.Default on my computer is CP950 (Big5)
byte[] b = Encoding.Default.GetBytes("我"); // one single Chinese character
IntPtr activeHwnd = GetActiveWindowControl();
if (activeHwnd != IntPtr.Zero)
{
int ch = 0;
for (int i = 0; i < b.Length; i++) // Assumed b has <=2 elements
{
ch = (ch << 8) | b[i];
}
uint WM_IME_CHAR = 0x0286;
SendMessage(activeHwnd, WM_IME_CHAR, (IntPtr)ch, (IntPtr)0)
}
}
So far this code seems to have worked quite well in most cases, however it doesn't work on Notepad++. Instead of the desired character, I got garbage input instead.
Further investigation using Spy++ shows that when using an IME to input, there isn't a WM_IME_CHAR
message sent to the Notepad++ edit window. All I get are WM_IME_STARTCOMPOSITION
, WM_IME_ENDCOMPOSITION
, WM_IME_REQUEST
, WM_IME_NOTIFY
and WM_IME_SETCONTEXT
. It seems that Notepad++ handles IME input different from other programs. (Also I believe there exist more programs that acts similar to Notepad++, but I just haven't found one yet.)
Also, this code will not work if the character(s) is outside the default character encoding of the target operating system (e.g. Simplified Chinese does not work with Big5).
I realize that the Windows 7 Tablet PC input panel perform text insertion very nicely (even works on GTK+ applications). I tried to emulate what it sends but it only appears to repeat the last IME-entered character. It doesn't work even it appears to be exactly the same as seen in Spy++.
SendMessage(active, WM_IME_STARTCOMPOSITION, (IntPtr)0, (IntPtr)0);
//SendMessage(active, WM_IME_COMPOSITION, (IntPtr)ch, (IntPtr)0x0800);
SendMessage(active, WM_IME_COMPOSITION, (IntPtr)0xA7DA, (IntPtr)0x0800);
SendMessage(active, WM_IME_NOTIFY, (IntPtr)0x010D, (IntPtr)0);
SendMessage(active, WM_IME_ENDCOMPOSITION, (IntPtr)0, (IntPtr)0);
SendMessage(active, WM_IME_NOTIFY, (IntPtr)0x010E, (IntPtr)0);
I've also found a SendKeys
class in .NET. Could it be used in my application?
Upvotes: 2
Views: 1883
Reputation: 12420
After a bit more searching, it turned out that SendInput
actually could be used to send Unicode input with KEYEVENTF_UNICODE
.
Now my code looks like this (InputSender
is my own class):
internal static void InputString(string str)
{
char[] chars = str.ToCharArray();
InputSender.Input[] inputs = new InputSender.Input[chars.Length * 2];
for (int i = 0; i < chars.Length; i++)
{
ushort ch = (ushort)chars[i];
// Key down
inputs[i * 2].type = InputSender.InputType.Keyboard;
inputs[i * 2].ki.wScan = ch;
inputs[i * 2].ki.dwFlags = InputSender.KeyboardEventFlags.Unicode;
// Key up
inputs[i * 2 + 1].type = InputSender.InputType.Keyboard;
inputs[i * 2 + 1].ki.wScan = ch;
inputs[i * 2 + 1].ki.dwFlags = InputSender.KeyboardEventFlags.Unicode | InputSender.KeyboardEventFlags.KeyUp;
}
InputSender.SendInput(inputs);
}
But it turned out that it doesn't work quite well with GTK+ programs. A bit searching shows that it should be fixed a while ago but programs which uses an older version of GTK+ may still encounter problems. Just tried the newest GTK+ with Geany with newest GTK+ is fine, while Inkscape refuses to interpret the input.
Anyway if I ignore the problem of GTK+, using SendInput
is sufficient (and should be better than WM_IME_CHAR
). I haven't tried this answer which fixes GTK+'s problem because I don't know how to do it and believe that it is not feasible.
Upvotes: 2