如何模拟键盘事件(向下、向上、按下)?

问题描述 投票:0回答:1

我在 Python 中使用 PyChrome 通过 Chrome DevTools Protocol (CDP) 模拟键盘事件,如 keyDown、keyUp 和 keyPress。 type 方法工作正常,但 down、up 和 press 方法未按预期运行。

问题:

  1. down
    :未按住该键。
  2. up
    :按键未释放。
  3. press
    :组合键无法正确模拟按键。
key_mapping = {
    "Backspace": {"code": "Backspace", "keyCode": 8},
    "Tab": {"code": "Tab", "keyCode": 9, "text": "\t"},
    "Enter": {"code": "Enter", "keyCode": 13, "text": "\r"},
    "Escape": {"code": "Escape", "keyCode": 27},
    "Space": {"code": "Space", "keyCode": 32, "text": " "},
    "Delete": {"code": "Delete", "keyCode": 46},
    "A": {"code": "KeyA", "keyCode": 65, "text": "a"},
    "B": {"code": "KeyB", "keyCode": 66, "text": "b"},
    "C": {"code": "KeyC", "keyCode": 67, "text": "c"},
    "D": {"code": "KeyD", "keyCode": 68, "text": "d"},
    "E": {"code": "KeyE", "keyCode": 69, "text": "e"},
    "F": {"code": "KeyF", "keyCode": 70, "text": "f"},
    "G": {"code": "KeyG", "keyCode": 71, "text": "g"},
    "H": {"code": "KeyH", "keyCode": 72, "text": "h"},
    "I": {"code": "KeyI", "keyCode": 73, "text": "i"},
    "J": {"code": "KeyJ", "keyCode": 74, "text": "j"},
    "K": {"code": "KeyK", "keyCode": 75, "text": "k"},
    "L": {"code": "KeyL", "keyCode": 76, "text": "l"},
    "M": {"code": "KeyM", "keyCode": 77, "text": "m"},
    "N": {"code": "KeyN", "keyCode": 78, "text": "n"},
    "O": {"code": "KeyO", "keyCode": 79, "text": "o"},
    "P": {"code": "KeyP", "keyCode": 80, "text": "p"},
    "Q": {"code": "KeyQ", "keyCode": 81, "text": "q"},
    "R": {"code": "KeyR", "keyCode": 82, "text": "r"},
    "S": {"code": "KeyS", "keyCode": 83, "text": "s"},
}

我创建了一个键盘类,它将公开几种方法,例如(向上、按下、向下、键入) 目前类型正在按我的预期工作。但对于向上、按或向下不起作用。我已尝试解决它们,但它们无法正常工作。

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

    def __init__(self, tab: Tab):
        self._tab = tab

    def type(self, text: str):
        """Simulates typing text character by character."""
        for char in text:
            # self._tab.call_method("Input.dispatchKeyEvent", type="keyDown", text=char)
            self._dispatch_key_event("keyDown", text=char)

            # self._tab.call_method("Input.dispatchKeyEvent", type="keyUp", 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 key_data:
            print(f"Holding down: {key} -> {key_data}")
            self._dispatch_key_event("keyDown", **key_data)

    def up(self, key: str):
        """Releases a held key."""
        key_data = self._get_key_data(key)
        if key_data:
            print(f"Releasing: {key} -> {key_data}")
            self._dispatch_key_event("keyUp", **key_data)

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

        for key in keys[:-1]:  # Everything except the last key
            key_data = self._get_key_data(key)
            self._dispatch_key_event("keyDown", **key_data)

        # Press the main key
        key_data = self._get_key_data(keys[-1])  # Last key in combination
        self._dispatch_key_event("keyDown", **key_data)
        self._dispatch_key_event("keyUp", **key_data)

        for key in reversed(keys[:-1]):
            key_data = self._get_key_data(key)
            self._dispatch_key_event("keyUp", **key_data)   

    def _dispatch_key_event(self, event_type: str, **kwargs):
        """Dispatches a key event to the tab."""
        print(f"Dispatching {event_type} with arguments: {kwargs}")
        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"  # For Mac "Cmd" becomes "Meta"
        elif key == "Controlormeta":
            # Automatically use 'Meta' for Mac, 'Control' for Windows/Linux
            return "Meta" if sys.platform == "darwin" else "Control"
        return key

