Johan
Johan

Reputation: 35194

Simulate a keypress for X seconds

Here is my code that I use to simulate a tab-keypress in a certain process:

[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);

public Form1()
{
    PostMessage(MemoryHandler.GetMainWindowHandle(), 
               (int)KeyCodes.WMessages.WM_KEYDOWN, 
               (int)KeyCodes.VKeys.VK_TAB, 0);

    InitializeComponent();
}

Is there any way to extend it so that it presses the key for (example) 1 second, instead of just tapping it?

Please note that I'm not interested in a Thread.Sleep() solution that blocks the UI thread.

Upvotes: 6

Views: 7220

Answers (6)

Peter Ritchie
Peter Ritchie

Reputation: 35881

If you want to simulate what Windows does with messages, you likely want to find out how fast the key repeat rate is. That can be found at HKEY_CURRENT_USER\Control Panel\Keyboard\KeyboardSpeed. there's also the KeyboardDelay value.

What Windows does is send a WM_KEYDOWN and a WM_CHAR when a key is initially pressed. Then, if the key is still pressed after KeyboardDelay time span, the WM_KEYDOWN and WM_CHAR pair are repeated every KeyboardSpeed until the key is depressed--at which point WM_KEYUP is sent.

I would suggest using a Timer to send the messages at a specific frequencies.

Update:

for example:

