【问题标题】:Capture dead keys on Keyboard hook捕获键盘挂钩上的死键
【发布时间】:2021-06-09 15:26:45
【问题描述】:

参考this question,更具体地说是this answer,似乎只有在MSB 操作之后,才会在键盘挂钩上捕获死键。答案的作者留下了固定的代码,但不知道Delphi等价物是什么。

我的实际代码:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows,
  Messages,
  SysUtils;

var
  hhk: HHOOK;
  Msg: TMsg;

{==================================================================================}

function ReverseBits(b: Byte): Byte;
var
  i: Integer;
begin
  Result := 0;
  for i := 1 to 8 do
  begin
    Result := (Result shl 1) or (b and 1);
    b := b shr 1;
  end;
end;

function GetCharFromVirtualKey(Key: Word): string;
var
  keyboardState: TKeyboardState;
  keyboardLayout: HKL;
  asciiResult: Integer;
begin
  GetKeyboardState(keyboardState);
  keyboardLayout := GetKeyboardLayout(0);
  SetLength(Result, 2);
  asciiResult := ToAsciiEx(Key, ReverseBits(MapVirtualKey(Key, MAPVK_VK_TO_CHAR)), keyboardState, @Result[1], 0, keyboardLayout);
  case asciiResult of
    0:
      Result := '';
    1:
      SetLength(Result, 1);
    2:
      ;
  else
    Result := '';
  end;
end;

{==================================================================================}

function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
  PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;

  TKBDLLHOOKSTRUCT = record
    vkCode: cardinal;
    scanCode: cardinal;
    flags: cardinal;
    time: cardinal;
    dwExtraInfo: Cardinal;
  end;

  PKeyboardLowLevelHookStruct = ^TKeyboardLowLevelHookStruct;

  TKeyboardLowLevelHookStruct = TKBDLLHOOKSTRUCT;
var
  LKBDLLHOOKSTRUCT: PKeyboardLowLevelHookStruct;
begin
  case nCode of
    HC_ACTION:
      begin
        if wParam = WM_KEYDOWN then
        begin
          LKBDLLHOOKSTRUCT := PKeyboardLowLevelHookStruct(lParam);
          Writeln(GetCharFromVirtualKey(LKBDLLHOOKSTRUCT^.vkCode));
        end;
      end;
  end;
  Result := CallNextHookEx(hhk, nCode, wParam, lParam);
end;

procedure InitHook;
begin
  hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
  if hhk = 0 then
    RaiseLastOSError;
end;

procedure KillHook;
begin
  if (hhk <> 0) then
    UnhookWindowsHookEx(hhk);
end;

begin
  try
    InitHook;
    while GetMessage(Msg, 0, 0, 0) do
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

【问题讨论】:

  • 如果它是正确的,您希望程序的哪个输出?
  • @fpiette,例如:Shift + 2 = @, Shift + "a" = A, Caps = 所有 a..z 大写 ...
  • @Coringa 为什么要反转MapVirtualKey() 的返回值的位?当MapVirtualKey() 返回UINT 时,为什么作为Byte?您链接到的答案根本没有反转任何位。它只是将它们向下移动,因此它可以检查高位是否为 1。该答案的 Delphi 等效项将是 if ((MapVirtualKey(Key, MAPVK_VK_TO_CHAR) shr 31) and 1) = 0 then ... 或更简单的 if (MapVirtualKey(Key, MAPVK_VK_TO_CHAR) and $80000000) = 0 then ... 甚至 Int32(MapVirtualKey(Key, MAPVK_VK_TO_CHAR)) &gt;= 0 then ...
  • @Coringa 我认为您误解了死键实际上是什么。死键是特殊键或键组合,它们不会立即处理,而是与下一个键输入事件相结合,前提是死键可以与下一个键组合。如果可以组合,则返回一个combined character。如果不是两个单独的字符(一个代表死键,一个代表第二个输入),则一个接一个地返回。所以基本上你需要在两个连续的键盘钩子事件中处理死键。
  • @Coringa 但是根据您上面的评论,您似乎对处理 Shift State 更感兴趣,它可以告诉您是否按下了 Shift、Ctrl 或 Alt 等键或者Caps LockNum LockScroll Lock 已启用或禁用。这些不被称为死键,而是修饰键。

标签: delphi keyboard hook delphi-10.3-rio


【解决方案1】:

在回答@fpiette 的最后评论时,这是我的解决方案:

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Windows,
  Messages,
  SysUtils;

var
  hhk: HHOOK;
  Msg: TMsg;

function IsTextCharForKeyTip(AKey: Word): Boolean;
var
  keyboardLayout: HKL;
  ActiveWindow: HWND;
  ActiveThreadID: DWord;
  ARes: UINT;
