You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题原因

  1. 重复触发逻辑:按住'A'键时,while循环会持续进入if分支,导致多次执行"按下'B'→睡眠3秒→抬起'B'"的流程,看起来像是'B'被立即抬起,实际是多次快速触发按键事件。
  2. 键盘事件不完整:原代码仅设置了虚拟键码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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 04:27:04