0

I'm currently playing around with curses and trying to understand how everything reacts. I was thinking I was making progress until I stumbled on this simple piece of code:

import curses

def main(stdscr: curses.window) -> None:
    stdscr.addstr(0, 0, "A")
    # stdscr.refresh()

    win = curses.newwin(2, 2, 1, 0)
    win.addstr(0, 0, "B")
    win.refresh()
    stdscr.getch()
    
curses.wrapper(main)

What I don't understand here is why the result is a screen with only an A, and no B. What I also find intriguing is that if I uncomment the commented line, I get both an A and a B.

Could someone please explain what's happening, or at least point me to some document that explains it.

Thanks in advance!

aureo
  • 35
  • 5

1 Answers1

1

The "B" is actually written to the terminal, but immediately overwritten in the repainting done for the refresh done as a side-effect of stdscr.getch(). The manual page says that getch does that:

If the window is not a pad, and it has been moved or modified since the last call to wrefresh, wrefresh will be called before another character is read.

Initializing curses makes stdscr cleared on the first time it is painted. Again, in the manual page (for initscr):

initscr also causes the first call to refresh(3x) to clear the screen.

I generated this listing using a utility (unmap) which makes everything readable (actually spaces aren't converted), taking the output to the terminal using script (and limiting the display to a 5x5 screen):

Script started on 2021-04-09 19:08:24-04:00 [TERM="screen.xterm-new" TTY="/dev/pts/0" COLUMNS="80" LINES="40"]
\n
\E[?1049h
\E[22;0;0t
\E[1;5r
\E(B
\E[m
\E[4l
\E[?7h
\E[?1h
\E=
\E[39;49m
\E[39;49m
\E[37m
\E[40m
\E[1;1H     
\E[2;1H     
\E[3;1H     
\E[4;1H     
\E[5;1H    
\E[?7l 
\E[?7h
\E[H
\E[2dB
\E[39;49m
\E[37m
\E[40m
\E[H     
\E[2;1H     
\E[3;1H     
\E[4;1H     
\E[5;1H    
\E[?7l 
\E[?7h
\E[HA
\E[?1l
\E>
\E[39;49m\r
\E[5d
\E[K
\E[5;1H
\E[?1049l
\E[23;0;0t\r
\E[?1l
\E>
\nScript done on 2021-04-09 19:08:24-04:00 [COMMAND_EXIT_CODE="0"]
\n

The "B" comes out on this line:

\E[2dB

and the "A" on this:

\E[HA

(the other characters are part of escape sequences).

Uncommenting that line finishes the repainting needed by initscr, and there's no remaining work for stdscr needed in the stdscr.getch() call (so none of the new window is overwritten).

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • This is amazing! Thanks a lot for your very detailed answer! And thank you for introducing me to ncurses' manual, it is far more detailed than python's curses' documentation and I'm sure it will help me dramatically in the future. – aureo Apr 12 '21 at 01:36
  • I'm actually writing a wrapper around python's curses in order to provide more widgets (tables, menus, ...) and I'm now thinking about adding `stdscr.refresh()` at the end of `initscr` in order to avoid this "side effect". I'm guessing it was not done in ncurses for performance issues and to prevent the screen to flicker, but I don't think it should be an issue nowadays. Do you know other reasons it was done like that? Do you see any reason it might be a bad idea to add the refresh in the initialization? – aureo Apr 12 '21 at 01:45