Alvin Wong
Alvin Wong

Reputation: 12420

Simulate input of non-ASCII characters

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.

What I want

I want to simulate input of Unicode character(s) by using Windows API, without needing to write an IME or TSF provider.

What I've tried

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)
    }
}

Problem encountered

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

Things that I want to prevent

Additional information

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

Answers (1)

Alvin Wong
Alvin Wong

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

Related Questions