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



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

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

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

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

我目前的问题是,如果我通过在挂钩过程中返回非零值来阻止键盘输入,则覆盖层的 UI 引擎将停止感知Ctrl键的状态,从而导致无法复制/粘贴到覆盖层的文本框中。有趣的是,在Alt-Tab'ing之前,一切正常,但在那之后,按Ctrl键钩抓取从VK_CONTROL转到VK_LCONTROL.更有趣的是,GetKeyState(VK_CONTROL)GetAsyncKeyState(VK_CONTROL)和 UI 端的GetAsyncKeyState(VK_LCONTROL)都没有将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 );
}

UI 引擎本身通过GetKeyState()检查CtrlShift和 Alt 状态,因为通过WM_SYSKEYDOWN消息跟踪这些状态会导致Alt-Tab卡住Alt键,因为窗口永远不会收到WM_SYSKEYUP消息。必要时,在几个不同的WM_...消息上调用检查Ctrl/Shift/Alt键状态的函数。但是,一旦VK_LCONTROL消息开始被键盘挂钩而不是VK_CONTROL消息拦截,此函数始终将所有键报告为未按下。

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

最新更新