Sending Keystrokes to external applications

Some users of my application have a need to send keystrokes to another application.

So far I’ve been able to get the handle of the specified application (exe name) and minimise, maximise, restore, move, resize and close it, but not had any luck with the keystrokes.

The target application may or may not be focused at the time. I’ve tried using SendMessage and PostMessage with WM_KEYDOWN / WM_KEYUP and WM_CHAR with no success.

It’s up to the end user to pick the application they want to control. I’ve only tried it with Notepad so far.

Is this an exercise in futility?

Have a look at the Windows API function: keybd_event

I’ve used it successfully to control other applications - just need to make sure that the application of interest is in the foreground so that it receives the key strokes.

“The target application may or may not be focused at the time” is the key bit for this application. In fact it’s almost guaranteed that the target application won’t be focused.

Sorry - I missed that. You might be struggling in that case.

Is there a reason why the application can’t be given focus at the time?

Yes, because mine has to have the focus 99% of the time as a control surface. :slight_smile:

Although I guess I could try the focus, send then get the focus back?

OK, I have the set focus, keybd_event and regain focus working.

Just need to work out the best way to let users specify the text or function keys and convert that to the keybd_event calls.

Thanks you very much for the pointers. :slight_smile:

You can just use my unit if you like - the code could probably do with a bit of a cleanup though:

unit ProgramControl;

interface

uses
  Windows,
  SysUtils,
  Clipbrd,
  Generics.Collections,
  WinApi.Messages;

type
  TKeyState    = (ks_Ctrl, ks_Alt, ks_Shift);
  TKeyStateSet = set of TKeyState;

function GetClipBoardText: string;
function SetClipBoardText(s: string): Boolean;

function SendKey(Wnd, VK: Cardinal; KeyStateSet: TKeyStateSet = []): Boolean;
function SendCopyCommand(Wnd: Cardinal): Boolean;
function SendPasteCommand(Wnd: Cardinal): Boolean;

function GetKeyCode(Code: string): Cardinal;

implementation

type
  TVKStringsDictionary = TDictionary<string, Cardinal>;

var
  VKStringsDictionary: TVKStringsDictionary;

function GetClipBoardText: string;
var
  i: Integer;
begin
  for i := 1 to 5 do
    begin
      try
        Result := ClipBoard.AsText;
        exit;
      except
        on e: Exception do

      end;
    end;
end;

function SetClipBoardText(s: string): Boolean;
var
  i: Integer;
begin
  Result := False;

  for i := 1 to 5 do
    begin
      try
        ClipBoard.AsText := s;
        Result           := True;
        exit;
      except
        on e: Exception do

      end;
    end;
end;

procedure KeyboardEvent(bVk: Byte; bScan: Byte; dwFlags: DWORD; dwExtraInfo: UIntPtr);
begin
  keybd_event(bVk, bScan, dwFlags, dwExtraInfo);
end;

function SendKey(Wnd, VK: Cardinal; KeyStateSet: TKeyStateSet = []): Boolean;
var
  Ctrl, Alt, Shift: Boolean;
  MC, MA, MS: Boolean;
begin
  Result := False;

  // Try to bring target window to foreground
  ShowWindow(Wnd, SW_SHOW);

  if Wnd <> 0 then
    begin
      if GetForegroundWindow <> Wnd then
        begin
          if not SetForegroundWindow(Wnd) then
            exit;

          Sleep(500);

          if not SetForegroundWindow(Wnd) then
            exit;
        end;
    end;

  Ctrl  := ks_Ctrl in KeyStateSet;
  Alt   := ks_Alt in KeyStateSet;
  Shift := ks_Shift in KeyStateSet;

  // 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
    KeyboardEvent(VK_CONTROL, 0, Byte(MC) * KEYEVENTF_KEYUP, 0);
  if Alt <> MA then
    KeyboardEvent(VK_MENU, 0, Byte(MA) * KEYEVENTF_KEYUP, 0);
  if Shift <> MS then
    KeyboardEvent(VK_SHIFT, 0, Byte(MS) * KEYEVENTF_KEYUP, 0);

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

  // Release modifier keys if necessary
  if Ctrl <> MC then
    KeyboardEvent(VK_CONTROL, 0, Byte(Ctrl) * KEYEVENTF_KEYUP, 0);
  if Alt <> MA then
    KeyboardEvent(VK_MENU, 0, Byte(Alt) * KEYEVENTF_KEYUP, 0);
  if Shift <> MS then
    KeyboardEvent(VK_SHIFT, 0, Byte(Shift) * KEYEVENTF_KEYUP, 0);

  Result := True;
