我的手臂骨折了,正在为 Windows 开发一款单手键盘应用程序,该应用程序会在按住空格键时镜像键盘布局,从而允许单手打字。镜像功能可以使用计时器来操作,但这并不理想 - 如果在按住空格键后快速按下镜像键,镜像功能会变得缓慢并且会留下空格。
有更好的方法来处理这个用例吗?我尝试使用 lParam 的 KBDLLHOOKSTRUCT 结构 中的标志,但我找不到区分单次按键和重复按键的方法。我还尝试过忽略空格键按下的输入,并在只需按一下时发送输入,但我遇到了它不起作用的问题,可能是由于无限循环。
任何正确方向的帮助或指示将不胜感激。
#include <windows.h>
#include <iostream>
#include <chrono>
#include <map>
using Clock = std::chrono::high_resolution_clock;
std::chrono::time_point<Clock> spacePressedTime;
bool spaceHeld = false;
std::map<DWORD, DWORD> keyMappings;
bool SendMirroredKeyPress(UINT vkCode);
void InitializeKeyMappings();
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
auto kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
if (kbdStruct.vkCode == VK_SPACE) {
switch (wParam) {
case WM_KEYDOWN:
if (!spaceHeld) { // Check if this is the first event of space bar press
spacePressedTime = Clock::now();
spaceHeld = true; // Prevent re-entry for this press
}
return 1; // Block this event to prevent default space input
case WM_KEYUP:
if (spaceHeld) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - spacePressedTime).count();
if (elapsed < 200) { // Threshold for distinguishing tap from hold
// It was a tap, simulate a space key press
INPUT input[2] = {};
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = VK_SPACE;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = VK_SPACE;
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(2, input, sizeof(INPUT));
std::cout << "Space pressed normally.\n";
}
else {
// It was a hold, so enter or stay in mirror mode without inserting a space
std::cout << "Leaving mirror mode.\n";
}
spaceHeld = false; // Reset for the next press
return 1; // Block the event to prevent default handling
}
break;
}
}
else {
// For other keys, check if space is held (mirror mode active) and handle accordingly
if (spaceHeld && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) {
if (SendMirroredKeyPress(kbdStruct.vkCode)) {
return 1; // Block the original key press
}
}
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
bool SendMirroredKeyPress(UINT vkCode) {
auto it = keyMappings.find(vkCode);
if (it != keyMappings.end()) {
INPUT input[1] = {};
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = it->second; // Mirrored virtual key code
// Send the mirrored key press
SendInput(1, input, sizeof(INPUT));
// Indicate that the original key press should be blocked
return true;
}
// No mirroring needed for this key
return false;
}
static void InitializeKeyMappings() {
keyMappings[0x4E] = 0x42; // N to B as example
}
int main() {
InitializeKeyMappings();
HHOOK hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, 0, 0);
std::cout << "Hook set. Press and hold space to enter mirror mode. Tap space for normal input. N should switch to b in mirror mode.\n";
MSG msg;
while (!GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnhookWindowsHookEx(hhkLowLevelKybd);
return 0;
}
正如我在评论中所说,区分单次按键和重复按键的一种方法是维护一个变量,指示最后按下的键是否是空格键。当按下其他键时该变量将被重置,并且您可以在按下空格键时检查该变量是否重复。例如,请参阅
我的答案的变量
static BOOL H = FALSE;
。