0

I am trying to send simple key sequences (ctrl-p, shift-p) to the foreground app (which happens to be the Visual Studio editor unit test buffer). No matter what I try, I cannot get my test key sequences of control-p or shift-p to work properly. The 'p' appears in the buffer, but not with the control/shift part.

I have read easily a dozen code examples from SO and the net, read the documentation and tried variations too many to count. But without success. I'm on a standard Windows10x64 system, running the code as a unit test.

The code below sets both the virtual key and the scancode for the modifier keys, which is probably wrong in practice. I have tried both ways without success. I included the code to set both so that you could see the code I was using for both fields.

Does anyone know what I am doing wrong? Thank you.

** UPDATE ** My code was not failing. I was running it in a unit test within Visual Studio, which was blocking the modifier keys. When I ran the same code outside of a unit test (without all the debugging layers etc), the code ran fine.

Lesson: Don't try to debug your SendInput code modifier keys in unit tests!!

  [DllImport("user32.dll", SetLastError = true)]
  public static extern uint MapVirtualKey(uint uCode, uint uMapType);

  [DllImport("user32.dll", SetLastError = true)]
  public static extern uint SendInput(uint nInputs, KeyInput[] pInputs, int cbSize);

  [Flags]
  public enum KeyEventFlag
  {
    KeyDown = 0x0000,
    ExtendedKey = 0x0001,
    KeyUp = 0x0002,
    Unicode = 0x0004,
    Scancode = 0x0008
  }

  [Flags]
  public enum InputType : int
  {
    Mouse = 0,
    Keyboard = 1,
    Hardware = 2
  }

  public struct KeyInput
  {
    public int type;
    public WinSend.InputUnion u;
  }

  [StructLayout(LayoutKind.Explicit)]
  public struct InputUnion
  {
    [FieldOffset(0)] public MouseInput mi;
    [FieldOffset(0)] public KeyboardInput ki;
    [FieldOffset(0)] public HardwareInput hi;
  }

  [StructLayout(LayoutKind.Sequential)]
  public struct KeyboardInput
  {
    public ushort wVk;
    public ushort wScan;
    public uint dwFlags;
    public uint time;
    public IntPtr dwExtraInfo;
  }

  const uint MAPVK_VK_TO_VSC = 0x00;
  const uint MAPVK_VSC_TO_VK = 0x01;
  const uint MAPVK_VK_TO_CHAR = 0x02;
  const uint MAPVK_VSC_TO_VK_EX = 0x03;
  const uint MAPVK_VK_TO_VSC_EX = 0x04;

  [TestMethod()]
  public void SendControlKeyTest() {
    DebugOn = true;
    const uint VK_CONTROL = 0x11;
    const uint VK_LCONTROL = 0xA2;
    const uint VK_LSHIFT = 0xA0;

    var keyList = new List<KeyInput>();

    // wVk = A virtual-key code. The code must be a value in the range 1 to 254.
    // If the dwFlags member specifies KEYEVENTF_UNICODE, wVk must be 0.

    // wScan = A hardware scan code for the key. If dwFlags specifies KEYEVENTF_UNICODE,
    // wScan specifies a Unicode char to send to the foreground application.

    // the MapVirtualKey calls return successfully
    var scanControl = MapVirtualKey((uint) VK_CONTROL, (uint) MAPVK_VK_TO_VSC);
    if (scanControl == 0) Dprint("MapVirtualKey for VK_CONTROL failed.p");
    var scanShift = MapVirtualKey((uint) VK_LSHIFT, (uint) MAPVK_VK_TO_VSC);
    if (scanShift == 0) Dprint("MapVirtualKey for VK_LSHIFT failed.");

    // this fails as is, fails with Flag.Unicode, and fails with no XXcode set
    // this also fails with either VK_CONTROL or 0 in the wVk field
    var vkdown = new KeyInput();
    vkdown.type = (int) InputType.Keyboard;
    vkdown.u.ki.wVk = (ushort) VK_CONTROL; // also fails with 0x0
    vkdown.u.ki.dwFlags = (uint) (KeyEventFlag.Scancode | KeyEventFlag.KeyDown);
    vkdown.u.ki.wScan = (ushort) scanControl;
    keyList.Add(vkdown);

    // this works - I see the 'p' when it runs
    var keydown = new KeyInput();
    keydown.type = (int) InputType.Keyboard;
    keydown.u.ki.wVk = 0; // must be zero if Flag.Unicode is set
    keydown.u.ki.dwFlags = (uint) (KeyEventFlag.Unicode | KeyEventFlag.KeyDown);
    keydown.u.ki.wScan = (ushort) 'p';
    keyList.Add(keydown);

    // this works - I see the 'p' when it runs
    var keyup = new KeyInput();
    keyup.type = (int) InputType.Keyboard;
    keyup.u.ki.wVk = 0; // must be zero if Flag.Unicode is set
    keyup.u.ki.dwFlags = (uint) (KeyEventFlag.Unicode | KeyEventFlag.KeyUp);
    keyup.u.ki.wScan = (ushort) 'p';
    keyList.Add(keyup);

    // this fails as is, fails with Flag.Unicode, and fails with no XXcode set
    // this also fails with either VK_CONTROL or 0 in the wVk field
    var vkup = new KeyInput();
    vkup.type = (int) InputType.Keyboard;
    vkup.u.ki.wVk = (ushort) VK_CONTROL; // also fails with 0x0
    vkup.u.ki.dwFlags = (uint) (KeyEventFlag.Scancode| KeyEventFlag.KeyUp);
    vkup.u.ki.wScan = (ushort) scanControl;
    keyList.Add(vkup);

    // SendInput returns 4, which means it sent 4 events successfully
    var keycount = SendInput((uint) keyList.Count, keyList.ToArray(),
      Marshal.SizeOf(typeof(KeyInput)));
    Dprint($"Sent {keycount} keys to the system.");

    // The Control-P should move my caret up one line, but it does not.
    // Shift should create a capital P in the buffer, but it does not.
    // All I see is a 'p' in the Visual studio buffer
  }
