Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added type stub for keyboard #8666

Merged
merged 18 commits into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyrightconfig.stricter.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"stubs/invoke",
"stubs/jmespath",
"stubs/jsonschema",
"stubs/keyboard",
"stubs/ldap3",
"stubs/Markdown",
"stubs/mock",
Expand Down
17 changes: 17 additions & 0 deletions stubs/keyboard/@tests/stubtest_allowlist.txt
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions stubs/keyboard/METADATA.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version = "0.13.*"

[tool.stubtest]
ignore_missing_stub = false
115 changes: 115 additions & 0 deletions stubs/keyboard/keyboard/__init__.pyi
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions stubs/keyboard/keyboard/_canonical_names.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
canonical_names: dict[str, str]
sided_modifiers: set[str]
all_modifiers: set[str]

def normalize_name(name: str) -> str: ...
24 changes: 24 additions & 0 deletions stubs/keyboard/keyboard/_generic.pyi
Original file line number Diff line number Diff line change
@@ -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: ...
28 changes: 28 additions & 0 deletions stubs/keyboard/keyboard/_keyboard_event.pyi
Original file line number Diff line number Diff line change
@@ -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: ...
43 changes: 43 additions & 0 deletions stubs/keyboard/keyboard/_mouse_event.pyi
Original file line number Diff line number Diff line change
@@ -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
78 changes: 78 additions & 0 deletions stubs/keyboard/keyboard/mouse.pyi
Original file line number Diff line number Diff line change
@@ -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