lc2817
lc2817

Reputation: 3742

curses.wrapper() messing up terminal after background/foreground sequence

I am investigating a bug where curses.wrapper does not restore the terminal properly. The issue is shown after a backgrounding/foregrounding sequence.

Consider the following python program saved in myprogram.py:

import curses, subprocess

# Function that does nothing
def f(*args, **kwargs):
    pass

curses.wrapper(f)

# Call vi to open a file 
subprocess.call("vi /tmp/foo", shell=True)

Steps to repro the issue:

  1. Run the program: python myprogram.py
  2. It starts vi editing the file /tmp/foo
  3. When I hit ctrl-z it brings me back to my shell
  4. When I resume the program with fg
  5. It restarts the editor but the screen is wrong (all black and the editor is not drawn)

Removing the curses.wrapper(f) line makes the program works: the editor is drawn properly when the program is resumed.

I tried multiple things, like replacing the call to curses.wrapper(f) by what it actually does and, the most minimal example (i.e. calling initscr, endwin) leads also to the same issue.

I am running:

What am I missing?

Upvotes: 3

Views: 1430

Answers (2)

Thomas Dickey
Thomas Dickey

Reputation: 54563

The source-code for curses.wrapper does nothing special with signals.

During initialization (such as a call to initscr), the ncurses library adds handlers for these signals: SIGINT, SIGTERM, SIGTSTP, SIGWINCH. For whatever reason (likely because it is an internal detail not directly visible to callers), this is documented mainly in the NEWS file.

Applications which need to add their own signal handler should do this after ncurses' initialization (since ncurses does this only once). Because curses applications can be switched to/from screen-mode, the signal handlers are left active until the program quits. For instance, it would be possible for a Python script to call curses.wrapper more than once (although it probably would not work correctly except with ncurses -- X/Open says that "portable applications must not call initscr more than once").

Saving and restoring the signal handler state as suggested by @lc2817 will work—but it is a workaround because it is not elegant. If curses.wrapper were modified to add some state to it, to remember if it was called before, and to save/restore the signal-handlers, the workaround would be unnecessary. To make it really portable, initscr should be called on the first use, and refresh on subsequent uses.

Upvotes: 3

lc2817
lc2817

Reputation: 3742

This happens to be a bug in curses.wrapper or anything underneath that forgets to restore signal handler to their previous values. This fixes it:

import curses, subprocess
import signal

# Function that does nothing
def f(*args, **kwargs):
    pass

a = signal.getsignal(signal.SIGTSTP)

curses.wrapper(f)

signal.signal(signal.SIGTSTP, a)

# Call vi to open a file 
subprocess.call("vi /tmp/oo", shell=True)

Upvotes: 3

Related Questions