0

If one googles backspace python stdin or so, there are a lot of SO results and none that solve my problem. They are all about what the backspace char is not how to get it.


Here's a function that reads a single keypress from stdin, courtesy https://stackoverflow.com/a/6599441/4532996:

def read_single_keypress():
    import termios, fcntl, sys, os
    fd = sys.stdin.fileno()
    # save old state
    flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
    attrs_save = termios.tcgetattr(fd)
    # make raw - the way to do this comes from the termios(3) man page.
    attrs = list(attrs_save) # copy the stored version to update
    # iflag
    attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
                  | termios.ISTRIP | termios.INLCR | termios. IGNCR
                  | termios.ICRNL | termios.IXON )
    # oflag
    attrs[1] &= ~termios.OPOST
    # cflag
    attrs[2] &= ~(termios.CSIZE | termios. PARENB)
    attrs[2] |= termios.CS8
    # lflag
    attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
                  | termios.ISIG | termios.IEXTEN)
    termios.tcsetattr(fd, termios.TCSANOW, attrs)
    # turn off non-blocking
    fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
    # read a single keystroke
    try:
        ret = sys.stdin.read(1) # returns a single character
    except KeyboardInterrupt:
        ret = 0
    finally:
        # restore old state
        termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
        fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
    return ret

Hacky as it is, it would appear to be cross-platform.

Implementing that is a utility function from my module:

def until(char) -> str:
    """get chars of stdin until char is read"""
    import sys
    y = ""
    sys.stdout.flush()
    while True:
        i = read_single_keypress()
        _ = sys.stdout.write(i)
        sys.stdout.flush()
        if i == char or i == 0:
            break
        y += i
    return y

Which works really well, except pressing backspace does nothing, and you can't move the cursor (which import readline; input() allows you to (at least on a Python built with GNU Readline)).

I understand the fact that the best way to implement both of these is probably curses. I also understand that curses defeats the standard-libraryness and cross-platformness of this module.

What I'm looking for is a way to read stdin in a way that will capture backspace, and, for a special bonus, DEL and preferably the arrow keys too.

The module targets Pythons both 2 and 3, but I'm okay with a solution that targets just Python 3, because people really need to stop using 2.


If you think I'm flaming mad for wanting to do this without curses, well, that's the point.

Community
  • 1
  • 1
cat
  • 3,888
  • 5
  • 32
  • 61
  • Many people prefer dogs. – Farseer Jan 31 '16 at 22:48
  • You can do this with PyQt or PySide by overriding the keyPressEvent, but I'm guessing you don't want to be dependent on a GUI library. – justengel Jan 31 '16 at 22:54
  • @HashSplat depending upon a GUI lib would be even less desirable than `curses`. – cat Jan 31 '16 at 22:55
  • @dawg sorry, that's the exact question I got the code snippet from, and it doesn't solve my problem, so no dupe – cat Jan 31 '16 at 23:31
  • But you used a different piece of code. Did you try the answer I liked to? At least on my Mac -- that works. – dawg Jan 31 '16 at 23:48
  • @dawg last time I tried that piece of code it didn't do what I wanted, so I disregarded it. Now, it works, for whatever reason. You can post that as an answer, I guess – cat Jan 31 '16 at 23:55

2 Answers2

1

Consider using this recipe from ActiveState that is also this SO answer:

class _Getch:
    """Gets a single character from standard input.  Does not echo to the
screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self): return self.impl()
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


class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


getch = _Getch()
# then call getch() to get the input...
Community
  • 1
  • 1
dawg
  • 98,345
  • 23
  • 131
  • 206
-1

The terminal driver in raw mode does allow one to read BS ^H and DEL characters. Arrow keys tend to be ESC sequences, not one single byte unless you have something like a real Wyse terminal from the 80's. It would not be expected for GNU readline features like arrow keys to work in raw mode which is saying don't do any character processing.

Developing a function key mapper was necessary to support ANSI/DEC VT100 style terminals for an application written which made the assumption 1 key = 1 input byte, effectively using Wyse terminal control characters as internal commands.

Similarly on output via write, unless the input bytes read are transformed, perhaps by pipe into od(1), how are you going to see non-printing characters?

The curses library is more about screen drawing and efficient updates, it uses raw mode and a getch() routine to allow menu options and so on without hitting return. It is not necessary if you are simply reading keyboard input.

Rob11311
  • 1,396
  • 8
  • 10
  • I've read this a few times and I still have no idea what your point is or how it answers my question. The way you'd "see" nonprintables is by their effect, i.e., arrow key moves the cursor (see: `readline`), backspace moves the cursor left & writes a blank at that point – cat Jan 31 '16 at 23:21
  • You're wrong, what you are asking for in raw mode is a byte 0..255 and it is THAT value which you detect, in raw mode terminal line processing is turned off. So for example I am explaining the implicit assumption, that an arrow key is one character is incorrect, it's an ESC sequence, multiple bytes. It's your model of how terminal processing works that is incorrecct. – Rob11311 Feb 01 '16 at 01:20
  • Okay, but I implemented backspace and arrow keys anyways, so it's okay https://github.com/catb0t/input_constrain – cat Feb 01 '16 at 01:37