Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python tty.setraw Ctrl + C doesn't work (getch)

Tags:

python

getch

This is my code.

def getch():
    fd = sys.stdin.fileno()
    old_Settings = termios.tcgetattr(fd)
    try: 
        tty.setraw(sys.stdin.fileno())
        sys.stdin.flush()
    except KeyboardInterrupt:
        print ("[Error] Abnormal program termination")
    finally:
        termios.tcsetattr(fd,termios.TCSADRAIN,old_settings)
    return ch

In this code, KeyboardInterrupt exception doesn't work.

Thank you

like image 544
SoosanShin Avatar asked Jul 25 '18 00:07

SoosanShin


1 Answers

Yes, when you set your TTY to raw mode—or, in particular, when you disable the termios setting ISIG—you no longer get the corresponding behavior:

  ISIG   When any of the characters INTR, QUIT, SUSP, or DSUSP are
         received, generate the corresponding signal.

In other words, the character INTR, aka ^C in most terminals, no longer generates a SIGINT signal, which is what Python uses to raise a KeyboardInterrupt.

This is how full-screen programs like emacs can use ^C as a control character instead of just quitting.

So, there are two options:


Instead of calling setraw, set the exact set of termios flags you want, making sure not to disable ISIG.

It's worth looking at the source to Python's tty.setraw to see exactly what it does, to use as a baseline.

You could just do exactly the same thing without the ISIG. But really, you should read the docs to learn what each of these things means.

And make sure to man termios on your own system, don't search online; things are a bit different on Linux vs. macOS/*BSD, and there are minor differences even between versions of the same OS.

Even after reading the docs, and experimenting on your own, it may not be entirely clear what effect every setting has. This article seems to do a decent job explaining things what goes into raw mode, and what each of the relevant flags actually means (although I only quickly skimmed it—and. again, you still need to read man termios for your platform).


Alternatively, you can just handle ^C manually.

You're already reading a character at a time with sys.stdin.read(1), so when someone hits ^C, you're going to read a '\x03' character.

You can do whatever you want when that happens, including raise KeyboardInterrupt.


As a side note, if this is Python 3, you may want to read in binary mode:

ch = sys.stdin.buffer.raw.read(1)
sys.stdin.flush()

(Then you'll check against b'\x03' instead of '\x03'. of course.)

In text mode, you should get a whole character at a time, even if it's a multibyte character in UTF-8 (or whatever your locale is). Which seems simpler at first—but that can actually be misleading, because a single character can still be only part of a keystroke. For example, with most terminals, an up arrow sends 3 characters ('\x1B[A'), butread(1)` will just give you the first one.

like image 71
abarnert Avatar answered Nov 09 '22 23:11

abarnert