Kevin
  • 1,548
  • 2
  • 19
  • 34

1 Answers1

2

Sorry for indirect answer but here's 100% working and tested solution for WPF application.

internal static class NativeMethods
{
    [DllImport("user32.dll")]
    public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

    [Flags]
    public enum KeyboardFlags : uint
    {
        None = 0,

        /// <summary>
        /// KEYEVENTF_EXTENDEDKEY = 0x0001 (If specified, the scan code was preceded by a prefix byte that has the value 0xE0 (224).)
        /// </summary>
        ExtendedKey = 1,

        /// <summary>
        /// KEYEVENTF_KEYUP = 0x0002 (If specified, the key is being released. If not specified, the key is being pressed.)
        /// </summary>
        KeyUp = 2,

        /// <summary>
        /// KEYEVENTF_UNICODE = 0x0004 (If specified, wScan identifies the key and wVk is ignored.)
        /// </summary>
        Unicode = 4,

        /// <summary>
        /// KEYEVENTF_SCANCODE = 0x0008 (Windows 2000/XP: If specified, the system synthesizes a VK_PACKET keystroke. The wVk parameter must be zero. This flag can only be combined with the KEYEVENTF_KEYUP flag. For more information, see the Remarks section.)
        /// </summary>
        ScanCode = 8,
    }

    [Flags]
    public enum MouseFlags : uint
    {
        /// <summary>
        /// Specifies that movement occurred.
        /// </summary>
        Move = 0x0001,

        /// <summary>
        /// Specifies that the left button was pressed.
        /// </summary>
        LeftDown = 0x0002,

        /// <summary>
        /// Specifies that the left button was released.
        /// </summary>
        LeftUp = 0x0004,

        /// <summary>
        /// Specifies that the right button was pressed.
        /// </summary>
        RightDown = 0x0008,

        /// <summary>
        /// Specifies that the right button was released.
        /// </summary>
        RightUp = 0x0010,

        /// <summary>
        /// Specifies that the middle button was pressed.
        /// </summary>
        MiddleDown = 0x0020,

        /// <summary>
        /// Specifies that the middle button was released.
        /// </summary>
        MiddleUp = 0x0040,

        /// <summary>
        /// Windows 2000/XP: Specifies that an X button was pressed.
        /// </summary>
        XDown = 0x0080,

