Giovanni
Giovanni

Reputation: 735

curses fails when calling addch on the bottom right corner

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.

Upvotes: 14

Views: 3603

Answers (5)

Giovanni
Giovanni

Reputation: 735

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)

Upvotes: 5

Thomas Dickey
Thomas Dickey

Reputation: 54455

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).

Upvotes: 7

Stepan Zakharov
Stepan Zakharov

Reputation: 747

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.

Upvotes: 0

Brian Killian
Brian Killian

Reputation: 41

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.

Upvotes: 4

tsionyx
tsionyx

Reputation: 1669

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)

Upvotes: 0

Related Questions