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

PyChrome模拟keyDown/keyUp/keyPress键盘事件异常求助

解决PyChrome中CDP键盘事件模拟异常问题

问题现象

  • down方法无法按住指定按键
  • up方法无法释放已按住的按键
  • press方法无法正确模拟组合键(如Control+T无法打开新标签页)

问题根源

  1. 键映射缺失关键修饰键:当前key_mapping中没有Control、Meta等修饰键的配置,导致组合键无法被识别。
  2. 事件参数不完整:CDP的Input.dispatchKeyEvent需要完整参数(如modifiers、autoRepeat、isComposing等)才能触发系统级键盘行为,仅传递code、keyCode不足以让浏览器识别按键按住状态。
  3. 组合键逻辑未同步修饰键状态:模拟组合键时未跟踪修饰键的激活状态,浏览器无法识别为组合操作。

修复方案

1. 补全键映射配置

添加修饰键和常用功能键的完整映射:

key_mapping = {
    "Backspace": {"code": "Backspace", "key": "Backspace", "keyCode": 8, "windowsVirtualKeyCode": 8},
    "Tab": {"code": "Tab", "key": "Tab", "keyCode": 9, "windowsVirtualKeyCode": 9, "text": "\t"},
    "Enter": {"code": "Enter", "key": "Enter", "keyCode": 13, "windowsVirtualKeyCode": 13, "text": "\r"},
    "Escape": {"code": "Escape", "key": "Escape", "keyCode": 27, "windowsVirtualKeyCode": 27},
    "Space": {"code": "Space", "key": " ", "keyCode": 32, "windowsVirtualKeyCode": 32, "text": " "},
    "Delete": {"code": "Delete", "key": "Delete", "keyCode": 46, "windowsVirtualKeyCode": 46},
    "Control": {"code": "ControlLeft", "key": "Control", "keyCode": 17, "windowsVirtualKeyCode": 17, "modifiers": 2},
    "Meta": {"code": "MetaLeft", "key": "Meta", "keyCode": 91, "windowsVirtualKeyCode": 91, "modifiers": 1024},
    "Shift": {"code": "ShiftLeft", "key": "Shift", "keyCode": 16, "windowsVirtualKeyCode": 16, "modifiers": 1},
    "Alt": {"code": "AltLeft", "key": "Alt", "keyCode": 18, "windowsVirtualKeyCode": 18, "modifiers": 4},
    "A": {"code": "KeyA", "key": "a", "keyCode": 65, "windowsVirtualKeyCode": 65, "text": "a"},
    "B": {"code": "KeyB", "key": "b", "keyCode": 66, "windowsVirtualKeyCode": 66, "text": "b"},
    "C": {"code": "KeyC", "key": "c", "keyCode": 67, "windowsVirtualKeyCode": 67, "text": "c"},
    "D": {"code": "KeyD", "key": "d", "keyCode": 68, "windowsVirtualKeyCode": 68, "text": "d"},
    "E": {"code": "KeyE", "key": "e", "keyCode": 69, "windowsVirtualKeyCode": 69, "text": "e"},
    "F": {"code": "KeyF", "key": "f", "keyCode": 70, "windowsVirtualKeyCode": 70, "text": "f"},
    "G": {"code": "KeyG", "key": "g", "keyCode": 71, "windowsVirtualKeyCode": 71, "text": "g"},
    "H": {"code": "KeyH", "key": "h", "keyCode": 72, "windowsVirtualKeyCode": 72, "text": "h"},
    "I": {"code": "KeyI", "key": "i", "keyCode": 73, "windowsVirtualKeyCode": 73, "text": "i"},
    "J": {"code": "KeyJ", "key": "j", "keyCode": 74, "windowsVirtualKeyCode": 74, "text": "j"},
    "K": {"code": "KeyK", "key": "k", "keyCode": 75, "windowsVirtualKeyCode": 75, "text": "k"},
    "L": {"code": "KeyL", "key": "l", "keyCode": 76, "windowsVirtualKeyCode": 76, "text": "l"},
    "M": {"code": "KeyM", "key": "m", "keyCode": 77, "windowsVirtualKeyCode": 77, "text": "m"},
    "N": {"code": "KeyN", "key": "n", "keyCode": 78, "windowsVirtualKeyCode": 78, "text": "n"},
    "O": {"code": "KeyO", "key": "o", "keyCode": 79, "windowsVirtualKeyCode": 79, "text": "o"},
    "P": {"code": "KeyP", "key": "p", "keyCode": 80, "windowsVirtualKeyCode": 80, "text": "p"},
    "Q": {"code": "KeyQ", "key": "q", "keyCode": 81, "windowsVirtualKeyCode": 81, "text": "q"},
    "R": {"code": "KeyR", "key": "r", "keyCode": 82, "windowsVirtualKeyCode": 82, "text": "r"},
    "S": {"code": "KeyS", "key": "s", "keyCode": 83, "windowsVirtualKeyCode": 83, "text": "s"},
    "T": {"code": "KeyT", "key": "t", "keyCode": 84, "windowsVirtualKeyCode": 84, "text": "t"}
}

