Skip to content

Commit 6caa67c

Browse files
committed
posix: wrap stdin in a cutom buffer
select() is not reliable to as an alternative to kbhit(), it only works one time an will than returne false untill the next key-press by the user. Now the content of stdin is read completly and put in a custom buffer, which allows for peeking of data.
1 parent 73230f1 commit 6caa67c

File tree

2 files changed

+30
-11
lines changed

2 files changed

+30
-11
lines changed

readchar/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Library to easily read single chars and key strokes"""
22

3-
__version__ = "4.0.4-dev1"
3+
__version__ = "4.0.4-dev2"
44
__all__ = ["readchar", "readkey", "key", "config"]
55

66
from sys import platform

readchar/_posix_read.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22
import termios
33
from copy import copy
4+
from io import StringIO
45
from select import select
56

67
from ._config import config
@@ -12,53 +13,71 @@ class ReadChar:
1213

1314
def __init__(self, cfg: config = None) -> None:
1415
self.config = cfg if cfg is not None else config
16+
self._buffer = StringIO()
1517

1618
def __enter__(self) -> "ReadChar":
1719
self.fd = sys.stdin.fileno()
1820
term = termios.tcgetattr(self.fd)
1921
self.old_settings = copy(term)
20-
term[3] &= ~(termios.ICANON | termios.ECHO | termios.IGNBRK | termios.BRKINT)
22+
23+
term[3] &= ~(
24+
termios.ICANON # don't require ENTER
25+
| termios.ECHO # don't echo
26+
| termios.IGNBRK
27+
| termios.BRKINT
28+
)
29+
term[6][termios.VMIN] = 0 # imideatly process every input
30+
term[6][termios.VTIME] = 0
2131
termios.tcsetattr(self.fd, termios.TCSAFLUSH, term)
2232
return self
2333

2434
def __exit__(self, type, value, traceback) -> None:
25-
termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings)
35+
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_settings)
36+
37+
def __update(self) -> None:
38+
"""check stdin and update the interal buffer if it holds data"""
39+
if sys.stdin in select([sys.stdin], [], [], 0)[0]:
40+
pos = self._buffer.tell()
41+
data = sys.stdin.read()
42+
self._buffer.write(data)
43+
self._buffer.seek(pos)
2644

2745
@property
2846
def key_waiting(self) -> bool:
2947
"""True if a key has been pressed and is waiting to be read. False if not."""
30-
return sys.stdin in select([sys.stdin], [], [], 0)[0]
48+
self.__update()
49+
pos = self._buffer.tell()
50+
next_byte = self._buffer.read(1)
51+
self._buffer.seek(pos)
52+
return bool(next_byte)
3153

3254
def char(self) -> str:
3355
"""Reads a singel char from the input stream and returns it as a string of
3456
length one. Does not require the user to press ENTER."""
35-
return sys.stdin.read(1)
57+
self.__update()
58+
return self._buffer.read(1)
3659

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

67+
c1 = self.char()
4468
if c1 in self.config.INTERRUPT_KEYS:
4569
raise KeyboardInterrupt
46-
4770
if c1 != "\x1B":
4871
return c1
49-
5072
c2 = self.char()
5173
if c2 not in "\x4F\x5B":
5274
return c1 + c2
53-
5475
c3 = self.char()
5576
if c3 not in "\x31\x32\x33\x35\x36":
5677
return c1 + c2 + c3
57-
5878
c4 = self.char()
5979
if c4 not in "\x30\x31\x33\x34\x35\x37\x38\x39":
6080
return c1 + c2 + c3 + c4
61-
6281
c5 = self.char()
6382
return c1 + c2 + c3 + c4 + c5
6483

0 commit comments

Comments
 (0)