覆盖应用程序的低级键盘挂钩问题

Boy*_*oyC 5 c++ keyboard winapi keyboard-hook

在这里的第一篇文章,多年来我一直在思考这个问题的正确解决方案。

我有自己的 UI 引擎和自己的键盘处理,并使用它来显示游戏覆盖。游戏覆盖本身对键盘和窗口事件都是透明的,以便对游戏的干扰最小,但为了使覆盖本身具有交互性,我需要诉诸键盘和鼠标挂钩来阻止某些事件到达游戏。对于鼠标输入,这非常简单,而且效果很好。这是我遇到问题的低级键盘挂钩。

在这一点上,我有一些在大多数情况下都可以使用的东西。我设法解决了几个涉及死键和错误输入的问题,但从未设法创建一个可以主动阻止游戏键盘输入的钩子 - 总是会出错。

例如,当用户试图在覆盖层上的文本框中写入一些文本并且不希望游戏处理相同的击键时,主动阻止键盘输入将是最有用的。

我当前的问题是,如果我通过在挂钩过程中返回非零值来阻止键盘输入,则覆盖的 UI 引擎将停止感知Ctrl键的状态,从而导致无法复制/粘贴到覆盖的文本框中。有趣的是,在Alt-Tab'ing之前,一切正常,但在那之后,Ctrl钩子抓住的按键从VK_CONTROL变为VK_LCONTROL。更有趣的是,无论GetKeyState(VK_CONTROL)GetAsyncKeyState(VK_CONTROL)还是GetAsyncKeyState(VK_LCONTROL)在 UI 端都没有将Ctrl键注册为按下状态。

由于多年的实验和变通方法,下面的键盘钩子代码有点乱。我会尽可能评论它。

LRESULT __stdcall KeyboardHook( int code, WPARAM wParam, LPARAM lParam )
{
  // this is an early exit if the game tells me that it actively has focus
  if ( disableHooks || mumbleLink.textBoxHasFocus )
    return CallNextHookEx( 0, code, wParam, lParam );

  // the following two early exits are remnants from earlier experimentation
  if ( code < 0 )
    return CallNextHookEx( 0, code, wParam, lParam );

  if ( wParam != WM_KEYDOWN && wParam != WM_KEYUP && wParam != WM_CHAR && wParam != WM_DEADCHAR && wParam != WM_UNICHAR )
    return CallNextHookEx( 0, code, wParam, lParam );

  // this checks if either the game or the overlay are in focus and otherwise ignores keyboard input
  auto wnd = GetForegroundWindow();
  if ( code != HC_ACTION || !lParam || ( wnd != gw2Window && App && wnd != (HWND)App->GetHandle() ) )
    return CallNextHookEx( 0, code, wParam, lParam );

  // this ignores the overlay itself if it's in focus for some odd reason
  if ( App && wnd == (HWND)App->GetHandle() )
    return CallNextHookEx( 0, code, wParam, lParam );

  KBDLLHOOKSTRUCT *kbdat = (KBDLLHOOKSTRUCT*)lParam;
  UINT mapped = MapVirtualKey( kbdat->vkCode, MAPVK_VK_TO_CHAR );

  // this bool tests if the overlay has a textbox in focus and the keyboard input should be blocked from propagating further
  bool inFocus = App->GetFocusItem() && App->GetFocusItem()->InstanceOf( "textbox" );

  // forcefully inject a WM_CHAR message to the overlay's UI engine - never figured out how to trigger a message that would be translated into a WM_CHAR properly
  if ( !( mapped & ( 1 << 31 ) ) && !inFocus && wParam == WM_KEYDOWN )
    App->InjectMessage( WM_CHAR, mapped, 0 );

  if ( inFocus )
  {
    PostMessage( (HWND)App->GetHandle(), wParam, kbdat->vkCode, 1 | ( kbdat->scanCode << 16 ) + ( kbdat->flags << 24 ) );

    /////////////////////////////////////////////////
    return 1; // this is where the key input should be blocked, but it causes the mentioned issues with the ctrl key (and probably others too)
    /////////////////////////////////////////////////
  }

  return CallNextHookEx( 0, code, wParam, lParam );
}
Run Code Online (Sandbox Code Playgroud)

的UI引擎本身检查CtrlShiftAlt通过状态GetKeyState(),因为通过追踪这些WM_SYSKEYDOWN消息将,例如,结果在一个Alt-Tab具有Alt键卡住因为窗口决不会收到WM_SYSKEYUP消息。检查Ctrl/ Shift/Alt键状态的函数WM_...在必要时在几个不同的消息上调用。然而,一旦VK_LCONTROL消息开始被键盘钩子拦截而不是被拦截VK_CONTROL,这个函数总是将所有键报告为未被按下。

小智 0

你可以尝试不同的方法。如果在此期间您的覆盖层是活动窗口,那么您可以在没有钩子的情况下处理键盘和鼠标事件,如果您想将事件转发到游戏,您只需为游戏窗口合成事件即可。