diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index 2a335d75f698..08de0060f412 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -51,6 +51,7 @@ "stubs/invoke", "stubs/jmespath", "stubs/jsonschema", + "stubs/keyboard", "stubs/ldap3", "stubs/Markdown", "stubs/mock", diff --git a/stubs/keyboard/@tests/stubtest_allowlist.txt b/stubs/keyboard/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000000..8ecec31a6b87 --- /dev/null +++ b/stubs/keyboard/@tests/stubtest_allowlist.txt @@ -0,0 +1,17 @@ +# scan_code *should* never be None in real use. This is also according to docs. +keyboard.KeyboardEvent.scan_code +keyboard._keyboard_event.KeyboardEvent.scan_code +# Defaults don't align with possible values +keyboard.mouse.on_button +keyboard.mouse.wait +# Private modules and tests +keyboard.__main__ +keyboard._darwinkeyboard +keyboard._darwinmouse +keyboard._keyboard_tests +keyboard._mouse_tests +keyboard._nixcommon +keyboard._nixkeyboard +keyboard._nixmouse +keyboard._winkeyboard +keyboard._winmouse diff --git a/stubs/keyboard/METADATA.toml b/stubs/keyboard/METADATA.toml new file mode 100644 index 000000000000..18912b609ed8 --- /dev/null +++ b/stubs/keyboard/METADATA.toml @@ -0,0 +1,4 @@ +version = "0.13.*" + +[tool.stubtest] +ignore_missing_stub = false diff --git a/stubs/keyboard/keyboard/__init__.pyi b/stubs/keyboard/keyboard/__init__.pyi new file mode 100644 index 000000000000..3235e0ac5453 --- /dev/null +++ b/stubs/keyboard/keyboard/__init__.pyi @@ -0,0 +1,115 @@ +from collections.abc import Callable, Generator, Iterable, Sequence +from queue import Queue +from threading import Event as _UninterruptibleEvent +from typing import Optional +from typing_extensions import TypeAlias + +from ._canonical_names import all_modifiers as all_modifiers, sided_modifiers as sided_modifiers +from ._generic import GenericListener as _GenericListener +from ._keyboard_event import KEY_DOWN as KEY_DOWN, KEY_UP as KEY_UP, KeyboardEvent as KeyboardEvent + +_Key: TypeAlias = int | str +_ScanCodeList: TypeAlias = list[int] | tuple[int, ...] +_ParseableHotkey: TypeAlias = _Key | list[int | _ScanCodeList] | tuple[int | _ScanCodeList, ...] +_Callback: TypeAlias = Callable[[KeyboardEvent], Optional[bool]] | Callable[[], Optional[bool]] +# mypy doesn't support PEP 646's TypeVarTuple yet: https://github.com/python/mypy/issues/12280 +# _Ts = TypeVarTuple("_Ts") +_Ts: TypeAlias = tuple[object, ...] + +version: str + +class _Event(_UninterruptibleEvent): + def wait(self) -> None: ... # type: ignore[override] # Actual implementation + +def is_modifier(key: _Key | None) -> bool: ... +def key_to_scan_codes(key: _ParseableHotkey, error_if_missing: bool = ...) -> tuple[int, ...]: ... +def parse_hotkey(hotkey: _ParseableHotkey) -> tuple[tuple[tuple[int, ...], ...], ...]: ... +def send(hotkey: _ParseableHotkey, do_press: bool = ..., do_release: bool = ...) -> None: ... + +press_and_release = send + +def press(hotkey: _ParseableHotkey) -> None: ... +def release(hotkey: _ParseableHotkey) -> None: ... + +# is_pressed cannot check multi-step hotkeys, so not using _ParseableHotkey + +def is_pressed(hotkey: _Key | _ScanCodeList) -> bool: ... +def call_later(fn: Callable[..., None], args: _Ts = ..., delay: float = ...) -> None: ... +def hook(callback: _Callback, suppress: bool = ..., on_remove: Callable[[], None] = ...) -> Callable[[], None]: ... +def on_press(callback: _Callback, suppress: bool = ...) -> Callable[[], None]: ... +def on_release(callback: _Callback, suppress: bool = ...) -> Callable[[], None]: ... +def hook_key(key: _ParseableHotkey, callback: _Callback, suppress: bool = ...) -> Callable[[], None]: ... +def on_press_key(key: _ParseableHotkey, callback: _Callback, suppress: bool = ...) -> Callable[[], None]: ... +def on_release_key(key: _ParseableHotkey, callback: _Callback, suppress: bool = ...) -> Callable[[], None]: ... +def unhook(remove: _Callback) -> None: ... + +unhook_key = unhook + +def unhook_all() -> None: ... +def block_key(key: _ParseableHotkey) -> Callable[[], None]: ... + +unblock_key = unhook_key + +def remap_key(src: _ParseableHotkey, dst: _ParseableHotkey) -> Callable[[], None]: ... + +unremap_key = unhook_key + +def parse_hotkey_combinations(hotkey: _ParseableHotkey) -> tuple[tuple[tuple[int, ...], ...], ...]: ... +def add_hotkey( + hotkey: _ParseableHotkey, + callback: Callable[..., bool | None], + args: _Ts = ..., + suppress: bool = ..., + timeout: float = ..., + trigger_on_release: bool = ..., +) -> Callable[[], None]: ... + +register_hotkey = add_hotkey + +def remove_hotkey(hotkey_or_callback: _ParseableHotkey | _Callback) -> None: ... + +unregister_hotkey = remove_hotkey +clear_hotkey = remove_hotkey + +def unhook_all_hotkeys() -> None: ... + +unregister_all_hotkeys = unhook_all_hotkeys +remove_all_hotkeys = unhook_all_hotkeys +clear_all_hotkeys = unhook_all_hotkeys + +def remap_hotkey( + src: _ParseableHotkey, dst: _ParseableHotkey, suppress: bool = ..., trigger_on_release: bool = ... +) -> Callable[[], None]: ... + +unremap_hotkey = remove_hotkey + +def stash_state() -> list[int]: ... +def restore_state(scan_codes: Iterable[int]) -> None: ... +def restore_modifiers(scan_codes: Iterable[int]) -> None: ... +def write(text: str, delay: float = ..., restore_state_after: bool = ..., exact: bool | None = ...) -> None: ... +def wait(hotkey: _ParseableHotkey | None = ..., suppress: bool = ..., trigger_on_release: bool = ...) -> None: ... +def get_hotkey_name(names: Iterable[str] | None = ...) -> str: ... +def read_event(suppress: bool = ...) -> KeyboardEvent: ... +def read_key(suppress: bool = ...) -> _Key: ... +def read_hotkey(suppress: bool = ...) -> str: ... +def get_typed_strings(events: Iterable[KeyboardEvent], allow_backspace: bool = ...) -> Generator[str, None, None]: ... +def start_recording( + recorded_events_queue: Queue[KeyboardEvent] | None = ..., +) -> tuple[Queue[KeyboardEvent], Callable[[], None]]: ... +def stop_recording() -> list[KeyboardEvent]: ... +def record(until: str = ..., suppress: bool = ..., trigger_on_release: bool = ...) -> list[KeyboardEvent]: ... +def play(events: Iterable[KeyboardEvent], speed_factor: float = ...) -> None: ... + +replay = play + +def add_word_listener( + word: str, callback: _Callback, triggers: Sequence[str] = ..., match_suffix: bool = ..., timeout: float = ... +) -> Callable[[], None]: ... +def remove_word_listener(word_or_handler: str | _Callback) -> None: ... +def add_abbreviation( + source_text: str, replacement_text: str, match_suffix: bool = ..., timeout: float = ... +) -> Callable[[], None]: ... + +register_word_listener = add_word_listener +register_abbreviation = add_abbreviation +remove_abbreviation = remove_word_listener diff --git a/stubs/keyboard/keyboard/_canonical_names.pyi b/stubs/keyboard/keyboard/_canonical_names.pyi new file mode 100644 index 000000000000..8a0c3a007643 --- /dev/null +++ b/stubs/keyboard/keyboard/_canonical_names.pyi @@ -0,0 +1,5 @@ +canonical_names: dict[str, str] +sided_modifiers: set[str] +all_modifiers: set[str] + +def normalize_name(name: str) -> str: ... diff --git a/stubs/keyboard/keyboard/_generic.pyi b/stubs/keyboard/keyboard/_generic.pyi new file mode 100644 index 000000000000..21f74b9e1830 --- /dev/null +++ b/stubs/keyboard/keyboard/_generic.pyi @@ -0,0 +1,24 @@ +from collections.abc import Callable +from queue import Queue +from threading import Lock, Thread +from typing import ClassVar +from typing_extensions import Literal, TypeAlias + +from ._keyboard_event import KeyboardEvent +from ._mouse_event import _MouseEvent + +_Event: TypeAlias = KeyboardEvent | _MouseEvent + +class GenericListener: + lock: ClassVar[Lock] + handlers: list[Callable[[_Event], bool | None]] + listening: bool + queue: Queue[_Event] + listening_thread: Thread | None + processing_thread: Thread | None + def invoke_handlers(self, event: _Event) -> Literal[1] | None: ... + def start_if_necessary(self) -> None: ... + def pre_process_event(self, event: _Event) -> None: ... + def process(self) -> None: ... + def add_handler(self, handler: Callable[[_Event], bool | None]) -> None: ... + def remove_handler(self, handler: Callable[[_Event], bool | None]) -> None: ... diff --git a/stubs/keyboard/keyboard/_keyboard_event.pyi b/stubs/keyboard/keyboard/_keyboard_event.pyi new file mode 100644 index 000000000000..9c511fdccf59 --- /dev/null +++ b/stubs/keyboard/keyboard/_keyboard_event.pyi @@ -0,0 +1,28 @@ +from typing_extensions import Literal + +from ._canonical_names import canonical_names as canonical_names, normalize_name as normalize_name + +KEY_DOWN: Literal["down"] +KEY_UP: Literal["up"] + +class KeyboardEvent: + event_type: Literal["down", "up"] | None + scan_code: int + name: str | None + time: float | None + device: str | None + modifiers: tuple[str, ...] | None + is_keypad: bool | None + + def __init__( + self, + event_type: Literal["down", "up"] | None, + scan_code: int, + name: str | None = ..., + time: float | None = ..., + device: str | None = ..., + modifiers: tuple[str, ...] | None = ..., + is_keypad: bool | None = ..., + ) -> None: ... + def to_json(self, ensure_ascii: bool = ...) -> str: ... + def __eq__(self, other: object) -> bool: ... diff --git a/stubs/keyboard/keyboard/_mouse_event.pyi b/stubs/keyboard/keyboard/_mouse_event.pyi new file mode 100644 index 000000000000..479fe4c9dbd8 --- /dev/null +++ b/stubs/keyboard/keyboard/_mouse_event.pyi @@ -0,0 +1,43 @@ +import sys +from typing import NamedTuple +from typing_extensions import Literal, TypeAlias + +_MouseEvent: TypeAlias = ButtonEvent | WheelEvent | MoveEvent # noqa: Y047 # Used outside + +LEFT: Literal["left"] +RIGHT: Literal["right"] +MIDDLE: Literal["middle"] +X: Literal["x"] +X2: Literal["x2"] + +UP: Literal["up"] +DOWN: Literal["down"] +DOUBLE: Literal["double"] +WHEEL: Literal["wheel"] + +VERTICAL: Literal["vertical"] +HORIZONTAL: Literal["horizontal"] + +if sys.platform == "linux" or sys.platform == "win32": + _MouseButton: TypeAlias = Literal["left", "right", "middle", "x", "x2"] +else: + _MouseButton: TypeAlias = Literal["left", "right", "middle"] + +if sys.platform == "win32": + _MouseEventType: TypeAlias = Literal["up", "down", "double", "wheel"] +else: + _MouseEventType: TypeAlias = Literal["up", "down"] + +class ButtonEvent(NamedTuple): + event_type: _MouseEventType + button: _MouseButton + time: float + +class WheelEvent(NamedTuple): + delta: int + time: float + +class MoveEvent(NamedTuple): + x: int + y: int + time: float diff --git a/stubs/keyboard/keyboard/mouse.pyi b/stubs/keyboard/keyboard/mouse.pyi new file mode 100644 index 000000000000..6285b0b0541d --- /dev/null +++ b/stubs/keyboard/keyboard/mouse.pyi @@ -0,0 +1,78 @@ +import sys +from collections.abc import Callable, Iterable +from ctypes import c_long +from typing import SupportsInt, TypeVar +from typing_extensions import Literal, TypeAlias + +from ._generic import GenericListener as _GenericListener +from ._mouse_event import ( + DOUBLE as DOUBLE, + DOWN as DOWN, + LEFT as LEFT, + MIDDLE as MIDDLE, + RIGHT as RIGHT, + UP as UP, + X2 as X2, + ButtonEvent as ButtonEvent, + MoveEvent as MoveEvent, + WheelEvent as WheelEvent, + X as X, + _MouseButton, + _MouseEvent, + _MouseEventType, +) + +# mypy doesn't support PEP 646's TypeVarTuple yet: https://github.com/python/mypy/issues/12280 +# _Ts = TypeVarTuple("_Ts") +_Ts: TypeAlias = tuple[object, ...] +_Callback: TypeAlias = Callable[[_MouseEvent], bool | None] +_C = TypeVar("_C", bound=_Callback) + +class _MouseListener(_GenericListener): + def init(self) -> None: ... + def pre_process_event( # type: ignore[override] # Mouse specific events and return + self, event: _MouseEvent + ) -> Literal[True]: ... + def listen(self) -> None: ... + +def is_pressed(button: _MouseButton = ...): ... +def press(button: _MouseButton = ...) -> None: ... +def release(button: _MouseButton = ...) -> None: ... +def click(button: _MouseButton = ...) -> None: ... +def double_click(button: _MouseButton = ...) -> None: ... +def right_click() -> None: ... +def wheel(delta: int = ...) -> None: ... +def move(x: SupportsInt, y: SupportsInt, absolute: bool = ..., duration: float = ...) -> None: ... +def drag(start_x: int, start_y: int, end_x: int, end_y: int, absolute: bool = ..., duration: float = ...) -> None: ... +def on_button( + callback: Callable[..., None], + args: _Ts = ..., + buttons: list[_MouseButton] | tuple[_MouseButton, ...] | _MouseButton = ..., + types: list[_MouseEventType] | tuple[_MouseEventType, ...] | _MouseEventType = ..., +) -> _Callback: ... +def on_click(callback: Callable[..., None], args: _Ts = ...) -> _Callback: ... +def on_double_click(callback: Callable[..., None], args: _Ts = ...) -> _Callback: ... +def on_right_click(callback: Callable[..., None], args: _Ts = ...) -> _Callback: ... +def on_middle_click(callback: Callable[..., None], args: _Ts = ...) -> _Callback: ... +def wait(button: _MouseButton = ..., target_types: tuple[_MouseEventType] = ...) -> None: ... + +if sys.platform == "win32": + def get_position() -> tuple[c_long, c_long]: ... + +else: + def get_position() -> tuple[int, int]: ... + +def hook(callback: _C) -> _C: ... +def unhook(callback: _Callback) -> None: ... +def unhook_all() -> None: ... +def record(button: _MouseButton = ..., target_types: tuple[_MouseEventType] = ...) -> _MouseEvent: ... +def play( + events: Iterable[_MouseEvent], + speed_factor: float = ..., + include_clicks: bool = ..., + include_moves: bool = ..., + include_wheel: bool = ..., +) -> None: ... + +replay = play +hold = press