curses fails when calling addch on the bottom right corner

Question:

I am starting to learn curses in Python. I am using Python 3.5 on Mac OS X. When I try to write in the bottom-right corner, the program crashes with the following error:

$ python ex_curses.py
[...]
  File "ex_curses.py", line 19, in do_curses
    screen.addch(mlines, mcols, 'c')
  _curses.error: add_wch() returned ERR

The example program is:

import curses

def do_curses(screen):
    curses.noecho()
    curses.curs_set(0)
    screen.keypad(1)

    (line, col) = 12, 0
    screen.addstr(line, col, "Hello world!")
    line += 1
    screen.addstr(line, col, "Hello world!", curses.A_REVERSE)

    screen.addch(0, 0, "c")

    (mlines, mcols) = screen.getmaxyx()
    mlines -= 1
    mcols -= 1
    screen.addch(mlines, mcols, 'c')

    while True:
        event = screen.getch()
        if event == ord("q"):
            break
    curses.endwin()

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

I have a feeling that I’m missing something obvious, but I don’t know what.

Asked By: Giovanni

||

Answers:

That is expected behavior (a quirk) because addch attempts to wrap to the next line after adding a character. There is a comment in lib_addch.c dealing with this:

/*
 * The _WRAPPED flag is useful only for telling an application that we've just
 * wrapped the cursor.  We don't do anything with this flag except set it when
 * wrapping, and clear it whenever we move the cursor.  If we try to wrap at
 * the lower-right corner of a window, we cannot move the cursor (since that
 * wouldn't be legal).  So we return an error (which is what SVr4 does).
 * Unlike SVr4, we can successfully add a character to the lower-right corner
 * (Solaris 2.6 does this also, however).
 */
Answered By: Thomas Dickey

For the future readers. After the @Thomas Dickey answer, I have added the following snippet to my code.

try: 
    screen.addch(mlines, mcols, 'c')
except _curses.error as e:
    pass 

Now my code looks like:

import curses
import _curses

def do_curses(screen):
    curses.noecho()
    curses.curs_set(0)
    screen.keypad(1)

    (line, col) = 12, 0
    screen.addstr(line, col, "Hello world!")
    line += 1
    screen.addstr(line, col, "Hello world!", curses.A_REVERSE)

    screen.addch(0, 0, "c")

    (mlines, mcols) = screen.getmaxyx()
    mlines -= 1
    mcols -= 1
    try:
        screen.addch(mlines, mcols, 'c')
    except _curses.error as e:
        pass

    while True:
        event = screen.getch()
        if event == ord("q"):
            break
    curses.endwin()

if __name__ == "__main__":
    curses.wrapper(do_curses)
Answered By: Giovanni

It is kind of exotic solution exploiting the fact that the curses can actually draw a bottom-right character when drawing a border (without raising an exception). See the full example:

# -*- coding: utf-8 -*-
import curses


def addch_bottom_right(window, ch):
    """
    Somehow, the underlying ncurses library has an issue
    with writing a char into bottom-right corner
    (see the https://stackoverflow.com/a/36389161 for example).

    But we can use the workaround:
    - create a subwindow 1x1 of the current window in the bottom-right corner
    - draw a border of that window, consisting only of the desired character:
      for a 1x1 window, that border will consist exclusively of this single character.
    - refresh the screen to show your new 'window' with the 'border'.
    """
    print("Putting char '%s' in the bottom-right corner" % ch)
    beg_y, beg_x = window.getbegyx()
    max_y, max_x = window.getmaxyx()
    br_y = beg_y + max_y - 1
    br_x = beg_x + max_x - 1

    print('Coordinates of current window: %sx%s' % (br_y, br_x))
    w = window.subwin(1, 1, br_y, br_x)

    # only 'br' (bottom-right corner) gets printed for 1x1 box
    w.border(*([ch] * 8))
    w.noutrefresh()
    window.noutrefresh()
    curses.doupdate()


def demo(screen, show_border=True):
    """
    Try the workaround with three different windows nesting levels.

    Borders drawn here only to show where the windows are.
    """
    curses.curs_set(0)

    w = screen.subwin(8, 8, 10, 10)
    if show_border:
        w.border()
    addch_bottom_right(w, 'Window'[0])

    w2 = w.subwin(3, 3, 12, 12)
    if show_border:
        w2.box()
    addch_bottom_right(w2, 'Subwindow'[0])

    addch_bottom_right(screen, 'Main screen'[0])

    screen.getch()


if __name__ == '__main__':
    curses.wrapper(demo)
Answered By: tsionyx

window.insch(...) can place a character at the lower right of a window without advancing the cursor. Any character at that position will be bumped to the right without causing an error.

Answered By: Brian Killian

This is extension for Brian’s answer. I decided to choose method based on writing position (y, x)

method = window.addch if (y+1, x+1) != window.getmaxyx() else window.inschr
method(y, x, char)

as the methods addch and insch have same call signatures.

Answered By: Stepan Zakharov
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.