Search code examples
pythongetchmintty

How can I make msvcrt.getch() behave the same way in mintty as in cmd?


I'm trying to invoke functions on key press in python. When the script is run in Windows command line (cmd.exe), everything works as expected. But usually when I'm on Windows, I use mintty.

In mintty, the application behaves different. The getch() blocks the application (forever).

import msvcrt

print("press a key")
char = msvcrt.getch()
print(f"you pressed: {char}")

In Windows command line:

press a key

(I press the a key)

you pressed: b'a'

(application closed as it should)

In mintty:

press a key

(i press a multiple times)

aaaaaaaaaaaaaa

(nothing happens)


Solution

  • The short answer is "ensure the windows python executable call is wrapped in a winpty call".

    I have a similar challenge, where I expect to support a fully interactive console application which uses termios in unixey environments and msvcrt in Windows.

    • it works in linux & mac,
    • it works in the windows cmd console,
    • it works in funky windows bash terminals like the VS Code one,
    • it works in "pure" msys2 (where python is installed via pacman, compiled to run in mintty, so supporting termios), but
    • it fails in "Git for Windows"'s mintty bash window, unless the caller adds "winpty" before the command... and when it fails, it fails big! (can't easily get out of the broken console app, as reading keys isn't working)

    The biggest remaining challenge I see here is detecting that failure.

    So far, I have conditions along the following:

    • If msvcrt was loaded (we're running in windows python)
    • And os.environ.get('TERM_PROGRAM') == "mintty" (mintty was the console)
    • And os.environ.get('TERM') == "xterm" (winpty hasn't changed the terminal type from mintty's xterm default)
    • Then raise an exception asking the user to call with winpty

    This relies on a strange series of coincidences on my system:

    • mintty's default "TERM" setting is "xterm" (but configurable!)
    • the winpty bundled with a recent git resets this to None (in python terms)
    • the winpty bundled with msys2 resets this to "xterm-256color"

    Unfortunately, I don't believe this logic is very reliable - other versions of winpty and mintty may behave differently, and users may have changed the terminal type setting in mintty to something other than "xterm", in which case my script will incorrectrly assume everything is OK.

    I would dearly love a more reliable way to detect when my script is in "python for windows running in mintty without winpty".

    UPDATE: Using Bash can work!

    For what it's worth, if you can rely on having bash available in the path (which, if you're trying to detect mintty from python, is actually quite likely I guess?), then you can:

    • Call a bash script (without piping stdin and stdout, and passing argument -i, so it connects to the terminal, and with)
    • Rely on mintty's Secondary Device Attributes support
    • Using echo -n -e to print the escape sequence
    • Using read with a timeout to capture the tty response
    • Use stderr or exit code to output an outcome to python

    Example bash script:

    echo -n -e '\e[>c';
    read -rs -t 0.2 -d "" <$(tty)
    echo "Look ma! Secondary device attributes are ${REPLY#?} - now if they start with [>77; then I know I'm in mintty."