views:

278

answers:

2

I'm working with an on-screen keyboard that needs to send key strokes to a third party application. They are running on Windows XP. A small set of characters that are not available on the US English keyboard need to be supported (such as "å" or ñ). After reviewing SendInput it seemed like the safest thing would be to send the hex unicode value of the character as a key stroke sequence. I wrote code that sends an "Alt" and "Add" key down event, followed by key down and up events for the four character unicode sequence with the Alt key ORed, then finally "Add" and "Alt" key up events. In my C# test app. I am using KeyPreview and sure enough, all of the events are coming through however all I get is a beep, no character. I have captured the same information from entering the key strokes manually, the KeyPreview information is identical, and the character appears.

Is it possible to use SendInput in this way? I haven't used a hook to examine the data but I've seen posts that indicate SendInput events have some sort of "injected" flag attached, maybe this causes the sequence to fail?

This demo code successfully sends key events, but a sequence of key events intended to generate a unicode character fails.

    private const uint KEYEVENTF_KEYDOWN = 0x0000;
    private const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
    private const uint KEYEVENTF_KEYUP = 0x0002;

    private const int INPUT_KEYBOARD = 1;

    [DllImport ("user32.dll", SetLastError = false)]
    static extern IntPtr GetMessageExtraInfo ();

    [DllImport ("user32.dll", SetLastError = true)]
    static extern uint SendInput (uint nInputs, [MarshalAs (UnmanagedType.LPArray, SizeConst = 1)] INPUT[] pInputs, int cbSize);

    [StructLayout (LayoutKind.Sequential, Size = 24)]
    private struct KEYBDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout (LayoutKind.Explicit)]
    private struct INPUT
    {
        [FieldOffset (0)]
        public int type;
        [FieldOffset (4)]
        public KEYBDINPUT ki;
    }

    private void PressKey (Keys k)
    {
        PressKeyDown (k);
        PressKeyUp (k);
    }

    private void PressKeyDown (Keys k)
    {
        INPUT input = new INPUT ();
        input.type = INPUT_KEYBOARD;
        input.ki.wVk = (byte)k;
        input.ki.wScan = 0;
        input.ki.time = 0;

        uint flags = KEYEVENTF_KEYDOWN;
        if ((33 <= (byte)k && (byte)k <= 46) || (91 <= (byte)k) && (byte)k <= 93)
            flags |= KEYEVENTF_EXTENDEDKEY;
        input.ki.dwFlags = flags;

        input.ki.dwExtraInfo = GetMessageExtraInfo ();

        Output ("Sending key down {0}. Flags:{1}", k, flags);

        INPUT[] inputs = new INPUT[] { input };
        uint result = SendInput ((uint)inputs.Length, inputs, Marshal.SizeOf (typeof (INPUT)));

        if ((uint)inputs.Length != result)
            MessageBox.Show ("PressKeyDown result = " + Marshal.GetLastWin32Error ());
    }

    private void PressKeyUp (Keys k)
    {
        INPUT input = new INPUT ();
        input.type = INPUT_KEYBOARD;
        input.ki.wVk = (byte)k;
        input.ki.wScan = 0;
        input.ki.time = 0;

        uint flags = KEYEVENTF_KEYUP;
        if ((33 <= (byte)k && (byte)k <= 46) || (91 <= (byte)k) && (byte)k <= 93)
            flags |= KEYEVENTF_EXTENDEDKEY;
        input.ki.dwFlags = flags;

        input.ki.dwExtraInfo = GetMessageExtraInfo ();

        Output ("Sending key up {0}", k);

        INPUT[] inputs = new INPUT[] { input };
        uint result = SendInput ((uint)inputs.Length, inputs, Marshal.SizeOf (typeof (INPUT)));

        if ((uint)inputs.Length != result)
            MessageBox.Show ("PressKeyUp result = " + Marshal.GetLastWin32Error ());
    }

    private void TestSend ()
    {
        System.Threading.Thread.CurrentThread.Join (1000);

        Keys k = Keys.Menu;
        PressKeyDown (k);

        System.Threading.Thread.Sleep (100);

        k = Keys.Add;
        k |= Keys.Alt;
        PressKeyDown (k);

        System.Threading.Thread.Sleep (100);

        k = Keys.NumPad0;
        k |= Keys.Alt;
        PressKey (k);

        System.Threading.Thread.Sleep (100);

        k = Keys.NumPad0;
        k |= Keys.Alt;
        PressKey (k);

        System.Threading.Thread.Sleep (100);

        k = Keys.E;
        k |= Keys.Alt;
        PressKey (k);

        System.Threading.Thread.Sleep (100);

        k = Keys.NumPad5;
        k |= Keys.Alt;
        PressKey (k);

        System.Threading.Thread.Sleep (100);

        PressKeyUp (Keys.Add);
        PressKeyUp (Keys.Menu);
    }
+1  A: 

You can generate them by holding down the Alt key and typing in the 4 digit Unicode codepoint on the numeric keypad. å = Alt + 0229, ñ = Alt + 0241. Find other codes with the Charmap.exe applet.

Hans Passant
Unfortunately Alt+DDDD depends on the current locale and I'm trying to avoid switching locales. I did try sending Alt plus the decimal unicode value, it doesn't cause a character to be displayed either.
Unicode codepoints are not culture sensitive. Be sure to send the keypad key, not the typing key.
Hans Passant
A: 

Apparently the processing of a sequence of key presses to represent a unicode character is done at a level that is not accessible through SendInput. I changed my code to set the unicode flag on dwFlags and set the unicode value on the wScan data parameter. I've managed to convince myself after testing with several European and Asian languages that this creates the same results as the multiple keystroke method.