delphisendinput

Delphi routine to simulate typing


I'm trying to develop a routine in Delphi XE to output text to the screen to simulate typing text in a game. It's not as simple as concatenating characters to a TMemo for example, as this results in flickering even when using beginupdate/endupdate. I've used the technique here, converted in to Delphi code as best I can and modified it based on comments here but I'm not getting the correct text. I'm getting garbage I guess because the characters are somehow not in the right format. Does anyone see what is wrong here or know a better way to simulate typing?

procedure SendEnter();
var
  KeyInputs: array [0..1] of TInput;
begin
  ZeroMemory(@KeyInputs,sizeof(KeyInputs));
  KeyInputs[0].Itype := INPUT_KEYBOARD;
  KeyInputs[0].ki.wVk := VK_RETURN;
  KeyInputs[0].ki.dwFlags := 0;
  KeyInputs[1].Itype := INPUT_KEYBOARD;
  KeyInputs[1].ki.wVk := VK_RETURN;
  KeyInputs[1].ki.dwFlags := KEYEVENTF_EXTENDEDKEY;
  SendInput(2, KeyInputs[0], SizeOf(TInput));
end;

function TForm2.PrintOutLine(sLine: string): boolean;
var
  i: integer;
  VKRes: SmallInt;
  VK: byte;
  KeyInputsU: array [0..3] of TInput;
  KeyInputsL: array [0..1] of TInput;
  bUppercase: boolean;
begin
  Memo1.SetFocus;
  i:= 1;
  while (i<=Length(sLine)) do begin
    bUppercase:= sLine[i]=UpCase(sLine[i]);
    VKRes:= VkKeyScanEx(sLine[i],GetKeyboardLayout(0));
    VK:= VKRes;
    if (bUppercase) then begin
      ZeroMemory(@KeyInputsU,sizeof(KeyInputsU));
      KeyInputsU[0].Itype := INPUT_KEYBOARD;
      KeyInputsU[0].ki.wVk := VK_LSHIFT;
      //KeyInputsU[0].ki.dwFlags := 0;
      KeyInputsU[1].Itype := INPUT_KEYBOARD;
      KeyInputsU[1].ki.wVk := vk;
      KeyInputsU[1].ki.dwFlags := KEYEVENTF_UNICODE ;
      KeyInputsU[2].Itype := INPUT_KEYBOARD;
      KeyInputsU[2].ki.wVk := vk;
      KeyInputsU[2].ki.dwFlags := KEYEVENTF_UNICODE or KEYEVENTF_KEYUP;
      KeyInputsU[3].Itype := INPUT_KEYBOARD;
      KeyInputsU[3].ki.wVk := VK_LSHIFT;
      KeyInputsU[3].ki.dwFlags := KEYEVENTF_KEYUP;
      SendInput(4, KeyInputsU[0], SizeOf(TInput));
    end
    else begin
      ZeroMemory(@KeyInputsL,sizeof(KeyInputsL));
      KeyInputsL[0].Itype := INPUT_KEYBOARD;
      KeyInputsL[0].ki.wVk := vk;
      KeyInputsL[0].ki.dwFlags := KEYEVENTF_UNICODE;
      KeyInputsL[1].Itype := INPUT_KEYBOARD;
      KeyInputsL[1].ki.wVk := vk;
      KeyInputsL[1].ki.dwFlags := KEYEVENTF_UNICODE or KEYEVENTF_KEYUP;
      SendInput(2, KeyInputsL[0], SizeOf(TInput));
    end;
    Application.ProcessMessages;
    Sleep(80);
    inc(i);
  end;
  SendEnter;
  Form2.SetFocus;
  Result:= True;
end;

Solution

  • To answer your modified question, and your latest comment about non-alpha characters still not working correctly:

    Your detection of upper/lower case fails on any character not in a .. z. If you look at the documentation for System.UpCase() it states Character values not in the range a..z are unaffected. Therefore, if you feed it a <, you will get back the same <. Your code will interpret that as an upper case character although it's not.

    You have been told in a comment to send the keys (actually characters) with dwFlags KEYEVENTF_UNICODE. You seem to have adopted that only partially and also erroneously.

    Note that the MSDN documentation says:

    wVk: ...If the dwFlags member specifies KEYEVENTF_UNICODE, wVk must be 0.

    wScan: ...If dwFlags specifies KEYEVENTF_UNICODE, wScan specifies a Unicode character

    and further, for flag KEYEVENTF_UNICODE:

    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.

    Ergo, you do not need to detect and separately deal with upper case and lower case characters. You just set wScan to the ordinal of the UTF-16 character you want to send . Therefore your non-alpha characters also works correctly with following code modified from yours:

    function TForm3.PrintOutLine(sLine: string): boolean;
    var
      i: integer;
      KeyInputsL: array [0..1] of TInput;
    begin
      Memo1.SetFocus;
      i:= 1;
      while (i<=Length(sLine)) do begin
        ZeroMemory(@KeyInputsL,sizeof(KeyInputsL));
    
        KeyInputsL[0].Itype := INPUT_KEYBOARD;
    //    KeyInputsL[0].ki.wVk := vk;            // don't use wVk with KEYEVENTF_UNICODE
        KeyInputsL[0].ki.wScan := ord(sLine[i]); // instead use wScan
        KeyInputsL[0].ki.dwFlags := KEYEVENTF_UNICODE;
    
        KeyInputsL[1].Itype := INPUT_KEYBOARD;
    //    KeyInputsL[1].ki.wVk := vk;
        KeyInputsL[1].ki.wScan := ord(sLine[i]);
        KeyInputsL[1].ki.dwFlags := KEYEVENTF_UNICODE or KEYEVENTF_KEYUP;
    
        SendInput(2, KeyInputsL[0], SizeOf(TInput));
    
        Application.ProcessMessages;
        Sleep(80);
        inc(i);
      end;
      SendEnter;
      Form3.SetFocus;
      Result:= True;
    end;
    

    The above answers your actual question but does not consider surrogate pairs of UTF-16 codepoints. There's a full code (in C++) for that shown here

    Also, not a part of your question, but I can't let it pass without a comment: Application.ProcessMessages and Sleep() is not the correct way of sending one character at a time. Use a timer instead to trigger the sending of each character.