Search code examples
pythonpython-3.xncursescursespython-curses

Text appears mangled when bkgd char is curses.ACS_CKBOARD


I'm trying to use a "haze" character - ACS_CKBOARD - as a background, with a custom foreground and background color.

This appears to work okay, but when I call addstr to show text on top of the background, the text is garbled. It appears to be a selection of "alternate characters" in place of my "Hello, world!", except of course that some few normal letters show through.

Here's a small screen capture:

iTerm2 Screen with background set to CKBOARD, mangled text in front

I'm assuming that something has jammed up inside curses. Does anyone know how I can get the correct text to display against this background?

Here's some code that produces the behavior:

import curses
import locale

def main(scr):
    curses.init_color(2, 650, 407, 160) # brown
    curses.init_color(6, 149, 113, 78)  # tan
    curses.init_pair(1, 2, 6)
    scr.bkgd(curses.ACS_CKBOARD, curses.color_pair(1))
    scr.clear()
    scr.addstr(10, 0, 'Hello, world!', curses.color_pair(0))
    scr.getch()


if __name__ == "__main__":
    locale.setlocale(locale.LC_ALL, '')
    curses.wrapper(main)

FWIW: I'm using iTerm2 on a Mac, Python version 3.5.3. If I run the same code in Terminal, I get a similar result with the wrong color:

Mac Terminal screen with background set to CKBOARD, mangle text in front

Update:

After reading Thomas Dickey's answer, I tried to implement what he suggested. I tried setting the bkgd followed by both clear and erase followed by another call to set the bkgd. That didn't work- I got the color without the background characters. Looking at the documentation for bkgd, it does specify that "The change is then applied to every character position in that window", so that's right out!

Then I found window.bkgdset(char[, attr]), which does not immediately apply it's setting to every character position. Instead, it just applies to newly-drawn bits. With that function, I modified my code as follows:

    curses.init_pair(1, 2, 6)
    scr.bkgd(curses.ACS_CKBOARD, curses.color_pair(1))
    scr.erase()
    scr.bkgdset(' ', curses.color_pair(1))
    scr.addstr(10, 0, 'Hello, world!', curses.color_pair(0))

And that works! Here's a screenshot to show the result:

iTerm2 screen with background ACS_CKBOARD, with clear text

Thanks, Thomas, for the suggestion about resetting the background after the paint!


Solution

  • The background character (which you have set with scr.bkgd) is a combination of video attribute, color pair and character which is merged with other characters added to the screen, e.g., via scr.addstr.

    For what it's worth, X/Open Curses documents the feature in Rendition of Characters Placed into a Window.

    The alternate character set aspect is one of those video attributes, and as documented by X/Open Curses:

    the attributes specified, OR-ed with the window attributes.

    If you don't want that, you can

    • temporarily set the background, erase the window (filling the background) and reset the background without the alternate character set, e.g.,
        scr.bkgd(curses.ACS_NORMAL, curses.color_pair(1))
    
    • use a different method for adding characters to the screen. The underlying curses library waddchstr passes the video attributes directly (no merging). But it does not appear to be part of the Python binding (nothing similar in the reference manual). The attr parameter of addstr likely refers to the window attribute (set with a similar function (in X/Open curses, the "window colour").

    Given the available functions (and limitations of the binding), calls with scr.bkgd are the way to solve the problem.