Kimomaru
Kimomaru

Reputation: 1023

Preventing key presses from appearing on screen

I'm working on some basic animations for python cli interfaces that appear when the script is running. This is a problem I have with pretty much every script I've written. If I'm executing the following animation;

def animatedSpinner(*arg):
    animation = ["|","/","-","\\"]
    a = 0
    while True:
        print(animation[a % len(animation)], end="\r")
        a += 1
        time.sleep(0.1)

It runs fine, but any key presses a user makes while it's running shows up on the screen. How can I prevent key presses from appearing on screen during an animation or any time a functioning is running?

Upvotes: 5

Views: 504

Answers (1)

abarnert
abarnert

Reputation: 365707

The ways to do this on POSIX and Windows are so different that, unless you want to search for a very high-level wrapper library (which do exist—some of them are lowest-common-denominator but probably have enough functionality for this, while others are very complicated), you might as well consider them separate problems. Since you said POSIX is what you really care about, I'll explain that.


The right way to do this is with termios. But this can be a bit hairy for beginners, so I'll come back to that at the end.

If you want a quick&dirty solution, you can just call out to the stty tool:

import subprocess

def echooff():
    subprocess.run(['stty', '-echo'], check=True)
def echoon():
    subprocess.run(['stty', 'echo'], check=True)

Whichever way you do it, make sure that you always call echoon before exit, no matter what. Otherwise, you'll leave the console in non-echoing mode, and your user (or you) will have to blindly do a reset or stty echo.

For example, in your main code:

try:
    echooff()
    # do stuff
finally:
    echoon()

Or, better, using contextlib:

@contextlib.contextmanager
def echo_disabled():
    try:
        echooff()
        yield
    finally:
        echoon()

Then:

with echo_disabled():
    # do stuff    

The tty module's source code is good sample code for getting started—but in this case, what you're doing is pretty close to the example right there in the termios docs:

@contextlib.contextmanager
def echo_disabled():
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    new = termios.tcgetattr(fd)
    new[3] = new[3] & ~termios.ECHO          # lflags
    try:
        yield
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old)

Upvotes: 4

Related Questions