因此,如果您这样调用 ->

keyboard.press("Control+T")
,预期的行为将打开一个新选项卡。但它不起作用。

python windows chrome-devtools-protocol
1个回答
0
投票

经过多次尝试,我发现为了实现键盘按键机制,您必须以 Unicode 代码发送 cdp 命令。

有几种情况需要考虑,例如shift、ctrl和alt,它们被视为修饰键,对于修饰符,我们有Unicode代码。 因此,假设您想发送相当于

shift+i
的按键。 在这种情况下,必须使用 cdp 命令发送修饰符。

我已经附上了键盘方法的代码块。

class Keys(Enum):
    SHIFT = "\ue008"
    CONTROL = "\ue009"
    ALT = "\ue00a"
    META = "\ue03d"
    ENTER = "\ue007"
    BACKSPACE = "\ue003"
    TAB = "\ue004"
    ESCAPE = "\ue00c"
    DELETE = "\ue017"
    ARROW_LEFT = "\ue012"
    ARROW_UP = "\ue013"
    ARROW_RIGHT = "\ue014"
    ARROW_DOWN = "\ue015"


modifierBit = {
    "\ue008": 8,  # SHIFT
    "\ue009": 2,  # CONTROL
    "\ue00a": 1,  # ALT
    "\ue03d": 4,  # META
}

