Sending Keystroke to another application using WinAPI
Asked Answered
E

2

8

I have to control another application by sending keystrokes to it like CTRLS or CTRLSHIFTC or CTRLF.

I've tried a lot of things, but I can't get it working. So I'm trying to get this right on a simpler case.

This successfully sends Hey to the notepad:

procedure TForm1.Button1Click(Sender: TObject);
  var notepad, edit: HWND;
begin
  notepad := FindWindow('notepad', nil);
  edit := FindWindowEx(notepad, FindWindow('Edit', nil), nil, nil);

  SendMessage(edit, WM_CHAR, dword('H'), 0);
  SendMessage(edit, WM_CHAR, dword('e'), 0);
  SendMessage(edit, WM_CHAR, dword('y'), 0);
end;

And this successfully sends the F5 key to the notepad, and also works with F3 poping up the Find dialog.

notepad := FindWindow('notepad', nil);
PostMessage(notepad, WM_KEYDOWN, VK_F5, 0);
PostMessage(notepad, WM_KEYUP, VK_F5, 0);

But I don't know why using SendMessage doesn't work on the exemple above.

The best thing I could came up was something like this, which does nothing.

notepad := FindWindow('notepad', nil);
PostMessage(notepad, WM_KEYDOWN, VK_CONTROL, 0);
PostMessage(notepad, WM_KEYDOWN, VkKeyScan('F'), 0);
PostMessage(notepad, WM_KEYUP, VkKeyScan('F'), 0);
PostMessage(notepad, WM_KEYUP, VK_CONTROL, 0);

I've found somewhere here a library which kinda emulate the VBScript send key functions, but just looking the code, it seems to just broadcast keys to the current application or all applications, since there's no Handle parameter.

Ell answered 6/4, 2013 at 17:11 Comment(0)
M
7

Warning: This method depends on implementation details, and should not be used if you need to guarantee the correctness of your program. (On the other hand, you are already on that path. For instance, IIRC, in Windows 95 there isn't even a Go to dialog.)

I opened notepad.exe in my favourite Resource Editor, and investigated the menu bar. I noticed that the ID of the Save menu item is 3. Hence, the following code executes the Save menu command in notepad:

var
  notepad: HWND;
begin
  notepad := FindWindow('notepad', nil);

  SendMessage(notepad, WM_COMMAND, 3, 0);

Similarly, Find is 21 in my version of notepad.exe. Go to is 24.

Update, according to comment: If you need to send Ctrl+Key, you can use SendInput:

var
  notepad: HWND;
  inputArray: array[0..3] of TInput;
begin
  notepad := FindWindow('notepad', nil);

  // TODO: Either exit if notepad isn't focused, or set focus to notepad

  FillChar(inputArray, length(inputArray) * sizeof(TInput), 0);

  inputArray[0].Itype := INPUT_KEYBOARD;
  inputArray[0].ki.wVk := VK_LCONTROL;
  inputArray[1].Itype := INPUT_KEYBOARD;
  inputArray[1].ki.wVk := VkKeyScan('S');
  inputArray[2].Itype := INPUT_KEYBOARD;
  inputArray[2].ki.wVk := VkKeyScan('S');
  inputArray[2].ki.dwFlags := KEYEVENTF_KEYUP;
  inputArray[3].Itype := INPUT_KEYBOARD;
  inputArray[3].ki.wVk := VK_LCONTROL;
  inputArray[3].ki.dwFlags := KEYEVENTF_KEYUP;

  SendInput(length(inputArray), inputArray[0], sizeof(TInput));
Marietta answered 6/4, 2013 at 17:14 Comment(10)
This is very usefull but doesn't work for me, because I need to automate a task in a game which doesn't have standard windows components, so AFAIK I can't find any command ID, I can just send messages to the top window handle, and the only reliable way I can find is that the keystrokes are always the same.Ell
Are there "old" versions of Delphi that do not support absolute? I thought that was inherited from Borland Pascal.Emmieemmit
@AndriyM: My understanding is that it was introduced in Delphi 2009, but apparently David disagrees on that one. I guess I was wrong.Marietta
I can see that you've already deleted the bit about absolute, so this is simply for confirmation. Delphi 6 and Delphi 2006, the two versions that I've been actively working with lately, support it, and I know for certain that that is the same absolute that I used quite often in Borland/Turbo Pascal. So yes, you can depend on it that all Delphi versions have absolute.Emmieemmit
@AndriyM: Myself, I tried Delphi 4 on my Win95 machine. Even the syntax highlighter recognised absolute.Marietta
You don't need absolute at all here. The following syntax works fine in XE3: SendInput(length(inputArray), inputarray[0], sizeof(TInput));Syreetasyria
Regarding absolute it has indeed existed since forever. It's a feature that the compiler engineers do not enjoy supporting and I expect it will disappear in the next gen compiler.Lonnalonnard
It worked, it's not ideally to SetForegroundWindow(notepad) but there's no better way yet. I have to ask what exactly the second parameter of sendInput, why it is inputarray[0] and not the whole array? I hope my question make senseEll
@Vitim_us: It actually is the whole array, sent by the address of the first element. The array is contiguous in memory (element 0 is directly followed by element 1, etc.). The first value passed to the function is the number of elements in the array, and the second is the address of the first element. The third value is the size of each element in the array.Syreetasyria
Did you read the documentation for SendInput? It is explained there.Lonnalonnard
S
4

I've been using keybd_event for ages. It will always work even if everything else fails because it feeds the input directly into the interface between the keyboard driver and Windows. There's really no difference between typing manually and generating keys using the function below. Only disadvantage is that the target window must always remain in the foreground.

procedure SendKey(Wnd,VK : Cardinal; Ctrl,Alt,Shift : Boolean);
var
  MC,MA,MS : Boolean;
begin
  // Try to bring target window to foreground
  ShowWindow(Wnd,SW_SHOW);
  SetForegroundWindow(Wnd);

  // Get current state of modifier keys
  MC:=Hi(GetAsyncKeyState(VK_CONTROL))>127;
  MA:=Hi(GetAsyncKeyState(VK_MENU))>127;
  MS:=Hi(GetAsyncKeyState(VK_SHIFT))>127;

  // Press modifier keys if necessary (unless already pressed by real user)
  if Ctrl<>MC then keybd_event(VK_CONTROL,0,Byte(MC)*KEYEVENTF_KEYUP,0);
  if Alt<>MA then keybd_event(VK_MENU,0,Byte(MA)*KEYEVENTF_KEYUP,0);
  if Shift<>MS then keybd_event(VK_SHIFT,0,Byte(MS)*KEYEVENTF_KEYUP,0);

  // Press key
  keybd_event(VK,0,0,0);
  keybd_event(VK,0,KEYEVENTF_KEYUP,0);

  // Release modifier keys if necessary
  if Ctrl<>MC then keybd_event(VK_CONTROL,0,Byte(Ctrl)*KEYEVENTF_KEYUP,0);
  if Alt<>MA then keybd_event(VK_MENU,0,Byte(Alt)*KEYEVENTF_KEYUP,0);
  if Shift<>MS then keybd_event(VK_SHIFT,0,Byte(Shift)*KEYEVENTF_KEYUP,0);
end;
Santiago answered 6/4, 2013 at 23:58 Comment(2)
It can fail. It fails when another process inserts keyboard events which interleave. That is why SendInput exists. I don't know why you think keybd_event is better than SendInput.Lonnalonnard
Games might not always respond to SendMessage() so a lower-level function is required. I assumed SendInput is a higher-level function based on SendMessage(). I stand corrected.Santiago

© 2022 - 2024 — McMap. All rights reserved.