Reputation: 8870
I'm playing around a little with Python and curses.
When I run
import time
import curses
def main():
curses.initscr()
curses.cbreak()
for i in range(3):
time.sleep(1)
curses.flash()
pass
print( "Hello World" )
curses.endwin()
if __name__ == '__main__':
main()
if I wait all the way through, curses.endwin()
gets called so everything works out fine.
However, if I cut it short with Ctrl-C, curses.endwin()
never gets called so it screws up my terminal session.
What is the proper way to handle this situation? How can I make sure that no matter how I try to end/interrupt the program (e.g. Ctrl-C, Ctrl-Z), it doesn't mess up the terminal?
Upvotes: 12
Views: 12647
Reputation: 17500
My advice: For testing purposes, call your script using a simple wrapper shell script; have the shell script perform a reset
command to bring your terminal settings back into a usable state:
#!/bin/sh
eval "$@"
stty -sane
reset
... call that as run.sh
and be happy. This should run your command almost exactly as your shell would if you entered the arguments as a command (more exactly if you wrap the arguments in hard quotes).
To ensure that your program will leave the terminal in a robust state, in the the face of uncaught exceptions and abnormal terminations ... either use the curses.wrapper()
method to call your top level entry point (probably main()
or whatever main_curses_ui()
you choose to implement) or wrap your code in your own sequence of curses.*
methods to restore cursor visibility, restore "cbreak" (canonical/cooked input) mode, restore the normal "echo" settings and whatever else you may have mucked with.
You can also use the Python: atexit Handlers to register all your clean-up actions. But there might still be cases where your code doesn't get called --- some sorts of non-catchable signals and any situation where os._exit() is invoked.
My little shell script wrapper should be fairly robust even in those cases.
Upvotes: 3
Reputation: 669
I believe you are looking for curses.wrapper See http://docs.python.org/dev/library/curses.html#curses.wrapper
It will do curses.cbreak(), curses.noecho() and curses_screen.keypad(1) on init and reverse them on exit, even if the exit was an exception.
Your program goes as a function to the wrapper, example:
def main(screen):
"""screen is a curses screen passed from the wrapper"""
...
if __name__ == '__main__':
curses.wrapper(main)
Upvotes: 47
Reputation: 526473
You can:
try
/finally
block that calls curses.endwin()
signal
libraryatexit
library.The first option is probably the simplest for a basic case (if you're not running much code).
The second option is the most specific, if you want to do something special for Ctrl+C.
The last option is the most robust if you always want to do certain shutdown actions, no matter how your program is ending.
Upvotes: 1
Reputation: 22728
You need to capture the signal and run endwin()
during the capture.
For info on this, look at this SO answer: How do I capture SIGINT in Python?
Upvotes: -1
Reputation: 117641
You could do this:
def main():
curses.initscr()
try:
curses.cbreak()
for i in range(3):
time.sleep(1)
curses.flash()
pass
print( "Hello World" )
finally:
curses.endwin()
Or more nicely, make a context wrapper:
class CursesWindow(object):
def __enter__(self):
curses.initscr()
def __exit__(self):
curses.endwin()
def main():
with CursesWindow():
curses.cbreak()
for i in range(3):
time.sleep(1)
curses.flash()
pass
print( "Hello World" )
Upvotes: 7