pythonpython-3.xncursescursespython-curses

Issue with Python displaying all available color pairs with Curses


I am running Python 3.10 and I am having an issue with curses, specifically when trying to utilize the full range of color pairs

curses.COLORS returns 256

curses.COLOR_PAIRS returns 65536

curses.has_extended_color_support() returns True

But when I initialize a color pair of 256, 512, 768, 1024, etc... the color pair seems to reset the colors that were initialized

Additionally, if I am trying to display any color pair > 255 (curses.COLORS - 1) it uses one of the pairs < 255 instead of the color pair ID I set. Curses allows me to run init_pair with these numbers, but it ignores them completely, and instead uses the color pair < 255

Here is an example code snippet

#!/usr/bin/env python3.10

"""
Python3.10 added extended color support to curses
curses.COLOR_PAIRS (xterm-256color) returns 65,536
curses.COLORS (xterm-256color) returns 256
"""

import curses


def cycler(start_color, end_color):
    # cycle through all possible color IDs or pair IDs curses can display, loops indefinitely
    while True:
        for i in range(start_color, end_color):
            yield i


def main(stdscr):
    curses.start_color()
    height, width = stdscr.getmaxyx()

    RED = 1  # Reserve the red color at color ID 1
    curses.init_color(RED, 1000, 0, 0)

    color_cycler = cycler(2, curses.COLORS - 1)  # RED is reserved at 1, start at 2
    pair_cycler = cycler(1, curses.COLOR_PAIRS - 1)

    has_extended_support = curses.has_extended_color_support()
    stdscr.addstr(0, 0, f"COLORS:{curses.COLORS} "
                        f"COLOR_PAIRS:{curses.COLOR_PAIRS} "
                        f"ExtendedSupport:{has_extended_support}", curses.color_pair(1))

    for y in range(1, height - 1):
        for x in range(0, width, 5):

            color_id = next(color_cycler)
            pair_id = next(pair_cycler)

            intensity = int((color_id / (curses.COLORS - 1)) * 1000)
            curses.init_color(color_id, intensity, intensity, intensity)

            # create a new color pair, (RED) foreground, (color) background
            curses.init_pair(pair_id, RED, color_id)
            string = str(pair_id).rjust(5, ' ')
            stdscr.addstr(y, x, string, curses.color_pair(pair_id))

    stdscr.noutrefresh()
    curses.doupdate()
    stdscr.getch()


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

And here is a screenshot of what it generates for me (notice the numbers 256, 512, 768, 1024) the numbers represent the color pair ID

enter image description here

Terminal apps I've tried:

All with the same issue


Solution

  • You would have to use the extended interface, as noted in the release announcement for ncurses 6.1:

    The motivation for making this extension came from noticing that termcap applications could (though not realistically) use larger numbers than would fit in 16-bits, and the fact that the number of color pairs for a 256-color xterm could not be expressed in terminfo (i.e., 32767 versus 65536). Also, a few terminals support direct-colors, which could use the extension.

    Python probably doesn't do that (its binding only covers part of ncurses), so color-values and color-pairs are a signed 16-bit number:

    0..32767
    

    Now... python 3.10 was released in October 2021. The most recent ncurses fix related to color-pairs was in August 2021. If you're using an older version of ncurses (e.g., by compiling python on something very old, such as RHEL8), then it's just a known bug to which the answer would be "upgrade".