        /// <summary>
        /// Windows 2000/XP: Specifies that an X button was released.
        /// </summary>
        XUp = 0x0100,

        /// <summary>
        /// Windows NT/2000/XP: Specifies that the wheel was moved, if the mouse has a wheel. The amount of movement is specified in mouseData. 
        /// </summary>
        VerticalWheel = 0x0800,

        /// <summary>
        /// Specifies that the wheel was moved horizontally, if the mouse has a wheel. The amount of movement is specified in mouseData. Windows 2000/XP:  Not supported.
        /// </summary>
        HorizontalWheel = 0x1000,

        /// <summary>
        /// Windows 2000/XP: Maps coordinates to the entire desktop. Must be used with MOUSEEVENTF_ABSOLUTE.
        /// </summary>
        VirtualDesk = 0x4000,

        /// <summary>
        /// Specifies that the dx and dy members contain normalized absolute coordinates. If the flag is not set, dxand dy contain relative data (the change in position since the last reported position). This flag can be set, or not set, regardless of what kind of mouse or other pointing device, if any, is connected to the system. For further information about relative mouse motion, see the following Remarks section.
        /// </summary>
        Absolute = 0x8000,
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBDINPUT
    {
        public ushort virtualKey;
        public ushort scanCode;
        public KeyboardFlags flags;
        public uint timeStamp;
        public IntPtr extraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MOUSEINPUT
    {
        public int deltaX;
        public int deltaY;
        public uint mouseData;
        public MouseFlags flags;
        public uint time;
        public IntPtr extraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct HARDWAREINPUT
    {
        public uint message;
        public ushort wParamL;
        public ushort wParamH;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InputUnion
    {
        [FieldOffset(0)]
        public MOUSEINPUT mouse;
        [FieldOffset(0)]
        public KEYBDINPUT keyboard;
        [FieldOffset(0)]
        public HARDWAREINPUT hardware;
    }
    public enum InputType : int
    {
        Mouse = 0,
        Keyboard = 1,
        Hardware = 2
    }
    public struct INPUT
    {
        public InputType type;
        public InputUnion union;
    }
}

KeyInterop.VirtualKeyFromKey here is standard method from .NET class. I guess that it can be replaced with WinAPI MapVirtualKey.

public static void ModifiedKeyStroke(Key key, ModifierKeys modifiers)
{
    static NativeMethods.INPUT BuildINPUT(Key k, NativeMethods.KeyboardFlags flags) => new NativeMethods.INPUT
    {
        type = NativeMethods.InputType.Keyboard,
        union = new NativeMethods.InputUnion { keyboard = new NativeMethods.KEYBDINPUT { virtualKey = (ushort)KeyInterop.VirtualKeyFromKey(k), scanCode = 0, flags = flags, timeStamp = 0, extraInfo = IntPtr.Zero } }
    };
    List<Key> keys = new List<Key>();
    if (modifiers.HasFlag(ModifierKeys.Control)) keys.Add(Key.LeftCtrl);
    if (modifiers.HasFlag(ModifierKeys.Alt)) keys.Add(Key.LeftAlt);
    if (modifiers.HasFlag(ModifierKeys.Shift)) keys.Add(Key.LeftShift);
    if (modifiers.HasFlag(ModifierKeys.Windows)) keys.Add(Key.LWin);
    keys.Add(key);
    NativeMethods.INPUT[] inputs = new NativeMethods.INPUT[keys.Count * 2];
    uint i = 0;
    foreach (Key k in keys) inputs[i++] = BuildINPUT(k, NativeMethods.KeyboardFlags.None);
    keys.Reverse();
    foreach (Key k in keys) inputs[i++] = BuildINPUT(k, NativeMethods.KeyboardFlags.KeyUp);
    _ = NativeMethods.SendInput(i, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}

Usage

ModifiedKeyStroke(Key.C, ModifierKeys.Control); // sends Ctrl+C
aepot
  • 4,558
  • 2
  • 12
  • 24
  • Hi, thank you for posting your solution. Unfortunately, I'm not using WPF and not using Windows Forms(?) where you get your Key.* definitions. I was hoping to find out what my code was doing wrong, even though it was based on reading over a dozen other pieces of working code. – Kevin Jul 28 '20 at 13:29
  • @Kevin `Key` and `ModifierKeys` are simple `enum`s, you can grab it from WPF Metadata (copy it as text to the code). – aepot Jul 28 '20 at 13:32
  • @Kevin I suggest to backup the code from the answer if you're finding it interesting or helpful. I want to delete this downvoted and not accepted answer soon. – aepot Jul 28 '20 at 14:56
  • @Kevin here's [`KeyInterop.VirtualKeyFromKey`](https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Input/KeyInterop.cs) – aepot Jul 28 '20 at 15:01
  • 1
    Thank you for your help. I copied your code so you can delete the answer. I'm not sure why they downvoted someone who was trying to help. I compared your WPF KeyInterop keys to my own and they are in the same order and have the same values. I'm starting to wonder if the VStudio unit test buffer (or debugger layers) prevent modifier keys from working. Other keys such as left arrow work fine. It's just that shift and control don't work. I think I saw an alt-x case work in the unit test buffer because it triggered a menu on VS. Thank you again. – Kevin Jul 28 '20 at 16:01
  • @Kevin i noticed that I don't send `wScan`, it always 0, and i don`t use Unicode flag. – aepot Jul 28 '20 at 16:18
  • I added the ki struct to the code above. My code still fails in the unit test buffer when I: use wVk = VK_LSHIFT, wScan=0, and do not use Scancode or Unicode in the flags word. I notice your code uses `keys.Add(Key.LeftCtrl);` but `Key.LeftCtrl` (and `Key.LeftAlt` etc) are not defined in the posted code. Maybe the answer lies in them, since only the modifier keys fail to work in my code. – Kevin Jul 28 '20 at 17:43
  • @Kevin `KeyInterop.VirtualKeyFromKey(Key.LeftCtrl)` is `VK_LCONTROL` which is `0xA2`. And you added `MouseInput`, not `KeyboardInput`. – aepot Jul 28 '20 at 18:14
  • Thank you again for trying to help me. I wrote an external program to send the keys outside of the VStudio unit test / debugging method. Everything worked fine. I conclude that you cannot use VStudio unit tests (MSTestV2 framework, at least) to run code that sends modifier keys to the unit test buffer. If I use an external program to send the keys, I can insert text and move around in the unit test buffer without a problem. – Kevin Jul 28 '20 at 18:15
  • My apologies, you are right about MouseInput. They were side by side, and I copied the wrong one. I have added KeyBoardInput as you requested. I also upvoted your answer and accepted it, since it led to a solution. I see now that your keys list is a list of `Keys` and not a list of `KeyInputs` - I was confused by the difference in how we named things. Thank you again. – Kevin Jul 28 '20 at 18:20
  • @Kevin i guess that you may try split sending keys into 2 sequences (just for test). Send keydowns, then wait ~50 millis, then send keyups separately. I don't know if it can help but give it a try. Additionally you may do it asynchronously. What if you run Test method in the same Thread as program runs. Then while Test code is being executed, the program can't handle a keypress. Your `KeyboardInput` is perfect. – aepot Jul 28 '20 at 18:27
  • The VK_CONTROL, VK_SHIFT, and VK_LMENU key sequences work fine now outside of debugging using an external program, but the VK_LWIN (down), VK_S (down/up for the windows search window), VK_LWIN (up) sequence still fails. I can't imagine why not since the other keys work well. Maybe that should be a separate question. I might try using separate waits or threads as you suggest, but threads is a whole separate pile of work (I don't understand them well, and most of my keys are working. :-)) – Kevin Jul 28 '20 at 18:56
  • @Kevin `LWin` and `RWin` keys doesn't work for me too. I know that problem but didn't solve it because it doesn't affect anything in my projects. Maybe it can be googled separately :) – aepot Jul 28 '20 at 18:59
  • This post seems to provide a reason and a (complex) workaround to work with the VK_LWIN key. MS wants to prevent hackers and programs from using the VK_LWIN key, apparently. I will not try that approach. https://stackoverflow.com/questions/2914989/how-can-i-deal-with-depressed-windows-logo-key-when-using-sendinput – Kevin Jul 28 '20 at 19:03
  • @Kevin That's it :) – aepot Jul 28 '20 at 19:05