0

I have the following code written in Delphi 10.1 Berlin, that tries to simulate a Shift+Right Arrow key on Windows in order to select text in a memo. This is a Delphi Firemonkey project, but that should not matter for my problem. The problem is that this code does what it is supposed to do on some Windows machines (selects text letter by letter), but it fails on others (only moves cursor pos, as if shift key is ignored). The failing of the code appears not to be specific to a Windows version, as it works on win 8.1 as well as on some Win 10 Fall Creatores Updates, but fails on some Win 10 Fall Creators installations. It seems also not specific to the SendInput mehtod, as the same phenomenon shows up using keybd_event. Anybody any ideas ?

 unit Unit63;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo;

type
  TForm63 = class(TForm)
    mmo1: TMemo;
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form63: TForm63;

implementation

uses windows;

{$R *.fmx}

procedure SendShiftRight;
var
  KeyInputs: array of TInput;
  KeyInputCount: Integer;

  procedure KeybdInput(VKey: Byte; Flags: DWORD);
  begin
    Inc(KeyInputCount);
    SetLength(KeyInputs, KeyInputCount);
    KeyInputs[KeyInputCount - 1].Itype := INPUT_KEYBOARD;
    with  KeyInputs[KeyInputCount - 1].ki do
    begin
      wVk := VKey;
      wScan := MapVirtualKey(wVk, 0);
      dwFlags := Flags;
      time := 0;
      dwExtraInfo := 0;
    end;
  end;
begin
  KeyInputCount := 0;
  KeybdInput(VK_SHIFT, 0);                // Shift
  KeybdInput(VK_RIGHT, 0);                 // Right
  KeybdInput(VK_RIGHT, KEYEVENTF_KEYUP);   // Right
  KeybdInput(VK_SHIFT, KEYEVENTF_KEYUP); // Shift
  SendInput(KeyInputCount, KeyInputs[0], SizeOf(KeyInputs[0]));
end;

procedure PostKeyEx32(key: Word; const shift: TShiftState; specialkey: Boolean);
{************************************************************
* Procedure PostKeyEx32
*
* Parameters:
*  key    : virtual keycode of the key to send. For printable
*           keys this is simply the ANSI code (Ord(character)).
*  shift  : state of the modifier keys. This is a set, so you
*           can set several of these keys (shift, control, alt,
*           mouse buttons) in tandem. The TShiftState type is
*           declared in the Classes Unit.
*  specialkey: normally this should be False. Set it to True to
*           specify a key on the numeric keypad, for example.
* Description:
*  Uses keybd_event to manufacture a series of key events matching
*  the passed parameters. The events go to the control with focus.
*  Note that for characters key is always the upper-case version of
*  the character. Sending without any modifier keys will result in
*  a lower-case character, sending it with [ssShift] will result
*  in an upper-case character!
// Code by P. Below
************************************************************}
type
  TShiftKeyInfo = record
    shift: Byte;
    vkey: Byte;
  end;
  byteset = set of 0..7;
const
  shiftkeys: array [1..3] of TShiftKeyInfo =
    ((shift: Ord(ssCtrl); vkey: VK_CONTROL),
    (shift: Ord(ssShift); vkey: VK_SHIFT),
    (shift: Ord(ssAlt); vkey: VK_MENU));
var
  flag: DWORD;
  bShift: ByteSet absolute shift;
  i: Integer;
begin
  for i := 1 to 3 do
  begin
    if shiftkeys[i].shift in bShift then
      keybd_event(shiftkeys[i].vkey, MapVirtualKey(shiftkeys[i].vkey, 0), 0, 0);
  end; { For }
  if specialkey then
    flag := KEYEVENTF_EXTENDEDKEY
  else
    flag := 0;

  keybd_event(key, MapvirtualKey(key, 0), flag, 0);
  flag := flag or KEYEVENTF_KEYUP;
  keybd_event(key, MapvirtualKey(key, 0), flag, 0);

  for i := 3 downto 1 do
  begin
    if shiftkeys[i].shift in bShift then
      keybd_event(shiftkeys[i].vkey, MapVirtualKey(shiftkeys[i].vkey, 0),
        KEYEVENTF_KEYUP, 0);
  end; { For }
