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

Python Windows按键模拟问题:输入问号却输出单引号

解决Windows下ctypes模拟按键时问号输出为单引号的问题

我正在开发一个用ctypes在Windows环境模拟按键的Python脚本,目标是输出指定字符串并替换特定字符为对应按键操作。但遇到问题:脚本应该输出问号(?)时,却打出了单引号(')。尝试调整虚拟键码和ASCII值后问题仍未解决。

原代码

import ctypes
import time

# Constants for virtual key codes
VK_SPACE = 0x20
VK_BACK = 0x08
VK_COMMA = 0xBC
VK_PERIOD = 0xBE
VK_SINGLE_QUOTE = 0xDE  # Virtual key code for single quote (')
VK_SHIFT = 0x10
VK_OEM_1 = 0xBA  # Virtual key code for the ';:' key
VK_OEM_2 = 0xBF  # Virtual key code for the '/?' key

# Function to simulate key press using Windows API
def press_key(virtual_key, key_down=True, key_up=False):
    if key_down:
        ctypes.windll.user32.keybd_event(virtual_key, 0, 0, 0)
    if key_up:
        ctypes.windll.user32.keybd_event(virtual_key, 0, 2, 0)

# Function to simulate pressing the shift key
def press_shift():
    press_key(VK_SHIFT)

# Function to simulate releasing the shift key
def release_shift():
    press_key(VK_SHIFT, key_down=False, key_up=True)

# Function to simulate typing a string
def simulate_typing(input_string):
    try:
        
        time.sleep(5)

        # Simulate typing with key presses and spaces
        for char in input_string:
            if char == ' ':
                press_key(VK_SPACE)
            elif char == ',':
                press_key(VK_COMMA)
            elif char == '.':
                press_key(VK_PERIOD)
            elif char == '\'':
                press_key(VK_SINGLE_QUOTE)  # Virtual key code for single quote
            elif char == '!':
                press_shift()
                press_key(0x31)  # Virtual key code for '1' to simulate '!'
                release_shift()
            elif char == '?':
                press_shift()
                press_key(0xBF)  # Virtual key code for the question mark on German keyboards
                release_shift()
            else:
                # Check if the character is uppercase and press the shift key accordingly
                if char.isupper():
                    press_shift()
                if char == '/':
                    press_key(VK_OEM_2)  # Virtual key code for '/?' to simulate '/'
                else:
                    press_key(ord(char.upper()))
                if char.isupper():
                    release_shift()

        print(f"Typed: {input_string}")

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    try:
        text_to_type = "Hello, World! How are you doing?"

        # Call the function to simulate typing
        simulate_typing(text_to_type)

    except KeyboardInterrupt:
        print("\nScript execution interrupted by user.")

已尝试方案

修改虚拟键码和ASCII值,比如用单引号的VK_OEM_7、ord('\''),以及问号的VK_OEM_2,但始终输出单引号。


问题根源

  1. 按键未正确释放:原press_key函数默认只执行按键按下操作,未释放按键,导致后续按键状态混乱,组合键(如Shift+VK_OEM_2)无法正确触发。
  2. 虚拟键码与键盘布局不匹配:不同键盘布局下,虚拟键码对应的字符不同,且keybd_event需要严格遵循“按下-释放”的顺序。
  3. 废弃API的局限性:keybd_event是已废弃的Windows API,兼容性和稳定性不如推荐的SendInput。

修复方案

修改后的代码

import ctypes
import time
from ctypes import wintypes

# Windows API constants
VK_SPACE = 0x20
VK_BACK = 0x08
VK_COMMA = 0xBC
VK_PERIOD = 0xBE
VK_OEM_7 = 0xDE  # 美式键盘单引号/双引号键
VK_SHIFT = 0x10
VK_OEM_2 = 0xBF  # 美式键盘/?键

# 定义SendInput所需的结构体
user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_UNICODE = 0x0004
KEYEVENTF_SCANCODE = 0x0008

class MOUSEINPUT(ctypes.Structure):
    _fields_ = (("dx", wintypes.LONG),
                ("dy", wintypes.LONG),
                ("mouseData", wintypes.DWORD),
                ("dwFlags", wintypes.DWORD),
                ("time", wintypes.DWORD),
                ("dwExtraInfo", wintypes.LPARAM))

class KEYBDINPUT(ctypes.Structure):
    _fields_ = (("wVk", wintypes.WORD),
                ("wScan", wintypes.WORD),
                ("dwFlags", wintypes.DWORD),
                ("time", wintypes.DWORD),
                ("dwExtraInfo", wintypes.LPARAM))

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = (("uMsg", wintypes.DWORD),
                ("wParamL", wintypes.WORD),
                ("wParamH", wintypes.WORD))

class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = (("mi", MOUSEINPUT),
                    ("ki", KEYBDINPUT),
                    ("hi", HARDWAREINPUT))
    _anonymous_ = ("_input",)
    _fields_ = (("type", wintypes.DWORD),
                ("_input", _INPUT))

def send_key(vk_code, press=True):
    """使用SendInput模拟按键按下或释放"""
    input_obj = INPUT()
    input_obj.type = INPUT_KEYBOARD
    input_obj.ki.wVk = vk_code
    if not press:
        input_obj.ki.dwFlags = KEYEVENTF_KEYUP
    user32.SendInput(1, ctypes.byref(input_obj), ctypes.sizeof(INPUT))

def press_and_release_key(vk_code):
    """模拟完整的按键按下+释放"""
    send_key(vk_code, press=True)
    time.sleep(0.01)  # 短延迟确保系统识别按键
    send_key(vk_code, press=False)

def simulate_typing(input_string):
    try:
        time.sleep(5)  # 预留切换窗口时间

        for char in input_string:
            if char == ' ':
                press_and_release_key(VK_SPACE)
            elif char == ',':
                press_and_release_key(VK_COMMA)
            elif char == '.':
                press_and_release_key(VK_PERIOD)
            elif char == '\'':
                press_and_release_key(VK_OEM_7)
            elif char == '!':
                send_key(VK_SHIFT, press=True)
                press_and_release_key(0x31)
                send_key(VK_SHIFT, press=False)
            elif char == '?':
                send_key(VK_SHIFT, press=True)
                press_and_release_key(VK_OEM_2)
                send_key(VK_SHIFT, press=False)
            elif char == '/':
                press_and_release_key(VK_OEM_2)
            else:
                if char.isupper():
                    send_key(VK_SHIFT, press=True)
                # 自动转换字符为对应虚拟键码
                vk_code = ctypes.windll.user32.VkKeyScanW(ord(char)) & 0xFF
                press_and_release_key(vk_code)
                if char.isupper():
                    send_key(VK_SHIFT, press=False)
            time.sleep(0.05)  # 模拟正常输入间隔

        print(f"Typed: {input_string}")

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    try:
        text_to_type = "Hello, World! How are you doing?"
        simulate_typing(text_to_type)
    except KeyboardInterrupt:
        print("\nScript execution interrupted by user.")

关键优化点

  1. 改用SendInput API:替代已废弃的keybd_event,提升输入模拟的稳定性和兼容性。
  2. 完整按键流程:每个按键都执行“按下+释放”操作,避免按键持续按住导致的输入混乱。
  3. 自动键码转换:使用VkKeyScanW函数自动将字符转换为对应虚拟键码,减少手动指定键码的布局适配问题。
  4. 添加延迟:短延迟确保系统有足够时间识别按键操作,模拟真实输入节奏。

内容的提问来源于stack exchange,提问作者jxnas_lol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 15:15:14