keyDefinitions = {
    "a": {"key": "a", "code": "KeyA", "keyCode": 65, "shiftKey": "A", "location": 0},
    "b": {"key": "b", "code": "KeyB", "keyCode": 66, "shiftKey": "B", "location": 0},
    "c": {"key": "c", "code": "KeyC", "keyCode": 67, "shiftKey": "C", "location": 0},
    "d": {"key": "d", "code": "KeyD", "keyCode": 68, "shiftKey": "D", "location": 0},
    "e": {"key": "e", "code": "KeyE", "keyCode": 69, "shiftKey": "E", "location": 0},
    "f": {"key": "f", "code": "KeyF", "keyCode": 70, "shiftKey": "F", "location": 0},
    "g": {"key": "g", "code": "KeyG", "keyCode": 71, "shiftKey": "G", "location": 0},
    "h": {"key": "h", "code": "KeyH", "keyCode": 72, "shiftKey": "H", "location": 0},
    "i": {"key": "i", "code": "KeyI", "keyCode": 73, "shiftKey": "I", "location": 0},
    "j": {"key": "j", "code": "KeyJ", "keyCode": 74, "shiftKey": "J", "location": 0},
    "k": {"key": "k", "code": "KeyK", "keyCode": 75, "shiftKey": "K", "location": 0},
    "l": {"key": "l", "code": "KeyL", "keyCode": 76, "shiftKey": "L", "location": 0},
    "m": {"key": "m", "code": "KeyM", "keyCode": 77, "shiftKey": "M", "location": 0},
    "n": {"key": "n", "code": "KeyN", "keyCode": 78, "shiftKey": "N", "location": 0},
    "o": {"key": "o", "code": "KeyO", "keyCode": 79, "shiftKey": "O", "location": 0},
    "p": {"key": "p", "code": "KeyP", "keyCode": 80, "shiftKey": "P", "location": 0},
    "q": {"key": "q", "code": "KeyQ", "keyCode": 81, "shiftKey": "Q", "location": 0},
    "r": {"key": "r", "code": "KeyR", "keyCode": 82, "shiftKey": "R", "location": 0},
    "s": {"key": "s", "code": "KeyS", "keyCode": 83, "shiftKey": "S", "location": 0},
    "t": {"key": "t", "code": "KeyT", "keyCode": 84, "shiftKey": "T", "location": 0},
    "u": {"key": "u", "code": "KeyU", "keyCode": 85, "shiftKey": "U", "location": 0},
    "v": {"key": "v", "code": "KeyV", "keyCode": 86, "shiftKey": "V", "location": 0},
    "w": {"key": "w", "code": "KeyW", "keyCode": 87, "shiftKey": "W", "location": 0},
    "x": {"key": "x", "code": "KeyX", "keyCode": 88, "shiftKey": "X", "location": 0},
    "y": {"key": "y", "code": "KeyY", "keyCode": 89, "shiftKey": "Y", "location": 0},
    "z": {"key": "z", "code": "KeyZ", "keyCode": 90, "shiftKey": "Z", "location": 0},
    "1": {"key": "1", "code": "Digit1", "keyCode": 49, "shiftKey": "!", "location": 0},
    "2": {"key": "2", "code": "Digit2", "keyCode": 50, "shiftKey": "@", "location": 0},
    "3": {"key": "3", "code": "Digit3", "keyCode": 51, "shiftKey": "#", "location": 0},
    "4": {"key": "4", "code": "Digit4", "keyCode": 52, "shiftKey": "$", "location": 0},
    "5": {"key": "5", "code": "Digit5", "keyCode": 53, "shiftKey": "%", "location": 0},
    "6": {"key": "6", "code": "Digit6", "keyCode": 54, "shiftKey": "^", "location": 0},
    "7": {"key": "7", "code": "Digit7", "keyCode": 55, "shiftKey": "&", "location": 0},
    "8": {"key": "8", "code": "Digit8", "keyCode": 56, "shiftKey": "*", "location": 0},
    "9": {"key": "9", "code": "Digit9", "keyCode": 57, "shiftKey": "(", "location": 0},
    "0": {"key": "0", "code": "Digit0", "keyCode": 48, "shiftKey": ")", "location": 0},
    "-": {"key": "-", "code": "Minus", "keyCode": 189, "shiftKey": "_", "location": 0},
    "=": {"key": "=", "code": "Equal", "keyCode": 187, "shiftKey": "+", "location": 0},
    "[": {
        "key": "[",
        "code": "BracketLeft",
        "keyCode": 219,
        "shiftKey": "{",
        "location": 0,
    },
    "]": {
        "key": "]",
        "code": "BracketRight",
        "keyCode": 221,
        "shiftKey": "}",
        "location": 0,
    },
    "\\": {
        "key": "\\",
        "code": "Backslash",
        "keyCode": 220,
        "shiftKey": "|",
        "location": 0,
    },
    ";": {
        "key": ";",
        "code": "Semicolon",
        "keyCode": 186,
        "shiftKey": ":",
        "location": 0,
    },
    "'": {"key": "'", "code": "Quote", "keyCode": 222, "shiftKey": '"', "location": 0},
    ",": {"key": ",", "code": "Comma", "keyCode": 188, "shiftKey": "<", "location": 0},
    ".": {"key": ".", "code": "Period", "keyCode": 190, "shiftKey": ">", "location": 0},
    "/": {"key": "/", "code": "Slash", "keyCode": 191, "shiftKey": "?", "location": 0},
    "`": {
        "key": "`",
        "code": "Backquote",
        "keyCode": 192,
        "shiftKey": "~",
        "location": 0,
    },
    # Numpad
    "Numpad0": {"key": "0", "code": "Numpad0", "keyCode": 96, "location": 3},
    "Numpad1": {"key": "1", "code": "Numpad1", "keyCode": 97, "location": 3},
    "Numpad2": {"key": "2", "code": "Numpad2", "keyCode": 98, "location": 3},
    "Numpad3": {"key": "3", "code": "Numpad3", "keyCode": 99, "location": 3},
    "Numpad4": {"key": "4", "code": "Numpad4", "keyCode": 100, "location": 3},
    "Numpad5": {"key": "5", "code": "Numpad5", "keyCode": 101, "location": 3},
    "Numpad6": {"key": "6", "code": "Numpad6", "keyCode": 102, "location": 3},
    "Numpad7": {"key": "7", "code": "Numpad7", "keyCode": 103, "location": 3},
    "Numpad8": {"key": "8", "code": "Numpad8", "keyCode": 104, "location": 3},
    "Numpad9": {"key": "9", "code": "Numpad9", "keyCode": 105, "location": 3},
    "NumpadMultiply": {
        "key": "*",
        "code": "NumpadMultiply",
        "keyCode": 106,
        "location": 3,
    },
    "NumpadAdd": {"key": "+", "code": "NumpadAdd", "keyCode": 107, "location": 3},
    "NumpadSubtract": {
        "key": "-",
        "code": "NumpadSubtract",
        "keyCode": 109,
        "location": 3,
    },
    "NumpadDecimal": {
        "key": ".",
        "code": "NumpadDecimal",
        "keyCode": 110,
        "location": 3,
    },
    "NumpadDivide": {"key": "/", "code": "NumpadDivide", "keyCode": 111, "location": 3},
    "F1": {"key": "F1", "code": "F1", "keyCode": 112, "location": 0},
    "F2": {"key": "F2", "code": "F2", "keyCode": 113, "location": 0},
    "F3": {"key": "F3", "code": "F3", "keyCode": 114, "location": 0},
    "F4": {"key": "F4", "code": "F4", "keyCode": 115, "location": 0},
    "F5": {"key": "F5", "code": "F5", "keyCode": 116, "location": 0},
    "F6": {"key": "F6", "code": "F6", "keyCode": 117, "location": 0},
    "F7": {"key": "F7", "code": "F7", "keyCode": 118, "location": 0},
    "F8": {"key": "F8", "code": "F8", "keyCode": 119, "location": 0},
    "F9": {"key": "F9", "code": "F9", "keyCode": 120, "location": 0},
    "F10": {"key": "F10", "code": "F10", "keyCode": 121, "location": 0},
    "F11": {"key": "F11", "code": "F11", "keyCode": 122, "location": 0},
    "F12": {"key": "F12", "code": "F12", "keyCode": 123, "location": 0},
    "\ue007": {
        "key": "Enter",
        "code": "Enter",
        "keyCode": 13,
        "text": "\r",
        "location": 0,
    },
    "\ue003": {"key": "Backspace", "code": "Backspace", "keyCode": 8, "location": 0},
    "\ue004": {"key": "Tab", "code": "Tab", "keyCode": 9, "text": "\t", "location": 0},
    "\ue00c": {"key": "Escape", "code": "Escape", "keyCode": 27, "location": 0},
    "\ue017": {"key": "Delete", "code": "Delete", "keyCode": 46, "location": 0},
    "Space": {"key": " ", "code": "Space", "keyCode": 32, "text": " ", "location": 0},
    "\ue012": {"key": "ArrowLeft", "code": "ArrowLeft", "keyCode": 37, "location": 0},
    "\ue013": {"key": "ArrowUp", "code": "ArrowUp", "keyCode": 38, "location": 0},
    "\ue014": {"key": "ArrowRight", "code": "ArrowRight", "keyCode": 39, "location": 0},
    "\ue015": {"key": "ArrowDown", "code": "ArrowDown", "keyCode": 40, "location": 0},
    "\ue008": {"key": "Shift", "code": "ShiftLeft", "keyCode": 16, "location": 1},
    "\ue009": {"key": "Control", "code": "ControlLeft", "keyCode": 17, "location": 1},
    "\ue00a": {"key": "Alt", "code": "AltLeft", "keyCode": 18, "location": 1},
    "\ue03d": {
        "key": "Meta",
        "code": "MetaLeft",
        "keyCode": 91,
        "location": 1,
    },  # Windows key or Command key
    "CapsLock": {"key": "CapsLock", "code": "CapsLock", "keyCode": 20, "location": 0},
    "NumLock": {"key": "NumLock", "code": "NumLock", "keyCode": 144, "location": 0},
    "ScrollLock": {
        "key": "ScrollLock",
        "code": "ScrollLock",
        "keyCode": 145,
        "location": 0,
    },
    "ShiftRight": {"key": "Shift", "code": "ShiftRight", "keyCode": 16, "location": 2},
    "ControlRight": {
        "key": "Control",
        "code": "ControlRight",
        "keyCode": 17,
        "location": 2,
    },
    "AltRight": {"key": "Alt", "code": "AltRight", "keyCode": 18, "location": 2},
    "MetaRight": {
        "key": "Meta",
        "code": "MetaRight",
        "keyCode": 92,
        "location": 2,
    },  # Right Windows key or Command key
    "Home": {"key": "Home", "code": "Home", "keyCode": 36, "location": 0},
    "End": {"key": "End", "code": "End", "keyCode": 35, "location": 0},
    "PageUp": {"key": "PageUp", "code": "PageUp", "keyCode": 33, "location": 0},
    "PageDown": {"key": "PageDown", "code": "PageDown", "keyCode": 34, "location": 0},
    "Insert": {"key": "Insert", "code": "Insert", "keyCode": 45, "location": 0},
    "MediaPlayPause": {
        "key": "MediaPlayPause",
        "code": "MediaPlayPause",
        "keyCode": 179,
        "location": 0,
    },
    "MediaStop": {
        "key": "MediaStop",
        "code": "MediaStop",
        "keyCode": 178,
        "location": 0,
    },
    "MediaTrackNext": {
        "key": "MediaTrackNext",
        "code": "MediaTrackNext",
        "keyCode": 176,
        "location": 0,
    },
    "MediaTrackPrevious": {
        "key": "MediaTrackPrevious",
        "code": "MediaTrackPrevious",
        "keyCode": 177,
        "location": 0,
    },
    "AudioVolumeMute": {
        "key": "AudioVolumeMute",
        "code": "AudioVolumeMute",
        "keyCode": 173,
        "location": 0,
    },
    "AudioVolumeDown": {
        "key": "AudioVolumeDown",
        "code": "AudioVolumeDown",
        "keyCode": 174,
        "location": 0,
    },
    "AudioVolumeUp": {
        "key": "AudioVolumeUp",
        "code": "AudioVolumeUp",
        "keyCode": 175,
        "location": 0,
    },
    "BrowserBack": {
        "key": "BrowserBack",
        "code": "BrowserBack",
        "keyCode": 166,
        "location": 0,
    },
    "BrowserForward": {
        "key": "BrowserForward",
        "code": "BrowserForward",
        "keyCode": 167,
        "location": 0,
    },
    "BrowserRefresh": {
        "key": "BrowserRefresh",
        "code": "BrowserRefresh",
        "keyCode": 168,
        "location": 0,
    },
    "BrowserHome": {
        "key": "BrowserHome",
        "code": "BrowserHome",
        "keyCode": 172,
        "location": 0,
    },
    "F13": {"key": "F13", "code": "F13", "keyCode": 124, "location": 0},
    "F14": {"key": "F14", "code": "F14", "keyCode": 125, "location": 0},
    "F15": {"key": "F15", "code": "F15", "keyCode": 126, "location": 0},
    "F16": {"key": "F16", "code": "F16", "keyCode": 127, "location": 0},
    "F17": {"key": "F17", "code": "F17", "keyCode": 128, "location": 0},
    "F18": {"key": "F18", "code": "F18", "keyCode": 129, "location": 0},
    "F19": {"key": "F19", "code": "F19", "keyCode": 130, "location": 0},
    "F20": {"key": "F20", "code": "F20", "keyCode": 131, "location": 0},
    "NumpadEnter": {
        "key": "Enter",
        "code": "NumpadEnter",
        "keyCode": 13,
        "location": 3,
    },
    "NumpadEqual": {"key": "=", "code": "NumpadEqual", "keyCode": 187, "location": 3},
    "NumLock": {"key": "NumLock", "code": "NumLock", "keyCode": 144, "location": 3},
    "ContextMenu": {
        "key": "ContextMenu",
        "code": "ContextMenu",
        "keyCode": 93,
        "location": 0,
    },
    "Power": {"key": "Power", "code": "Power", "keyCode": 255, "location": 0},
    "Sleep": {"key": "Sleep", "code": "Sleep", "keyCode": 95, "location": 0},
    "WakeUp": {"key": "WakeUp", "code": "WakeUp", "keyCode": 255, "location": 0},
}


