What's the correct way to print a line to the bottom of a terminal window that can handle resizing?
import curses
from curses import wrapper
def main(stdscr):
inp = 0
y,x = stdscr.getmaxyx()
stdscr.clear()
stdscr.nodelay(1)
while inp != 48 and inp != 27:
stdscr.addnstr(y-1,0, 'I AM KILL TERMINAL WHEN RESIZE AAAAAAAH', x)
inp = stdscr.getch()
wrapper(main)
Once I resize the terminal to less columns then the length of the string it tries to wrap onto the next line and errors out. I can't see anything in the documentation about disabling wrapping.
I've tried to update my max y,x values before the addstr function.
while inp != 48 and inp != 27:
if (y,x) != stdscr.getmaxyx():
y,x = stdscr.getmaxyx()
stdscr.addnstr(y-1,0, 'I AM KILL TERMINAL WHEN RESIZE AAAAAAAH', x)
inp = stdscr.getch()
I've also tried capturing SIGWINCH
while inp != 48 and inp != 27:
def resize_handler(signum, frame):
stdscr.erase()
stdscr.refresh()
termsize = shutil.get_terminal_size()
curses.resizeterm(termsize[1],termsize[0])
y,x = stdscr.getmaxyx()
signal.signal(signal.SIGWINCH, resize_handler)
stdscr.addnstr(y-1,0, 'I AM KILL TERMINAL WHEN RESIZE AAAAAAAH', x)
inp = stdscr.getch()
But neither of these seem to capture the terminal update early enough.
The correct way to handle SIGWINCH
for the given example is to wrap the stdscr.addnstr
andstdscr.getch
calls in a block of code which redraws the text (limiting the number of characters to the existing terminal size), doing this as often as the terminal is resized.
The problem is that the stdscr.getch
call (actually the C function in ncurses doing the work) is interrupted. That does the refresh
which is performed in the loop in the first example. ncurses' stdscr.getch
should be returning a KEY_RESIZE
from the stdscr.getch
call, which applications can use to tell when to repaint things. (This works except for OpenBSD which omits the feature for non-technical reasons).
Establishing a signal handle prevents ncurses from telling the application that the terminal has resized. Reading the second example, it appears that the ncurses library will still be waiting for input, already having done the refresh that puts the addnstr
text on the screen (from before the first resize).
The discussion Curses and resizing windows shows one pitfall: if the application is not going to read characters, it will never see KEY_RESIZE
. But your example doesn't do that. I'd discard the signal handler (besides getting in the way, it uses signal-unsafe functions which can break python), and change the first example to something like this:
import curses
from curses import wrapper
def main(stdscr):
inp = 0
y,x = stdscr.getmaxyx()
stdscr.clear()
stdscr.nodelay(1)
while inp != 48 and inp != 27:
while True:
try:
stdscr.addnstr(y-1,0, 'I AM KILL TERMINAL WHEN RESIZE AAAAAAAH', x)
except curses.error:
pass
inp = stdscr.getch()
if inp != curses.KEY_RESIZE:
break
stdscr.erase()
y,x = stdscr.getmaxyx()
wrapper(main)