Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion readchar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Library to easily read single chars and key strokes"""

__version__ = "4.0.4-dev0"
__version__ = "4.0.4-dev1"
__all__ = ["readchar", "readkey", "key", "config"]

from sys import platform
Expand Down
59 changes: 59 additions & 0 deletions readchar/_posix_read.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,68 @@
import sys
import termios
from copy import copy
from select import select

from ._config import config


class ReadChar:
"""A ContextManager allowing for keypress collection without requiering the user to
confirm presses with ENTER. Can be used non-blocking while inside the context."""

def __init__(self, cfg: config = None) -> None:
self.config = cfg if cfg is not None else config

def __enter__(self) -> "ReadChar":
self.fd = sys.stdin.fileno()
term = termios.tcgetattr(self.fd)
self.old_settings = copy(term)
term[3] &= ~(termios.ICANON | termios.ECHO | termios.IGNBRK | termios.BRKINT)
termios.tcsetattr(self.fd, termios.TCSAFLUSH, term)
return self

def __exit__(self, type, value, traceback) -> None:
termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings)

@property
def key_waiting(self) -> bool:
Comment thread
Cube707 marked this conversation as resolved.
"""True if a key has been pressed and is waiting to be read. False if not."""
return sys.stdin in select([sys.stdin], [], [], 0)[0]

def char(self) -> str:
"""Reads a singel char from the input stream and returns it as a string of
length one. Does not require the user to press ENTER."""
return sys.stdin.read(1)

def key(self) -> str:
"""Reads a keypress from the input stream and returns it as a string. Keypressed
consisting of multiple characterrs will be read completly and be returned as a
string matching the definitions in `key.py`.
Does not require the user to press ENTER."""
c1 = self.char()

if c1 in self.config.INTERRUPT_KEYS:
raise KeyboardInterrupt

if c1 != "\x1B":
return c1

c2 = self.char()
if c2 not in "\x4F\x5B":
return c1 + c2

c3 = self.char()
if c3 not in "\x31\x32\x33\x35\x36":
return c1 + c2 + c3

c4 = self.char()
if c4 not in "\x30\x31\x33\x34\x35\x37\x38\x39":
return c1 + c2 + c3 + c4

c5 = self.char()
return c1 + c2 + c3 + c4 + c5


# Initially taken from:
# http://code.activestate.com/recipes/134892/
# Thanks to Danny Yoo
Expand Down
52 changes: 52 additions & 0 deletions readchar/_win_read.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,60 @@
import msvcrt
import signal

from . import _win_key as key
from ._config import config


class ReadChar:
"""A ContextManager allowing for keypress collection without requiering the user to
confirm presses with ENTER. Can be used non-blocking while inside the context."""

@staticmethod
def __silent_CTRL_C_callback(signum, frame):
msvcrt.ungetch(key.CTRL_C.encode("ascii"))

def __init__(self, cfg: config = None) -> None:
self.config = cfg if cfg is not None else config

def __enter__(self) -> "ReadChar":
self.__org_SIGBREAK_handler = signal.getsignal(signal.SIGBREAK)
self.__org_SIGINT_handler = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGBREAK, signal.default_int_handler)
signal.signal(signal.SIGINT, ReadChar.__silent_CTRL_C_callback)
return self

def __exit__(self, type, value, traceback) -> None:
signal.signal(signal.SIGBREAK, self.__org_SIGBREAK_handler)
signal.signal(signal.SIGINT, self.__org_SIGINT_handler)

@property
def key_waiting(self) -> bool:
"""True if a key has been pressed and is waiting to be read. False if not."""
return msvcrt.kbhit()

def char(self) -> str:
"""Reads a singel char from the input stream and returns it as a string of
length one. Does not require the user to press ENTER."""
return msvcrt.getch().decode("latin1")

def key(self) -> str:
"""Reads a keypress from the input stream and returns it as a string. Keypressed
consisting of multiple characterrs will be read completly and be returned as a
string matching the definitions in `key.py`.
Does not require the user to press ENTER."""
c = self.char()

if c in self.config.INTERRUPT_KEYS:
raise KeyboardInterrupt

# if it is a normal character:
if c not in "\x00\xe0":
return c

# if it is a special key, read second half:
return "\x00" + self.char()


def readchar() -> str:
"""Reads a single character from the input stream.
Blocks until a character is available."""
Expand Down