pfnuesel
pfnuesel

Reputation: 15320

Use curses to detect user input immediately

I'm using curses in python2.7 to control a robot. I would like to steer it with e.g. the w key, to tell it it should go forward. Here's my code, stripped away of all robot controls:

import curses
from time import sleep

if __name__ == "__main__":
    shell = curses.initscr()
    shell.nodelay(True)

    while True:
        key = shell.getch()

        if key == 119:
            print("key w pressed")

        sleep(0.03)

That works fine, except that I have to press enter for the key to be recognized. So I can press w several times, and when I press enter, the robot does exactly what it's supposed to do, or in this example, the text key w pressed appears as many times as I have pressed it. But I would like this to happen immediately, i.e. without having to press enter. How can this be achieved?

Upvotes: 2

Views: 4476

Answers (3)

ojklan
ojklan

Reputation: 21

The existing answers are correct, cbreak() will switch off buffered input mode, meaning getch() will return a key press immediately.

I suggest using the curses.wrapper() function. This sets up the screen, and buffering, how you want it in this case. But it also catches exceptions and makes sure the terminal is returned to a clean state when your application exits. Read about it here

An example based on the OPs code would be:

import curses
from time import sleep

def program_loop(stdscr):
    while True:
        key = stdscr.getch()


        if key == 119:
            print("key w pressed\r")

        sleep(0.03)

if __name__ == "__main__":
    curses.wrapper(program_loop)

Upvotes: 2

Thomas Dickey
Thomas Dickey

Reputation: 54515

Given that OP did not use window.keypad, calling either curses.raw or curses.cbreak would give (almost) the same result for allowing the script to read unbuffered characters.

Calling curses.endwin() is a more reliable way to restore things. Bear in mind that it is a wrapper around curses. curses simulates cbreak mode, setting the real terminal mode to raw. Calling curses.nocbreak has no effect on the real terminal mode.

If you call nocbreak to cleanup rather than call endwin, you will have additional problems to resolve (see Clean up ncurses mess in terminal after a crash for instance), because the terminal will be in raw mode (including real noecho, which makes typing stty sane^M painful).

Referring to the documentation for endwin,

A program should always call endwin before exiting or escaping from curses mode temporarily. This routine

   o   restores tty modes,

   o   moves the cursor to the lower left-hand corner of  the
       screen and

   o   resets the terminal into the proper non-visual mode. 

As noted, cbreak and raw are almost the same. The main difference lies in how the program would respond to keyboard interrupts (things like ^C). If you happen to be using ncurses, it establishes signal handlers which (for SIGINT and SIGTERM) will call curses.endwin. Other curses implementations do not do this, and even if your script calls curses.endwin it is not certain that it will work as intended (since curses tends to call unsafe stream I/O functions which cannot be used reliably in a signal handler).

Upvotes: 1

meuh
meuh

Reputation: 12255

add curses.cbreak() to your setup, and when you cleanup call curses.nocbreak() to restore the terminal to a usable state.

You can do the cleanup by catching ctrl-c as an exception. Eg:

 try:
     curses.cbreak()
     while True:
            key = shell.getch()
            if key == 119:
                print("key w pressed")
            sleep(0.03)
 except KeyboardInterrupt:
     curses.nocbreak()

Upvotes: 1

Related Questions