end; { PostKeyEx32 }


procedure TForm63.btn1Click(Sender: TObject);
begin
  mmo1.SetFocus;
  SendShiftRight;
  // PostKeyEx32(vkRight, [ssShift], False);    // doesn't work either
end;


end.
iamjoosy
  • 3,299
  • 20
  • 30
  • It will fail on Secure Boot x64 Windows 10, you now need UIAccess to use SendInput. https://msdn.microsoft.com/en-us/library/bb625963.aspx – FredS Nov 15 '17 at 21:52
  • Thanks, @FredS. I edited my question to be more specific about what fails. On the "failing" machines only the right arrow key press is sent, supressing the "shift" key. If Secure Boot is the culprit I would expect that all SendInput is spressed. Anyway I will give it a try and disable Scure Boot – iamjoosy Nov 16 '17 at 09:39
  • Ok, Disabling Secure Boot did not solve the problem. – iamjoosy Nov 16 '17 at 10:01
  • 1
    I had something similar with [MetaKeys](http://yoy.be/metakeys.html) and had to solve it by disabling the num-lock first when it's active... (Almost forgot, I did open-source this: https://github.com/stijnsanders/tools/blob/master/MetaKeys/Main.pas#L793 ) – Stijn Sanders Nov 16 '17 at 10:55
  • @Stijn, can you please make your comment an answer as it solved my problem. Btw. do you have an explanation for this weird behaviour? – iamjoosy Nov 16 '17 at 11:14
  • I was able to reproduce on my system and adding KEYEVENTF_EXTENDEDKEY to the flag solved it. – FredS Nov 16 '17 at 17:30

1 Answers1

2

Turns out that SendInput still requires KEYEVENTF_EXTENDEDKEY for Extended Keys. Yet MapVirtualKey cannot be relied on to return the Extended Key Value (224) starting with Vista.

Modify your code to use ExtendedKeyFlag:

begin
  KeyInputCount := 0;
  KeybdInput(VK_SHIFT, ExtendedKeyFlag(VK_SHIFT));                   // Shift
  KeybdInput(VK_RIGHT, ExtendedKeyFlag(VK_RIGHT));                   // Right
  KeybdInput(VK_RIGHT, ExtendedKeyFlag(VK_RIGHT) or KEYEVENTF_KEYUP);// Right
  KeybdInput(VK_SHIFT, ExtendedKeyFlag(VK_SHIFT) or KEYEVENTF_KEYUP);// Shift
  SendInput(KeyInputCount, KeyInputs[0], SizeOf(KeyInputs[0]));
end;

ExtendedKeyFlag bypasses MapVirtualKey and maps the Extended Keys:

/// <summary>
///   Returns KEYEVENTF_EXTENDEDKEY if part of ExtendedKeys
/// </summary>
/// <remarks>
///   No API exists to properly return the Extended Keys in Vista+
///   <note type="warning">
///     Do NOT trust in MapVirtualKeyEx(MAPVK_VK_TO_VSC_EX) (&gt;= Vista) because it does not return the extended flag for some keys although they are extended keys
///   </note>
/// </remarks>
/// <seealso href="http://letcoderock.blogspot.ca/2011/10/sendinput-with-shift-key-not-work.html">
///   SendInput with SHIFT key does not work!?
/// </seealso>
/// <seealso href="https://stackoverflow.com/questions/21197257/keybd-event-keyeventf-extendedkey-explanation-required#21202784">
///   keybd_event KEYEVENTF_EXTENDEDKEY explanation required
/// </seealso>
function ExtendedKeyFlag(const VKey : Word): Cardinal;
begin
  If (VKey in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_HOME, VK_END, VK_PRIOR, VK_NEXT, VK_INSERT, VK_DELETE]) then Result := KEYEVENTF_EXTENDEDKEY
  else Result := 0;
end;
FredS
  • 680
  • 1
  • 5
  • 6
  • Although your answer ultimately is the correct one, please edit your answer and format it in a way that other poeple can understand it. As it is, it is difficult to understand. – iamjoosy Nov 17 '17 at 08:52