Windows下SendInput为何不遵循Key Up/Down按键状态标志?
Windows下SendInput模拟键盘按键无法保持按下状态的解决方法
问题描述
在Windows系统中使用SendInput模拟键盘按键按下与抬起时,行为不符合预期:按下'A'键触发逻辑时,本应模拟按下'B'键并保持3秒后抬起,但实际按下后立即抬起。鼠标按键模拟正常,仅键盘输入存在该问题。
背景
开发跨平台键鼠共享程序,实现单套键鼠控制多台设备,自研以学习新技术。Linux端功能正常,仅Windows端键盘模拟异常。
原代码
#include <iostream> #include <Windows.h> void SimulateKeyboard(unsigned int keycode, bool press){ #ifdef _WIN32 INPUT input = {0}; input.type = INPUT_KEYBOARD; input.ki.wVk = keycode; input.ki.dwFlags = press ? 0 : KEYEVENTF_KEYUP; // Key down SendInput(1, &input, sizeof(INPUT)); #elif __linux__ //It works right in linux XTestFakeKeyEvent(xConn.display, osKeyCode, press ? True : False, 0); XFlush(xConn.display); #endif } void SimulateMouse(int button, bool press){ // 0 = left, 1 = right, 2 = middle #ifdef _WIN32 DWORD buttonFlag = press ? (button == 0) ? MOUSEEVENTF_LEFTDOWN : (button == 1) ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_MIDDLEDOWN : (button == 0) ? MOUSEEVENTF_LEFTUP : (button == 1) ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_MIDDLEUP; INPUT input = {0}; input.type = INPUT_MOUSE; input.mi.dwFlags = buttonFlag; SendInput(1, &input, sizeof(INPUT)); #elif __linux__ //1 left, 2 mid, 3 right for xdotool //Hacky and probably unsafe solution, will fix later, but works for now if(button==0) system(press?"xdotool mousedown 1":"xdotool mouseup 1"); if(button==1) system(press?"xdotool mousedown 3":"xdotool mouseup 3"); if(button==2) system(press?"xdotool mousedown 2":"xdotool mouseup 2"); #endif } void main(){ while(1){ if(GetAsyncKeyState('A')&0x8000){ //This works as expected using SendInput; presses LMB and keeps it pressed for 3000 ms then releases LMB //SimulateMouse(0, true); //Sleep(3000); //SimulateMouse(0, false); //This does NOT work as expected using SendInput; this presses B and releases it imediatly producing a key press. It should be holding down for 3 seconds then releasing SimulateKeyboard('B', true); Sleep(3000); SimulateKeyboard('B', false); } } return; }
问题原因
- 重复触发逻辑:按住'A'键时,while循环会持续进入if分支,导致多次执行"按下'B'→睡眠3秒→抬起'B'"的流程,看起来像是'B'被立即抬起,实际是多次快速触发按键事件。
- 键盘事件不完整:原代码仅设置了虚拟键码
wVk,未补充扫描码wScan,部分系统或应用可能无法正确识别单纯的虚拟键码事件。
解决方案
1. 添加触发标志位,避免重复执行
在main函数中添加标志位,确保每次'A'键按下仅触发一次模拟逻辑,直到'A'键抬起后才允许再次触发。
2. 完善键盘事件参数
补充扫描码wScan的设置,通过MapVirtualKey将虚拟键码转换为扫描码,提升事件的兼容性和可靠性。
修改后的代码
#include <iostream> #include <Windows.h> void SimulateKeyboard(unsigned int keycode, bool press){ #ifdef _WIN32 INPUT input = {0}; input.type = INPUT_KEYBOARD; input.ki.wVk = keycode; // 将虚拟键码转换为扫描码 input.ki.wScan = MapVirtualKey(keycode, MAPVK_VK_TO_VSC); input.ki.dwFlags = press ? 0 : KEYEVENTF_KEYUP; // 处理扩展键(如Alt、Ctrl等,非必需但提升通用性) if ((GetKeyState(keycode) & 0x10000000) != 0) { input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } SendInput(1, &input, sizeof(INPUT)); #elif __linux__ XTestFakeKeyEvent(xConn.display, osKeyCode, press ? True : False, 0); XFlush(xConn.display); #endif } void SimulateMouse(int button, bool press){ // 0 = left, 1 = right, 2 = middle #ifdef _WIN32 DWORD buttonFlag = press ? (button == 0) ? MOUSEEVENTF_LEFTDOWN : (button == 1) ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_MIDDLEDOWN : (button == 0) ? MOUSEEVENTF_LEFTUP : (button == 1) ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_MIDDLEUP; INPUT input = {0}; input.type = INPUT_MOUSE; input.mi.dwFlags = buttonFlag; SendInput(1, &input, sizeof(INPUT)); #elif __linux__ if(button==0) system(press?"xdotool mousedown 1":"xdotool mouseup 1"); if(button==1) system(press?"xdotool mousedown 3":"xdotool mouseup 3"); if(button==2) system(press?"xdotool mousedown 2":"xdotool mouseup 2"); #endif } int main(){ bool isTriggered = false; while(1){ bool aPressed = (GetAsyncKeyState('A') & 0x8000) != 0; if(aPressed && !isTriggered){ isTriggered = true; SimulateKeyboard('B', true); Sleep(3000); SimulateKeyboard('B', false); } else if(!aPressed && isTriggered){ // 重置标志位,允许下次触发 isTriggered = false; } // 减少CPU占用 Sleep(10); } return 0; }
额外说明
- 将
void main()改为标准的int main(),符合C++语法规范。 - 添加
Sleep(10)减少循环对CPU的占用。 - 扫描码的补充能让更多应用正确识别模拟的键盘事件,避免因仅使用虚拟键码导致的兼容性问题。
内容的提问来源于stack exchange,提问作者Zachwuzhere
相关产品推荐
相关产品推荐