2. 修改Keyboard类实现

完善事件参数,添加修饰键状态跟踪:

import sys

class Keyboard:
    """Handles keyboard interactions on the page"""

    def __init__(self, tab: Tab):
        self._tab = tab
        self._pressed_modifiers = 0  # 跟踪当前按住的修饰键位掩码

    def type(self, text: str):
        """Simulates typing text character by character."""
        for char in text:
            self._pressed_modifiers = 0  # 重置修饰键状态,避免干扰输入
            self._dispatch_key_event("keyDown", text=char)
            self._dispatch_key_event("keyUp", text=char)

    def down(self, key: str):
        """Holds down a specified key without releasing it."""
        key_data = self._get_key_data(key)
        if not key_data:
            return
        
        # 更新修饰键掩码
        if "modifiers" in key_data:
            self._pressed_modifiers |= key_data["modifiers"]
        
        # 补充CDP要求的默认参数
        event_params = {
            "autoRepeat": False,
            "isKeypad": False,
            "isComposing": False,
            "modifiers": self._pressed_modifiers,
            **key_data
        }
        self._dispatch_key_event("keyDown", **event_params)

    def up(self, key: str):
        """Releases a held key."""
        key_data = self._get_key_data(key)
        if not key_data:
            return
        
        # 更新修饰键掩码
        if "modifiers" in key_data:
            self._pressed_modifiers &= ~key_data["modifiers"]
        
        # 补充CDP要求的默认参数
        event_params = {
            "autoRepeat": False,
            "isKeypad": False,
            "isComposing": False,
            "modifiers": self._pressed_modifiers,
            **key_data
        }
        self._dispatch_key_event("keyUp", **event_params)

    def press(self, combination: str):
        keys = combination.split("+")
        if not keys:
            return

        # 按下所有修饰键
        for key in keys[:-1]:
            self.down(key)

        # 按下并释放主按键
        main_key = keys[-1]
        key_data = self._get_key_data(main_key)
        if key_data:
            event_params = {
                "autoRepeat": False,
                "isKeypad": False,
                "isComposing": False,
                "modifiers": self._pressed_modifiers,
                **key_data
            }
            self._dispatch_key_event("keyDown", **event_params)
            self._dispatch_key_event("keyUp", **event_params)

        # 释放所有修饰键
        for key in reversed(keys[:-1]):
            self.up(key)

    def _dispatch_key_event(self, event_type: str, **kwargs):
        """Dispatches a key event to the tab."""
        self._tab.call_method(
            "Input.dispatchKeyEvent", type=event_type, _timeout=5, **kwargs
        )

    def _get_key_data(self, key: str):
        """Helper method to retrieve the appropriate key data from the key mapping."""
        normalized_key = self._normalize_key(key)
        key_data = key_mapping.get(normalized_key)
        if not key_data:
            print(f"Warning: Key '{key}' not found in key_mapping.")
        return key_data

    @staticmethod
    def _normalize_key(key: str) -> str:
        """Normalizes key names for compatibility with the key mapping."""
        key = key.capitalize()
        if key == "Ctrl":
            return "Control"
        elif key == "Cmd":
            return "Meta"  # Mac下Cmd对应Meta
        elif key == "Controlormeta":
            # 自动根据系统选择修饰键
            return "Meta" if sys.platform == "darwin" else "Control"
        return key

3. 关键修复点说明

  • 补全修饰键映射:添加Control、Meta等修饰键的modifiers位掩码,这是CDP识别修饰键的核心参数。
  • 跟踪修饰键状态:用_pressed_modifiers变量记录当前激活的修饰键,确保后续事件的参数同步。
  • 完善事件参数:补充CDP要求的默认参数,让浏览器正确识别事件类型。
  • 组合键逻辑优化:复用down和up方法处理修饰键,保证状态一致性。

测试验证

调用keyboard.press("Control+T")可正常打开新标签页;调用down("Control")后执行type("t")也能触发组合键行为;up("Control")可正确释放按键。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 06:22:33