-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Added type stub for keyboard #8666
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
Changes from 17 commits
72c7ec4
8f9a3ff
93ac04e
151009a
78626e3
403d8b9
e4d65c1
4aebccf
a977295
8520713
f4b79fd
92b3c37
48bb876
b997dca
7601043
4f54455
1f11b30
9796cb1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
version = "0.13.*" | ||
|
||
[tool.stubtest] | ||
ignore_missing_stub = false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
from collections import Counter, defaultdict, deque | ||
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 Literal, 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]] | ||
# Can't use ParamSpecArgs on `args`, only on `*args` | ||
# _P = ParamSpec("_P") | ||
_P: TypeAlias = tuple[object, ...] | ||
|
||
version: str | ||
|
||
class _Event(_UninterruptibleEvent): | ||
def wait(self) -> None: ... # type: ignore[override] # Actual implementation | ||
|
||
def is_modifier(key: _Key | None) -> bool: ... | ||
|
||
class _KeyboardListener(_GenericListener): | ||
transition_table: dict[ | ||
tuple[Literal["free"], Literal["up"], Literal["modifier"]] | ||
| tuple[Literal["free"], Literal["down"], Literal["modifier"]] | ||
| tuple[Literal["pending"], Literal["up"], Literal["modifier"]] | ||
| tuple[Literal["pending"], Literal["down"], Literal["modifier"]] | ||
| tuple[Literal["suppressed"], Literal["up"], Literal["modifier"]] | ||
| tuple[Literal["suppressed"], Literal["down"], Literal["modifier"]] | ||
| tuple[Literal["allowed"], Literal["up"], Literal["modifier"]] | ||
| tuple[Literal["allowed"], Literal["down"], Literal["modifier"]] | ||
| tuple[Literal["free"], Literal["up"], Literal["hotkey"]] | ||
| tuple[Literal["free"], Literal["down"], Literal["hotkey"]] | ||
| tuple[Literal["pending"], Literal["up"], Literal["hotkey"]] | ||
| tuple[Literal["pending"], Literal["down"], Literal["hotkey"]] | ||
| tuple[Literal["suppressed"], Literal["up"], Literal["hotkey"]] | ||
| tuple[Literal["suppressed"], Literal["down"], Literal["hotkey"]] | ||
| tuple[Literal["allowed"], Literal["up"], Literal["hotkey"]] | ||
| tuple[Literal["allowed"], Literal["down"], Literal["hotkey"]] | ||
| tuple[Literal["free"], Literal["up"], Literal["other"]] | ||
| tuple[Literal["free"], Literal["down"], Literal["other"]] | ||
| tuple[Literal["pending"], Literal["up"], Literal["other"]] | ||
| tuple[Literal["pending"], Literal["down"], Literal["other"]] | ||
| tuple[Literal["suppressed"], Literal["up"], Literal["other"]] | ||
| tuple[Literal["suppressed"], Literal["down"], Literal["other"]] | ||
| tuple[Literal["allowed"], Literal["up"], Literal["other"]] | ||
| tuple[Literal["allowed"], Literal["down"], Literal["other"]], | ||
tuple[Literal[False], Literal[True], Literal["free"]] | ||
| tuple[Literal[False], Literal[False], Literal["pending"]] | ||
| tuple[Literal[True], Literal[True], Literal["free"]] | ||
| tuple[Literal[False], Literal[True], Literal["allowed"]] | ||
| tuple[Literal[False], Literal[False], Literal["free"]] | ||
| tuple[Literal[False], Literal[False], Literal["suppressed"]] | ||
| tuple[Literal[False], None, Literal["free"]] | ||
| tuple[Literal[False], None, Literal["suppressed"]] | ||
| tuple[Literal[False], None, Literal["allowed"]] | ||
| tuple[Literal[True], Literal[True], Literal["allowed"]] | ||
| tuple[Literal[False], Literal[False], Literal["allowed"]], | ||
] | ||
active_modifiers: set[int] | ||
blocking_hooks: list[_Callback] | ||
blocking_keys: defaultdict[int, list[_Callback]] | ||
nonblocking_keys: defaultdict[int, list[_Callback]] | ||
blocking_hotkeys: defaultdict[tuple[int, ...], list[_Callback]] | ||
nonblocking_hotkeys: defaultdict[tuple[int, ...], list[_Callback]] | ||
filtered_modifiers: Counter[int] | ||
is_replaying: bool | ||
modifier_states: dict[_Key, str] | ||
def init(self) -> None: ... | ||
def pre_process_event(self, event): ... | ||
def direct_callback(self, event): ... | ||
def listen(self) -> None: ... | ||
|
||
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: _P = ..., delay: float = ...) -> None: ... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be expressible with PEP 646's TypeVarTuple, though we can't use that yet because mypy doesn't support it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good to know. I hadn't looked into 3.11's variadic generics yet. That's cool. So this would be dependant on python/mypy#12280 |
||
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: _P = ..., | ||
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[deque[KeyboardEvent]]: ... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's just a list of KeyboardEvents. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I got confused into thinking it was a list of deque, but it's just converting |
||
def record(until: str = ..., suppress: bool = ..., trigger_on_release: bool = ...) -> list[deque[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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
basestring = str | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove this, it's clearly not meant as a part of the API. |
||
canonical_names: dict[str, str] | ||
sided_modifiers: set[str] | ||
all_modifiers: set[str] | ||
|
||
def normalize_name(name: str) -> str: ... |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||||
from collections.abc import Callable | ||||||
from queue import Queue | ||||||
from threading import Lock, Thread | ||||||
from typing_extensions import Literal, TypeAlias | ||||||
|
||||||
from ._keyboard_event import KeyboardEvent | ||||||
from ._mouse_event import _MouseEvent | ||||||
|
||||||
_Event: TypeAlias = KeyboardEvent | _MouseEvent | ||||||
|
||||||
class GenericListener: | ||||||
lock: Lock | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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: ... |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from typing_extensions import Literal | ||
|
||
from ._canonical_names import canonical_names as canonical_names, normalize_name as normalize_name | ||
|
||
basestring = str | ||
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: ... |
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 |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,76 @@ | ||||||||
import sys | ||||||||
from collections.abc import Callable, Iterable | ||||||||
from ctypes import c_long | ||||||||
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, | ||||||||
) | ||||||||
|
||||||||
# Can't use ParamSpecArgs on `args`, only on `*args` | ||||||||
# _P = ParamSpec("_P") | ||||||||
_P: TypeAlias = tuple[object, ...] | ||||||||
_Callback: TypeAlias = Callable[[_MouseEvent], bool | None] | ||||||||
|
||||||||
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: int | c_long, y: int | c_long, absolute: bool = ..., duration: float = ...) -> None: ... | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
It calls Line 200 in 8e4b89a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't know about that one. Thanks! |
||||||||
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: _P = ..., | ||||||||
buttons: list[_MouseButton] | tuple[_MouseButton, ...] | _MouseButton = ..., | ||||||||
types: list[_MouseEventType] | tuple[_MouseEventType, ...] | _MouseEventType = ..., | ||||||||
) -> _Callback: ... | ||||||||
def on_click(callback: Callable[..., None], args: _P = ...) -> _Callback: ... | ||||||||
def on_double_click(callback: Callable[..., None], args: _P = ...) -> _Callback: ... | ||||||||
def on_right_click(callback: Callable[..., None], args: _P = ...) -> _Callback: ... | ||||||||
def on_middle_click(callback: Callable[..., None], args: _P = ...) -> _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: _Callback) -> _Callback: ... | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use a bound TypeVar to signify that the exact same type is returned. |
||||||||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little excessive. How do we help users by duplicating this whole table at the type level?