I'm trying to disable stdin buffering, in order to read the response of ANSI code \033[6n
(which should report the cursor position).
I tried stdin_ub = os.fdopen(stdin.fileno(), 'rb', buffering=0)
as suggested in answer Setting smaller buffer size for sys.stdin?, but still the program is blocked at line ch = stdin_ub.read(1)
of the first attempt to read. It unblocks when return is typed into the terminal, which suggests the stdin is still line buffered.
For reference, here's the complete code:
def getpos():
stdin_ub = os.fdopen(sys.stdin.fileno(), 'rb', buffering=0)
sys.stdout.write('\033[6n')
sys.stdout.flush()
ch, k, field = None, -1, [b'', b'']
while True:
#print('reading wait...')
ch = stdin_ub.read(1)
#print('reading OK')
if ch == b'[': k = 0
elif ch == b';': k = 1
elif ch == b'R': break
elif k >= 0: field[k] += ch
try:
return tuple(map(int, field))
except:
pass
I'm using python 3.5.1
The trick is to use tty.setcbreak(sys.stdin.fileno(), termios.TCSANOW)
and before that store the terminal attributes via termios.getattr
in variable to restore the default behavior. With cbreak
set, sys.stdin.read(1)
is unbuffered. This also suppress the ansi controll code response from the terminal.
def getpos():
buf = ""
stdin = sys.stdin.fileno()
tattr = termios.tcgetattr(stdin)
try:
tty.setcbreak(stdin, termios.TCSANOW)
sys.stdout.write("\x1b[6n")
sys.stdout.flush()
while True:
buf += sys.stdin.read(1)
if buf[-1] == "R":
break
finally:
termios.tcsetattr(stdin, termios.TCSANOW, tattr)
# reading the actual values, but what if a keystroke appears while reading
# from stdin? As dirty work around, getpos() returns if this fails: None
try:
matches = re.match(r"^\x1b\[(\d*);(\d*)R", buf)
groups = matches.groups()
except AttributeError:
return None
return (int(groups[0]), int(groups[1]))
Unfortunately, there is no portable way to do that. The underlying IO system is line buffered when reading from keyboard on common OS, for example Windows and Unix families.
The curses module would offer an almost portable way to control the line discipline, unfortunately it does not work on windows systems.
If you can use it, you will have to use
curses.noecho()
curses.raw() # or curses.cbreak()
to enter raw mode (generally echo should be set off)
and
curses.echo()
curses.noraw() # resp. curses.nocbreak()
to return to normal cooked more
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With