import sys
import threading
import tty
import termios
def loop():
fd = sys.stdin.fileno()
mode = termios.tcgetattr(fd)
tty.setraw(fd)
try:
while True:
sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, mode)
threading.Thread(target=loop).start()
I tried to manually send End-Of-Transmission character using sys.stdin.write(chr(4)) but it results in io.UnsupportedOperation: not writable.
I tried to close stdin but it will not terminate blocking read(1) method immediately. It triggers an exception after I typed another key.
I tried seletors but selector.select() need extra keys after I invoked selector.close(). And selector it self needs an event loop and this loop becomes uninterruptable.
If you run the loop in a thread then it will be hard to interrupt and also keep original process running. But it's easy to run this loop in a subprocess. So you can interrupt the subprocess to interrupt the blocking stdin.read(1)
import os
import sys
import tty
import termios
import time
import signal
from multiprocessing import Process
def loop():
# https://stackoverflow.com/questions/30134297/python-multiprocessing-stdin-input
# Subprocess stdin will be set to os.devnull. So we need to reopen it here.
# Use parent process stdin(fd=0) instead of subprocess stdin(fd=7)
sys.stdin = open(0) #
fd = sys.stdin.fileno()
mode = termios.tcgetattr(fd)
tty.setraw(fd)
try:
while True:
ch = sys.stdin.read(1)
if ord(ch) == 3:
break
except KeyboardInterrupt:
print("interrupted!\r\n", end="")
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, mode)
if __name__ == '__main__':
p = Process(target=loop)
p.start()
time.sleep(1)
os.kill(p.pid, signal.SIGINT)
p.join()
print("done")
Related
I have a very weird print() bug while using Threads and Termios. I have a repeating Thread catching a key via Termios while printing some stuff. But always it prints a new line it doesn't start at the beginning of the line but where the last line ended.
This is my code:
def func1():
while True:
try:
var = int(inputChar())
except ValueError:
var = 0
Thread(target=func1).start()
while True:
print("stuff")
time.sleep(2)
This is my inputChar() function:
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
This ist the expected output:
stuff
stuff
stuff
stuff
stuff
This is the output:
stuff
stuff
stuff
stuff
stuff
I have no idea why this is happening but you can fix it by replacing the print command with
print("stuff\r")
or
sys.stdout.write("stuff\n\r")
the \r at the end is known as a cartridge return. It's useful when sys.stdout.write() is not a viable option
I saw this example that launches bash thru subprocess where asynchronous read/write is achieved. However I wonder if there is a way to preserve this behavior and capture a keyboard interrupt.
Run interactive Bash with popen and a dedicated TTY Python
This is my version of the code. The issue is python code is not INTERRUPTED.
import subprocess
import sys
import os
import signal
import atexit
import select
import termios
import tty
import pty
def execute(command):
old_tty = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin.fileno())
master_fd, slave_fd = pty.openpty()
process = subprocess.Popen(command,
preexec_fn=os.setsid,
stdin=slave_fd,
stdout=slave_fd,
stderr=slave_fd,
universal_newlines=True)
global child_pid
child_pid = process.pid
while process.poll() is None:
r, w, e = select.select([sys.stdin, master_fd], [], [])
if sys.stdin in r:
d = os.read(sys.stdin.fileno(), 10240)
os.write(master_fd, d)
elif master_fd in r:
o = os.read(master_fd, 10240)
if o:
os.write(sys.stdout.fileno(), o)
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
def exit_protocol():
global child_pid
if child_pid is None:
pass
else:
print "Shutting Down bash", child_pid
atexit.register(exit_protocol)
try:
execute('bash')
except KeyboardInterrupt as exc:
print "Clean up all bash's child jobs"
I'm trying to combine the getch and progressbar Python modules on Linux, but I cannot get it to work. I want to use getch to listen for keyboard input to interrupt the progress bar, but when I insert the getch statement, the progress bar refuses to update automatically, only updating when I press a button on the keyboard.
The code I'm currently using is below. I'm using ProgressBar2 and the getch module, but I've tried using tqdm and my own getch method with no avail.
bar = progressbar.ProgressBar()
for i in range(101):
sleep(0.01)
bar.update(i)
ch = getch.getch()
When using my own implementation of getch, I've narrowed down the problem to be with the 'sys.stdin.read(1)' line in the following code.
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno(), termios.TCSADRAIN)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
On Windows, using the msvcrt module, I have no problems whatsoever.
I've had the same issue on Linux, so I opted for a different solution: capture the SIGINT signal (CTRL+C) to exit gracefully, or immediately after two presses (the regular SIGINT way).
import signal
import time
signal.signal(signal.SIGINT, signal_handler)
sigint_again = False
asked_termination = False
def signal_handler(self, signal, frame):
"""Handles SIGINT signal, blocks it to terminate gracefully"""
print('You pressed Ctrl+C!:', signal, frame)
if is_sigint_called_twice():
print("\nForced terminating script!")
sys.exit(0)
asked_termination = True
def is_sigint_called_twice(self):
"""Check if pressing ctrl+c a second time to terminate immediately"""
if not sigint_again:
sigint_again = True
return False
else:
return True
while not asked_termination:
print("do_stuff()")
time.sleep(1)
I have been trying to write a keyboard listener without installing any packages. What I wanted was to create a non-blocking way of reading only one character of user input. So I created another thread besides the main one. Here is my code:
import sys, os
import thread
import time
try:
from msvcrt import getch
except ImportError:
def getch():
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
char = None;
def key_listener():
global char;
while True:
char = getch()
# escape key to exit
if ord(char) == 27:
break
#print char #comment this line for test
thread.start_new_thread(key_listener, ())
while True:
print("Whatever")
time.sleep(1);
And the printed strings are a bit weird:
Yinans-MacBook-Pro:anaconda game Yinan$ python inputtest.py
Whatever
Whatever
Whatever
Whatever
Whatever
Whatever
See those indents? I never expected to have that. And I have been trying a whole afternoon to solve it but failed. Does anybody know how to solve this? I will be so grateful. (btw I'm using a macbook pro.)
Putting STDIN in raw mode put STDOUT in raw mode as well, so the normal \n is not expanded to a CRLF. You will need to print a \r at the end of your string in order to return the cursor to the first column.
Any time I use the recipe at http://code.activestate.com/recipes/134892/ I can't seem to get it working. It always throws the following error:
Traceback (most recent call last):
...
old_settings = termios.tcgetattr(fd)
termios.error: (22, 'Invalid argument)
My best thought is that it is because I'm running it in Eclipse so termios is throwing a fit about the file descriptor.
This is working on Ubuntu 8.04.1 , Python 2.5.2, i get no such error. May be you should try it from command line, eclipse may be using its own stdin, i get exact same error if I run it from Wing IDE, but from command line it works great.
Reason is that IDE e.g Wing is using there own class netserver.CDbgInputStream as sys.stdin
so sys.stdin.fileno is zero, thats why the error.
Basically IDE stdin is not a tty (print sys.stdin.isatty() is False)
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _GetchUnix()
print getch()
Putting terminal into raw mode isn't always a good idea. Actually it's enough to clear ICANON bit. Here is another version of getch() with timeout support:
import tty, sys, termios
import select
def setup_term(fd, when=termios.TCSAFLUSH):
mode = termios.tcgetattr(fd)
mode[tty.LFLAG] = mode[tty.LFLAG] & ~(termios.ECHO | termios.ICANON)
termios.tcsetattr(fd, when, mode)
def getch(timeout=None):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
setup_term(fd)
try:
rw, wl, xl = select.select([fd], [], [], timeout)
except select.error:
return
if rw:
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
if __name__ == "__main__":
print getch()