class Keyboard:
    """Enhanced keyboard implementation based on DrissionPage's logic"""

    def __init__(self, tab: Tab):
        self._tab = tab
        self.modifier = 0

    def make_input_data(self, key: str, key_up: bool = False) -> Optional[Dict]:
        """Creates input data for key events based on DrissionPage's implementation"""
        data = keyDefinitions.get(key)
        if not data:
            return None

        result = {
            "modifiers": self.modifier,
            "autoRepeat": False,
            "type": "keyUp" if key_up else "keyDown",
        }

        shift = self.modifier & 8

        if shift and data.get("shiftKey"):
            result["key"] = data["shiftKey"]
            result["text"] = data["shiftKey"]
        elif "key" in data:
            result["key"] = data["key"]

        if len(result.get("key", "")) == 1:
            result["text"] = result["key"]

        sys_text = "windowsVirtualKeyCode"
        if shift and data.get("shiftKeyCode"):
            result[sys_text] = data["shiftKeyCode"]
        elif "keyCode" in data:
            result[sys_text] = data["keyCode"]

        if "code" in data:
            result["code"] = data["code"]

        if "location" in data:
            result["location"] = data["location"]
            result["isKeypad"] = data["location"] == 3
        else:
            result["location"] = 0
            result["isKeypad"] = False

        if shift and data.get("shiftText"):
            result["text"] = data["shiftText"]
            result["unmodifiedText"] = data["shiftText"]
        elif "text" in data:
            result["text"] = data["text"]
            result["unmodifiedText"] = data["text"]

        if self.modifier & ~8:
            result["text"] = ""

        result["type"] = (
            "keyUp" if key_up else ("keyDown" if result.get("text") else "rawKeyDown")
        )

        return result

    def down(self, key: str):
        """Holds down a specified key"""
        key = getattr(Keys, key.upper(), key.lower())
        key_value = key.value if isinstance(key, Keys) else key
        if key_value in modifierBit:
            self.modifier |= modifierBit[key_value]
            return self

        data = self.make_input_data(key_value)
        if not data:
            raise ValueError(f"Invalid key: {key}")

        self._tab.call_method("Input.dispatchKeyEvent", **data)
        return self

    def up(self, key: str):
        """Releases a held key"""
        key = getattr(Keys, key.upper(), key.lower())
        key_value = key.value if isinstance(key, Keys) else key

        # Handle modifier keys
        if key_value in modifierBit:
            self.modifier &= ~modifierBit[key_value]
            return self

        # Handle regular keys
        data = self.make_input_data(key_value, key_up=True)
        if not data:
            raise ValueError(f"Invalid key: {key}")

        self._tab.call_method("Input.dispatchKeyEvent", **data)
        return self

    def type(self, text: str):
        """Types text character by character"""
        for char in text:
            if char == " ":
                data = self.make_input_data("Space")
            else:
                data = self.make_input_data(char)

            if data:
                self._tab.call_method("Input.dispatchKeyEvent", **data)
                
                if char == " ":
                    up_data = self.make_input_data("Space", key_up=True)
                else:
                    up_data = self.make_input_data(char, key_up=True)
                if up_data:
                    self._tab.call_method("Input.dispatchKeyEvent", **up_data)
        return self

© www.soinside.com 2019 - 2024. All rights reserved.