end;

function SendCopyCommand(Wnd: Cardinal): Boolean;
begin
  Result := SendKey(Wnd, VkKeyScan('C'), [ks_Ctrl]);
end;

function SendPasteCommand(Wnd: Cardinal): Boolean;
begin
  Result := SendKey(Wnd, VkKeyScan('V'), [ks_Ctrl]);
end;

function GetKeyCode(Code: string): Cardinal;
begin
  if not VKStringsDictionary.TryGetValue(UpperCase(Code), Result) then
    Result := 0;

  if (Result = 0) and (Length(Code) = 1) then
    Result := Ord(Code[1]);
end;

initialization
  VKStringsDictionary := TVKStringsDictionary.Create;
  VKStringsDictionary.Add('LBUTTON', VK_LBUTTON);
  VKStringsDictionary.Add('RBUTTON', VK_RBUTTON);
  VKStringsDictionary.Add('CANCEL', VK_CANCEL);
  VKStringsDictionary.Add('MBUTTON', VK_MBUTTON);
  VKStringsDictionary.Add('XBUTTON1', VK_XBUTTON1);
  VKStringsDictionary.Add('XBUTTON2', VK_XBUTTON2);
  VKStringsDictionary.Add('BACK', VK_BACK);
  VKStringsDictionary.Add('TAB', VK_TAB);
  VKStringsDictionary.Add('CLEAR', VK_CLEAR);
  VKStringsDictionary.Add('RETURN', VK_RETURN);
  VKStringsDictionary.Add('SHIFT', VK_SHIFT);
  VKStringsDictionary.Add('CONTROL', VK_CONTROL);
  VKStringsDictionary.Add('MENU', VK_MENU);
  VKStringsDictionary.Add('PAUSE', VK_PAUSE);
  VKStringsDictionary.Add('CAPITAL', VK_CAPITAL);
  VKStringsDictionary.Add('KANA', VK_KANA);
  VKStringsDictionary.Add('HANGUL', VK_HANGUL);
  VKStringsDictionary.Add('JUNJA', VK_JUNJA);
  VKStringsDictionary.Add('FINAL', VK_FINAL);
  VKStringsDictionary.Add('HANJA', VK_HANJA);
  VKStringsDictionary.Add('KANJI', VK_KANJI);
  VKStringsDictionary.Add('CONVERT', VK_CONVERT);
  VKStringsDictionary.Add('NONCONVERT', VK_NONCONVERT);
  VKStringsDictionary.Add('ACCEPT', VK_ACCEPT);
  VKStringsDictionary.Add('MODECHANGE', VK_MODECHANGE);
  VKStringsDictionary.Add('ESCAPE', VK_ESCAPE);
  VKStringsDictionary.Add('SPACE', VK_SPACE);
  VKStringsDictionary.Add('PRIOR', VK_PRIOR);
  VKStringsDictionary.Add('NEXT', VK_NEXT);
  VKStringsDictionary.Add('END', VK_END);
  VKStringsDictionary.Add('HOME', VK_HOME);
  VKStringsDictionary.Add('LEFT', VK_LEFT);
  VKStringsDictionary.Add('UP', VK_UP);
  VKStringsDictionary.Add('RIGHT', VK_RIGHT);
  VKStringsDictionary.Add('DOWN', VK_DOWN);
  VKStringsDictionary.Add('SELECT', VK_SELECT);
  VKStringsDictionary.Add('PRINT', VK_PRINT);
  VKStringsDictionary.Add('EXECUTE', VK_EXECUTE);
  VKStringsDictionary.Add('SNAPSHOT', VK_SNAPSHOT);
  VKStringsDictionary.Add('INSERT', VK_INSERT);
  VKStringsDictionary.Add('DELETE', VK_DELETE);
  VKStringsDictionary.Add('HELP', VK_HELP);
  VKStringsDictionary.Add('LWIN', VK_LWIN);
  VKStringsDictionary.Add('RWIN', VK_RWIN);
  VKStringsDictionary.Add('APPS', VK_APPS);
  VKStringsDictionary.Add('SLEEP', VK_SLEEP);
  VKStringsDictionary.Add('NUMPAD0', VK_NUMPAD0);
  VKStringsDictionary.Add('NUMPAD1', VK_NUMPAD1);
  VKStringsDictionary.Add('NUMPAD2', VK_NUMPAD2);
  VKStringsDictionary.Add('NUMPAD3', VK_NUMPAD3);
  VKStringsDictionary.Add('NUMPAD4', VK_NUMPAD4);
  VKStringsDictionary.Add('NUMPAD5', VK_NUMPAD5);
  VKStringsDictionary.Add('NUMPAD6', VK_NUMPAD6);
  VKStringsDictionary.Add('NUMPAD7', VK_NUMPAD7);
  VKStringsDictionary.Add('NUMPAD8', VK_NUMPAD8);
  VKStringsDictionary.Add('NUMPAD9', VK_NUMPAD9);
  VKStringsDictionary.Add('MULTIPLY', VK_MULTIPLY);
  VKStringsDictionary.Add('ADD', VK_ADD);
  VKStringsDictionary.Add('SEPARATOR', VK_SEPARATOR);
  VKStringsDictionary.Add('SUBTRACT', VK_SUBTRACT);
  VKStringsDictionary.Add('DECIMAL', VK_DECIMAL);
  VKStringsDictionary.Add('DIVIDE', VK_DIVIDE);
  VKStringsDictionary.Add('F1', VK_F1);
  VKStringsDictionary.Add('F2', VK_F2);
  VKStringsDictionary.Add('F3', VK_F3);
  VKStringsDictionary.Add('F4', VK_F4);
  VKStringsDictionary.Add('F5', VK_F5);
  VKStringsDictionary.Add('F6', VK_F6);
  VKStringsDictionary.Add('F7', VK_F7);
  VKStringsDictionary.Add('F8', VK_F8);
  VKStringsDictionary.Add('F9', VK_F9);
  VKStringsDictionary.Add('F10', VK_F10);
  VKStringsDictionary.Add('F11', VK_F11);
  VKStringsDictionary.Add('F12', VK_F12);
  VKStringsDictionary.Add('F13', VK_F13);
  VKStringsDictionary.Add('F14', VK_F14);
  VKStringsDictionary.Add('F15', VK_F15);
  VKStringsDictionary.Add('F16', VK_F16);
  VKStringsDictionary.Add('F17', VK_F17);
  VKStringsDictionary.Add('F18', VK_F18);
  VKStringsDictionary.Add('F19', VK_F19);
  VKStringsDictionary.Add('F20', VK_F20);
  VKStringsDictionary.Add('F21', VK_F21);
  VKStringsDictionary.Add('F22', VK_F22);
  VKStringsDictionary.Add('F23', VK_F23);
  VKStringsDictionary.Add('F24', VK_F24);
  VKStringsDictionary.Add('NUMLOCK', VK_NUMLOCK);
  VKStringsDictionary.Add('SCROLL', VK_SCROLL);
  VKStringsDictionary.Add('LSHIFT', VK_LSHIFT);
  VKStringsDictionary.Add('RSHIFT', VK_RSHIFT);
  VKStringsDictionary.Add('LCONTROL', VK_LCONTROL);
  VKStringsDictionary.Add('RCONTROL', VK_RCONTROL);
  VKStringsDictionary.Add('LMENU', VK_LMENU);
  VKStringsDictionary.Add('RMENU', VK_RMENU);
  VKStringsDictionary.Add('BROWSER_BACK', VK_BROWSER_BACK);
  VKStringsDictionary.Add('BROWSER_FORWARD', VK_BROWSER_FORWARD);
  VKStringsDictionary.Add('BROWSER_REFRESH', VK_BROWSER_REFRESH);
  VKStringsDictionary.Add('BROWSER_STOP', VK_BROWSER_STOP);
  VKStringsDictionary.Add('BROWSER_SEARCH', VK_BROWSER_SEARCH);
  VKStringsDictionary.Add('BROWSER_FAVORITES', VK_BROWSER_FAVORITES);
  VKStringsDictionary.Add('BROWSER_HOME', VK_BROWSER_HOME);
  VKStringsDictionary.Add('VOLUME_MUTE', VK_VOLUME_MUTE);
  VKStringsDictionary.Add('VOLUME_DOWN', VK_VOLUME_DOWN);
  VKStringsDictionary.Add('VOLUME_UP', VK_VOLUME_UP);
  VKStringsDictionary.Add('MEDIA_NEXT_TRACK', VK_MEDIA_NEXT_TRACK);
  VKStringsDictionary.Add('MEDIA_PREV_TRACK', VK_MEDIA_PREV_TRACK);
  VKStringsDictionary.Add('MEDIA_STOP', VK_MEDIA_STOP);
  VKStringsDictionary.Add('MEDIA_PLAY_PAUSE', VK_MEDIA_PLAY_PAUSE);
  VKStringsDictionary.Add('LAUNCH_MAIL', VK_LAUNCH_MAIL);
  VKStringsDictionary.Add('LAUNCH_MEDIA_SELECT', VK_LAUNCH_MEDIA_SELECT);
  VKStringsDictionary.Add('LAUNCH_APP1', VK_LAUNCH_APP1);
  VKStringsDictionary.Add('LAUNCH_APP2', VK_LAUNCH_APP2);
  VKStringsDictionary.Add('OEM_1', VK_OEM_1);
  VKStringsDictionary.Add('OEM_PLUS', VK_OEM_PLUS);
  VKStringsDictionary.Add('OEM_COMMA', VK_OEM_COMMA);
  VKStringsDictionary.Add('OEM_MINUS', VK_OEM_MINUS);
  VKStringsDictionary.Add('OEM_PERIOD', VK_OEM_PERIOD);
  VKStringsDictionary.Add('OEM_2', VK_OEM_2);
  VKStringsDictionary.Add('OEM_3', VK_OEM_3);
  VKStringsDictionary.Add('OEM_4', VK_OEM_4);
  VKStringsDictionary.Add('OEM_5', VK_OEM_5);
  VKStringsDictionary.Add('OEM_6', VK_OEM_6);
  VKStringsDictionary.Add('OEM_7', VK_OEM_7);
  VKStringsDictionary.Add('OEM_8', VK_OEM_8);
  VKStringsDictionary.Add('OEM_102', VK_OEM_102);
  VKStringsDictionary.Add('PACKET', VK_PACKET);
  VKStringsDictionary.Add('PROCESSKEY', VK_PROCESSKEY);
  VKStringsDictionary.Add('ATTN', VK_ATTN);
  VKStringsDictionary.Add('CRSEL', VK_CRSEL);
  VKStringsDictionary.Add('EXSEL', VK_EXSEL);
  VKStringsDictionary.Add('EREOF', VK_EREOF);
  VKStringsDictionary.Add('PLAY', VK_PLAY);
  VKStringsDictionary.Add('ZOOM', VK_ZOOM);
  VKStringsDictionary.Add('NONAME', VK_NONAME);
  VKStringsDictionary.Add('PA1', VK_PA1);
  VKStringsDictionary.Add('OEM_CLEAR', VK_OEM_CLEAR);
finalization
  VKStringsDictionary.Free;
end.
1 Like

Thanks for that. I don’t understand all of it yet, but I’ll have a go !

Tangentially related, but I just like that this exists. :slight_smile:

" A Key’s Odyssey - By Peter Below"
https://edn.embarcadero.com/article/38447

1 Like

Ti Paul,
Golly me, that is about the most brilliant piece I have ever read.

It is very well written, and it sure covered ALL the details. Every single one of em.

Thanks for telling us about it.

1 Like

Hi @John_Walker. It’s very cool, imo.