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,但始终输出单引号。
问题根源
- 按键未正确释放:原
press_key函数默认只执行按键按下操作,未释放按键,导致后续按键状态混乱,组合键(如Shift+VK_OEM_2)无法正确触发。 - 虚拟键码与键盘布局不匹配:不同键盘布局下,虚拟键码对应的字符不同,且
keybd_event需要严格遵循“按下-释放”的顺序。 - 废弃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.")
关键优化点
- 改用SendInput API:替代已废弃的
keybd_event,提升输入模拟的稳定性和兼容性。 - 完整按键流程:每个按键都执行“按下+释放”操作,避免按键持续按住导致的输入混乱。
- 自动键码转换:使用
VkKeyScanW函数自动将字符转换为对应虚拟键码,减少手动指定键码的布局适配问题。 - 添加延迟:短延迟确保系统有足够时间识别按键操作,模拟真实输入节奏。
内容的提问来源于stack exchange,提问作者jxnas_lol
相关产品推荐
相关产品推荐