begin
  ActiveWindow := GetForegroundWindow;
  ActiveThreadID := GetWindowThreadProcessId(ActiveWindow, nil);
  keyboardLayout := GetKeyboardLayout(ActiveThreadID);
  ARes := MapVirtualKeyEx(AKey, MAPVK_VK_TO_CHAR, keyboardLayout);
  Result := ((ARes and $FFFF0000) = 0) and (Char(ARes) <> ' ') and (CharInSet(Char(ARes), [#32..#255]));
end;

function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
  PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;

  TKBDLLHOOKSTRUCT = record
    vkCode: cardinal;
    scanCode: cardinal;
    flags: cardinal;
    time: cardinal;
    dwExtraInfo: cardinal;
  end;

  PKeyboardLowLevelHookStruct = ^TKeyboardLowLevelHookStruct;

  TKeyboardLowLevelHookStruct = TKBDLLHOOKSTRUCT;
var
  LKBDLLHOOKSTRUCT: PKeyboardLowLevelHookStruct;
  keyboardState: TKeyboardState;
  keyboardLayout: HKL;
  ScanCode: Integer;
  ActiveWindow: HWND;
  ActiveThreadID: DWord;
  CapsKey, ShiftKey: Boolean;
  KeyString: string;
begin
  Result := CallNextHookEx(hhk, nCode, wParam, lParam);
  case nCode of
    HC_ACTION:
      begin
        CapsKey := False;
        ShiftKey := False;
        if Odd(GetKeyState(VK_CAPITAL)) then
          if GetKeyState(VK_SHIFT) < 0 then
            CapsKey := False
          else
            CapsKey := True
        else if GetKeyState(VK_SHIFT) < 0 then
          ShiftKey := True
        else
          ShiftKey := False;

        if GetKeyState(VK_BACK) < 0 then
          Write('[Backspace]') { #08 }; // #08, overwrites what was deleted on the console :D.

        if GetKeyState(VK_SPACE) < 0 then
          Write(#32);

        if GetKeyState(VK_TAB) < 0 then
          Write(#09);

        case wParam of
          WM_KEYDOWN, WM_SYSKEYDOWN:
            begin
              LKBDLLHOOKSTRUCT := PKeyboardLowLevelHookStruct(lParam);

              if (not IsTextCharForKeyTip(LKBDLLHOOKSTRUCT^.vkCode)) then
                Exit;

              SetLength(KeyString, 2);
              ActiveWindow := GetForegroundWindow;
              ActiveThreadID := GetWindowThreadProcessId(ActiveWindow, nil);
              keyboardLayout := GetKeyboardLayout(ActiveThreadID);
              GetKeyboardState(keyboardState);
              ScanCode := MapVirtualKeyEx(LKBDLLHOOKSTRUCT^.vkCode, MAPVK_VK_TO_CHAR, keyboardLayout);
              ToAsciiEx(LKBDLLHOOKSTRUCT^.vkCode, ScanCode, keyboardState, @KeyString[1], 0, keyboardLayout);

              if CapsKey or ShiftKey then
                Write(UpperCase(KeyString[1]))
              else
                Write(LowerCase(KeyString[1]));
            end;
        end;
      end;
  end;
end;

procedure InitHook;
begin
  hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
  if hhk = 0 then
    RaiseLastOSError;
end;

procedure KillHook;
begin
  if (hhk <> 0) then
    UnhookWindowsHookEx(hhk);
end;

begin
  try
    InitHook;
    while True do
    begin
      if PeekMessage(Msg, 0, 0, 0, 0) then
      begin
        GetMessage(Msg, 0, 0, 0);
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

【讨论】:

  • 我注意到这仍然不能满足你所​​说的你想要的 - 它不会给你@ for 按住 2 的 shift 键(我的键盘也不会,因为我不在美国!)如果您将其突出显示为您最初想要的部分解决方案,它可能会对其他用户有所帮助。
  • 根据您的要求,这似乎不像我预期的那样工作。我正在使用French-Belgian keyboard。顶行的第三个键有符号“é”(仅键)、“2”(Shift)和“@”(AltGr)。单独键和键+移位有效。但是 Key + AltGr 不起作用。您的代码应显示“@”时不显示任何内容。这与关联 AltGr 代码的所有键相同。
  • @fpiette,我不知道修复它。有什么建议吗?
  • 或许:var ScanCode: Integer; ... ScanCode := MapVirtualKeyEx(LKBDLLHOOKSTRUCT^.vkCode, MAPVK_VK_TO_CHAR, KeyboardLayout); ToAsciiEx(LKBDLLHOOKSTRUCT^.vkCode, ScanCode, ... ?
  • @Coringa 你应该编辑你的答案说“......这是我的部分解决方案。仍然让 Alt-Gr 工作。”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 2015-09-22
相关资源
最近更新 更多