Source code for pyppeteer.input

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Keyboard and Mouse module."""

import asyncio
from typing import Any, Dict, TYPE_CHECKING

from pyppeteer.connection import CDPSession
from pyppeteer.errors import PyppeteerError
from pyppeteer.us_keyboard_layout import keyDefinitions
from pyppeteer.util import merge_dict

if TYPE_CHECKING:
    from typing import Set  # noqa: F401


[docs]class Keyboard(object): """Keyboard class provides as api for managing a virtual keyboard. The high level api is :meth:`type`, which takes raw characters and generate proper keydown, keypress/input, and keyup events on your page. For finer control, you can use :meth:`down`, :meth:`up`, and :meth:`sendCharacter` to manually fire events as if they were generated from a real keyboard. An example of holding down ``Shift`` in order to select and delete some text: .. code:: await page.keyboard.type('Hello, World!') await page.keyboard.press('ArrowLeft') await page.keyboard.down('Shift') for i in ' World': await page.keyboard.press('ArrowLeft') await page.keyboard.up('Shift') await page.keyboard.press('Backspace') # Result text will end up saying 'Hello!'. An example of pressing ``A``: .. code:: await page.keyboard.down('Shift') await page.keyboard.press('KeyA') await page.keyboard.up('Shift') """ def __init__(self, client: CDPSession) -> None: self._client = client self._modifiers = 0 self._pressedKeys: Set[str] = set()
[docs] async def down(self, key: str, options: dict = None, **kwargs: Any ) -> None: """Dispatch a ``keydown`` event with ``key``. If ``key`` is a single character and no modifier keys besides ``Shift`` are being held down, and a ``keypress``/``input`` event will also generated. The ``text`` option can be specified to force an ``input`` event to be generated. If ``key`` is a modifier key, like ``Shift``, ``Meta``, or ``Alt``, subsequent key presses will be sent with that modifier active. To release the modifier key, use :meth:`up` method. :arg str key: Name of key to press, such as ``ArrowLeft``. :arg dict options: Option can have ``text`` field, and if this option specified, generate an input event with this text. .. note:: Modifier keys DO influence :meth:`down`. Holding down ``shift`` will type the text in upper case. """ options = merge_dict(options, kwargs) description = self._keyDescriptionForString(key) autoRepeat = description['code'] in self._pressedKeys self._pressedKeys.add(description['code']) self._modifiers |= self._modifierBit(description['key']) text = options.get('text') if text is None: text = description['text'] await self._client.send('Input.dispatchKeyEvent', { 'type': 'keyDown' if text else 'rawKeyDown', 'modifiers': self._modifiers, 'windowsVirtualKeyCode': description['keyCode'], 'code': description['code'], 'key': description['key'], 'text': text, 'unmodifiedText': text, 'autoRepeat': autoRepeat, 'location': description['location'], 'isKeypad': description['location'] == 3, })
def _modifierBit(self, key: str) -> int: if key == 'Alt': return 1 if key == 'Control': return 2 if key == 'Meta': return 4 if key == 'Shift': return 8 return 0 def _keyDescriptionForString(self, keyString: str) -> Dict: # noqa: C901 shift = self._modifiers & 8 description = { 'key': '', 'keyCode': 0, 'code': '', 'text': '', 'location': 0, } definition: Dict = keyDefinitions.get(keyString) # type: ignore if not definition: raise PyppeteerError(f'Unknown key: {keyString}') if 'key' in definition: description['key'] = definition['key'] if shift and definition.get('shiftKey'): description['key'] = definition['shiftKey'] if 'keyCode' in definition: description['keyCode'] = definition['keyCode'] if shift and definition.get('shiftKeyCode'): description['keyCode'] = definition['shiftKeyCode'] if 'code' in definition: description['code'] = definition['code'] if 'location' in definition: description['location'] = definition['location'] if len(description['key']) == 1: # type: ignore description['text'] = description['key'] if 'text' in definition: description['text'] = definition['text'] if shift and definition.get('shiftText'): description['text'] = definition['shiftText'] if self._modifiers & ~8: description['text'] = '' return description
[docs] async def up(self, key: str) -> None: """Dispatch a ``keyup`` event of the ``key``. :arg str key: Name of key to release, such as ``ArrowLeft``. """ description = self._keyDescriptionForString(key) self._modifiers &= ~self._modifierBit(description['key']) if description['code'] in self._pressedKeys: self._pressedKeys.remove(description['code']) await self._client.send('Input.dispatchKeyEvent', { 'type': 'keyUp', 'modifiers': self._modifiers, 'key': description['key'], 'windowsVirtualKeyCode': description['keyCode'], 'code': description['code'], 'location': description['location'], })
[docs] async def sendCharacter(self, char: str) -> None: """Send character into the page. This method dispatches a ``keypress`` and ``input`` event. This does not send a ``keydown`` or ``keyup`` event. .. note:: Modifier keys DO NOT effect :meth:`sendCharacter`. Holding down ``shift`` will not type the text in upper case. """ await self._client.send('Input.dispatchKeyEvent', { 'type': 'char', 'modifiers': self._modifiers, 'text': char, 'key': char, 'unmodifiedText': char, })
[docs] async def type(self, text: str, options: Dict = None, **kwargs: Any ) -> None: """Type characters into a focused element. This method sends ``keydown``, ``keypress``/``input``, and ``keyup`` event for each character in the ``text``. To press a special key, like ``Control`` or ``ArrowDown``, use :meth:`press` method. :arg str text: Text to type into a focused element. :arg dict options: Options can have ``delay`` (int|float) field, which specifies time to wait between key presses in milliseconds. Defaults to 0. .. note:: Modifier keys DO NOT effect :meth:`type`. Holding down ``shift`` will not type the text in upper case. """ options = merge_dict(options, kwargs) delay = options.get('delay', 0) for char in text: if char in keyDefinitions: await self.press(char, {'delay': delay}) else: await self.sendCharacter(char) if delay: await asyncio.sleep(delay / 1000)
[docs] async def press(self, key: str, options: Dict = None, **kwargs: Any ) -> None: """Press ``key``. If ``key`` is a single character and no modifier keys besides ``Shift`` are being held down, a ``keypress``/``input`` event will also generated. The ``text`` option can be specified to force an input event to be generated. :arg str key: Name of key to press, such as ``ArrowLeft``. This method accepts the following options: * ``text`` (str): If specified, generates an input event with this text. * ``delay`` (int|float): Time to wait between ``keydown`` and ``keyup``. Defaults to 0. .. note:: Modifier keys DO effect :meth:`press`. Holding down ``Shift`` will type the text in upper case. """ options = merge_dict(options, kwargs) await self.down(key, options) if 'delay' in options: await asyncio.sleep(options['delay'] / 1000) await self.up(key)
[docs]class Mouse(object): """Mouse class.""" def __init__(self, client: CDPSession, keyboard: Keyboard) -> None: self._client = client self._keyboard = keyboard self._x = 0.0 self._y = 0.0 self._button = 'none'
[docs] async def move(self, x: float, y: float, options: dict = None, **kwargs: Any) -> None: """Move mouse cursor (dispatches a ``mousemove`` event). Options can accepts ``steps`` (int) field. If this ``steps`` option specified, Sends intermediate ``mousemove`` events. Defaults to 1. """ options = merge_dict(options, kwargs) fromX = self._x fromY = self._y self._x = x self._y = y steps = options.get('steps', 1) for i in range(1, steps + 1): x = round(fromX + (self._x - fromX) * (i / steps)) y = round(fromY + (self._y - fromY) * (i / steps)) await self._client.send('Input.dispatchMouseEvent', { 'type': 'mouseMoved', 'button': self._button, 'x': x, 'y': y, 'modifiers': self._keyboard._modifiers, })
[docs] async def click(self, x: float, y: float, options: dict = None, **kwargs: Any) -> None: """Click button at (``x``, ``y``). Shortcut to :meth:`move`, :meth:`down`, and :meth:`up`. This method accepts the following options: * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to ``left``. * ``clickCount`` (int): defaults to 1. * ``delay`` (int|float): Time to wait between ``mousedown`` and ``mouseup`` in milliseconds. Defaults to 0. """ options = merge_dict(options, kwargs) await self.move(x, y) await self.down(options) if options and options.get('delay'): await asyncio.sleep(options.get('delay', 0) / 1000) await self.up(options)
[docs] async def down(self, options: dict = None, **kwargs: Any) -> None: """Press down button (dispatches ``mousedown`` event). This method accepts the following options: * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to ``left``. * ``clickCount`` (int): defaults to 1. """ options = merge_dict(options, kwargs) self._button = options.get('button', 'left') await self._client.send('Input.dispatchMouseEvent', { 'type': 'mousePressed', 'button': self._button, 'x': self._x, 'y': self._y, 'modifiers': self._keyboard._modifiers, 'clickCount': options.get('clickCount') or 1, })
[docs] async def up(self, options: dict = None, **kwargs: Any) -> None: """Release pressed button (dispatches ``mouseup`` event). This method accepts the following options: * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to ``left``. * ``clickCount`` (int): defaults to 1. """ options = merge_dict(options, kwargs) self._button = 'none' await self._client.send('Input.dispatchMouseEvent', { 'type': 'mouseReleased', 'button': options.get('button', 'left'), 'x': self._x, 'y': self._y, 'modifiers': self._keyboard._modifiers, 'clickCount': options.get('clickCount') or 1, })
class Touchscreen(object): """Touchscreen class.""" def __init__(self, client: CDPSession, keyboard: Keyboard) -> None: """Make new touchscreen object.""" self._client = client self._keyboard = keyboard async def tap(self, x: float, y: float) -> None: """Tap (``x``, ``y``). Dispatches a ``touchstart`` and ``touchend`` event. """ touchPoints = [{'x': round(x), 'y': round(y)}] await self._client.send('Input.dispatchTouchEvent', { 'type': 'touchStart', 'touchPoints': touchPoints, 'modifiers': self._keyboard._modifiers, }) await self._client.send('Input.dispatchTouchEvent', { 'type': 'touchEnd', 'touchPoints': [], 'modifiers': self._keyboard._modifiers, })