6

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.

Johan
  • 35,120
  • 54
  • 178
  • 293
  • 1
    Is this asp.net or winforms? You've tagged both. – keyboardP Jul 31 '13 at 23:00
  • 2
    Is the purpose of holding down the key to trigger auto-repeat, or is there some other purpose? – Robert Harvey Jul 31 '13 at 23:01
  • @RobertHarvey What do you mean by auto-repeat? – Johan Jul 31 '13 at 23:03
  • 2
    I'll ask my question in a different way: why do you need to hold it down for one second? – Robert Harvey Jul 31 '13 at 23:04
  • @keyboardP your answer is almost fine, just need to modify a little, why delete? – King King Jul 31 '13 at 23:06
  • @RobertHarvey I got what you mean by auto-repeat. Yes, that's what I'm after. I'm trying to simulate user-like behavior in another application – Johan Jul 31 '13 at 23:07
  • 1
    Then simply wait for a half-second or so, and then send repeated keystrokes, just like auto-repeat would. – Robert Harvey Jul 31 '13 at 23:07
  • 2
    @KingKing I think the approach may be a bit too reliant on assuming how the system would react and therefore may have unintended consequences. I've not tried using `PostMessage` like that so I'm not too sure what side-effects there may be. – keyboardP Jul 31 '13 at 23:08
  • @RobertHarvey I guess that would work. But doesn't the actual auto repeat frequency differ between computers? It's a windows setting, right? Not that it matter that much though... – Johan Jul 31 '13 at 23:08
  • 2
    @Johan consider `SystemInformation.KeyboardRepeat` we should not auto-repeat with an arbitrary period of time as the cycle, otherwise it's not real simulation. – King King Jul 31 '13 at 23:10
  • The usual settings are 1/2 second delay, and about 20 characters per second repeat rate. Hardly anyone ever bothers to change those. As King Kong points out, you can read those settings from the [SystemInformation](http://msdn.microsoft.com/en-us/library/System.Windows.Forms.SystemInformation_properties.aspx) class. – Robert Harvey Jul 31 '13 at 23:11

6 Answers6

6

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Ok, and what would the timer do, if not `PostMessage()`? – Johan Aug 01 '13 at 23:19
  • Why do you assume the timer's Tick event handler wouldn't use PostMessage? It usually works fine if you have a good window handle. If not then SendInput is the better mouse trap. Erm, keyboard trap. SendKeys tends to work too. I'm not recommending any one in particular, without knowing anything about what kind of process is being targeted, I assumed this question was about getting keystrokes to repeat. – Hans Passant Aug 01 '13 at 23:31
  • I'm referring to your answer; "Windows and PostMessage() is not going to do it for you". I wanted to know why it's not enough and what I should use instead. – Johan Aug 02 '13 at 11:23
  • It is not going to **repeat** the keystroke. That's why you need a Timer. Write an event handler for its Tick event and send the key. Set the Interval to about 100 milliseconds. – Hans Passant Aug 02 '13 at 11:25
  • Thanks, but I tried that already. Both with a timer and a synchronous loop using `Thread.Sleep()`, with both `PostMessage` and `SendMessage`. I don't know why the other app doesn't recognize it as a key being held down. Do you know any other approaches? – Johan Aug 02 '13 at 11:47
  • Sorry, I answered the question you asked. I can't help you figure out why this app is not co-operating, you didn't tell me anything about it. It is rather a common problem, so be sure to search for answers first. Click the Ask Question button again if that doesn't help, be sure to document your question well. – Hans Passant Aug 02 '13 at 11:54
  • @Johan using `SendKeys.Send` would work as you expected, however using `SendMessage` is not simple for the complexity of keystroke data. If it's a mouse click, the problem may be solved more easily. – King King Aug 10 '13 at 18:28
4

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);
}
Community
  • 1
  • 1
Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
1

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.
Ming Slogar
  • 2,327
  • 1
  • 19
  • 41
1

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.

AmazingTurtle
  • 1,187
  • 1
  • 15
  • 31
  • 1
    I don't know if you are paying attention to the OP when he specifically said he is not interested in using the `Thread.Sleep()` solution. – Edper Aug 09 '13 at 12:17
  • 2
    He's not interested in a sleep that block the ui thread - this solution doesn't block the ui thread but the "worker thread" that executes this code. This is a valid solution - as long as there aren't too many events like this. – MasterPlanMan Aug 09 '13 at 16:08
  • Why not just use a `Timer`, that's what it's for? – Peter Ritchie Aug 11 '13 at 21:29
1

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)));
}
Will Calderwood
  • 4,393
  • 3
  • 39
  • 64
0

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.

Community
  • 1
  • 1
Ermac Pd
  • 3
  • 2