int keyboardDelay, keyboardSpeed;
using (var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Keyboard"))
{
    Debug.Assert(key != null);
    keyboardDelay = 1;
    int.TryParse((String)key.GetValue("KeyboardDelay", "1"), out keyboardDelay);
    keyboardSpeed = 31;
    int.TryParse((String)key.GetValue("KeyboardSpeed", "31"), out keyboardSpeed);
}

maxRepeatedCharacters = 30; // repeat char 30 times
var startTimer = new System.Windows.Forms.Timer {Interval = keyboardSpeed};
startTimer.Tick += startTimer_Tick;
startTimer.Start();
var repeatTimer = new System.Windows.Forms.Timer();
repeatTimer.Interval += keyboardDelay;
repeatTimer.Tick += repeatTimer_Tick;

//...

private static void repeatTimer_Tick(object sender, EventArgs e)
{
    PostMessage(MemoryHandler.GetMainWindowHandle(),
               (int)KeyCodes.WMessages.WM_KEYDOWN,
               (int)KeyCodes.VKeys.VK_TAB, 0);
    PostMessage(MemoryHandler.GetMainWindowHandle(),
                (int)KeyCodes.WMessages.WM_CHAR,
                (int)KeyCodes.VKeys.VK_TAB, 0);

    counter++;
    if (counter > maxRepeatedCharacters)
    {
        Timer timer = sender as Timer;
        timer.Stop();
    }
}

private static void startTimer_Tick(object sender, EventArgs eventArgs)
{
    Timer timer = sender as Timer;
    timer.Stop();
    PostMessage(MemoryHandler.GetMainWindowHandle(),
               (int)KeyCodes.WMessages.WM_KEYDOWN,
               (int)KeyCodes.VKeys.VK_TAB, 0);
    PostMessage(MemoryHandler.GetMainWindowHandle(),
               (int)KeyCodes.WMessages.WM_CHAR,
               (int)KeyCodes.VKeys.VK_TAB, 0);
}

Upvotes: 3

Will Calderwood
Will Calderwood

Reputation: 4636

I'm not sure exactly what you're trying to achieve, but below is the function I use to simulate text input using SendInput.

If you alter this slightly to make the final call to SendInput from a new thread, and then separate out the down and up events with a timer, does that achieve what you need?

[DllImport("user32.dll", SetLastError = true)]
static extern UInt32 SendInput(UInt32 numberOfInputs, INPUT[] inputs, Int32 sizeOfInputStructure);

public enum InputType : uint
{
    MOUSE = 0,
    KEYBOARD = 1,
    HARDWARE = 2,
}

struct INPUT
{
    public UInt32 Type;
    public MOUSEKEYBDHARDWAREINPUT Data;
}

struct KEYBDINPUT
{
    public UInt16 Vk;
    public UInt16 Scan;
    public UInt32 Flags;
    public UInt32 Time;
    public IntPtr ExtraInfo;
}

public enum KeyboardFlag : uint // UInt32
{
    EXTENDEDKEY = 0x0001,
    KEYUP = 0x0002,
    UNICODE = 0x0004,
    SCANCODE = 0x0008,
}

public static void SimulateTextEntry(string text)
{
    if (text.Length > UInt32.MaxValue / 2) throw new ArgumentException(string.Format("The text parameter is too long. It must be less than {0} characters.", UInt32.MaxValue / 2), "text");

    var chars = UTF8Encoding.ASCII.GetBytes(text);
    var len = chars.Length;
    INPUT[] inputList = new INPUT[len * 2];
    for (int x = 0; x < len; x++)
    {
        UInt16 scanCode = chars[x];

        var down = new INPUT();
        down.Type = (UInt32)InputType.KEYBOARD;
        down.Data.Keyboard = new KEYBDINPUT();
        down.Data.Keyboard.Vk = 0;
        down.Data.Keyboard.Scan = scanCode;
        down.Data.Keyboard.Flags = (UInt32)KeyboardFlag.UNICODE;
        down.Data.Keyboard.Time = 0;
        down.Data.Keyboard.ExtraInfo = IntPtr.Zero;

        var up = new INPUT();
        up.Type = (UInt32)InputType.KEYBOARD;
        up.Data.Keyboard = new KEYBDINPUT();
        up.Data.Keyboard.Vk = 0;
        up.Data.Keyboard.Scan = scanCode;
        up.Data.Keyboard.Flags = (UInt32)(KeyboardFlag.KEYUP | KeyboardFlag.UNICODE);
        up.Data.Keyboard.Time = 0;
        up.Data.Keyboard.ExtraInfo = IntPtr.Zero;

        // Handle extended keys:
        // If the scan code is preceded by a prefix byte that has the value 0xE0 (224),
        // we need to include the KEYEVENTF_EXTENDEDKEY flag in the Flags property. 
        if ((scanCode & 0xFF00) == 0xE000)
        {
            down.Data.Keyboard.Flags |= (UInt32)KeyboardFlag.EXTENDEDKEY;
            up.Data.Keyboard.Flags |= (UInt32)KeyboardFlag.EXTENDEDKEY;
        }

        inputList[2*x] = down;
        inputList[2*x + 1] = up;

    }

    var numberOfSuccessfulSimulatedInputs = SendInput((UInt32)len*2, inputList, Marshal.SizeOf(typeof(INPUT)));
}

Upvotes: 1

AmazingTurtle
AmazingTurtle

Reputation: 1207

I would do it in a thread, for sleeping and not-blocking the UI thread. Look at this:

System.Threading.Thread KeyThread = new System.Threading.Thread(() => {
       //foreach process
       // press key now
       PostMessage(MemoryHandler.GetMainWindowHandle(), 
              (int)KeyCodes.WMessages.WM_KEYDOWN, 
              (int)KeyCodes.VKeys.VK_TAB, 0);
       System.Threading.Thread.Sleep(1000); // wait 1 second

       //foreach process
       // release keys again
       PostMessage(MemoryHandler.GetMainWindowHandle(), 
              (int)KeyCodes.WMessages.WM_KEYUP, 
              (int)KeyCodes.VKeys.VK_TAB, 0);
});

Ofcourse, you have to start it.

Upvotes: 1

Ming Slogar
Ming Slogar

Reputation: 2357

When holding a key down on a physical keyboard, repeated keystrokes are passed to the active window. This is not built into the keyboard, but is a Windows feature.

You can simulate this by doing the following steps in order:

  1. Send a keydown message.
  2. Run a timer at 30 ms intervals (the default in Windows, changeable through Ease of Access settings), sending a keypress message to the window at each tick.
  3. Send a keyup message.

Upvotes: 1

Hans Passant
Hans Passant

Reputation: 941317

The repeating of a keystroke when you hold it down is a feature that's built into the keyboard controller. A microprocessor that's built into the keyboard itself. The 8042 microcontroller was the traditional choice, the keyboard device driver still carries its name.

So, no, this is not done by Windows and PostMessage() is not going to do it for you. Not exactly a problem, you can simply emulate it with a Timer.

Upvotes: 6

Ermac Pd
Ermac Pd

Reputation: 3

Not sure what you're doing with the PostMessage but modifying some code from here: SendKey.Send() Not working

   [DllImport("user32.dll", SetLastError = true)]
    static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
    public static void PressKey(Keys key, bool up)
    {
        const int KEYEVENTF_EXTENDEDKEY = 0x1;
        const int KEYEVENTF_KEYUP = 0x2;
        if (up)
        {
            keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, (UIntPtr)0);
        }
        else
        {
            keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY, (UIntPtr)0);
        }
    }

    void TestProc()
    {
        PressKey(Keys.Tab, false);
        Thread.Sleep(1000);
        PressKey(Keys.Tab, true);
    }

Maybe this could work for you. Its just a key down and then a key up with a sleep in between. You could even add to this further and pass a time value for how long you want the key to stay down.

Upvotes: 0

Related Questions