reversed(top()) code tags rss about

More than 256 color pairs in curses

February 5, 2019
[curses] [c] [programming] [terminal]

Those who used curses at least once to draw something in color had to notice that setting colors happens in pairs (foreground and background) and number of such pairs is limited. These limitations and some basics about upgrading code to lift one of the limits will be described in this short post.

The 256 color pairs limit

The first issue is that while 256 color pairs are enough to hold all color configurations of 16-colors (16 foreground * 16 background = 256), they become scarce resource that requires managing on part of your application when number of available colors is greater (usually 256 these days).

The second issue is that original color API (COLOR_PAIR() macro) didn't account for possibility of having more than 256 color pairs and your application needs changes to be able to handle this case or it might work incorrectly (COLOR_PAIR() just can't handle large color pair numbers, yet you'll feed them to it).

The third and last piece is that most of curses examples you'll find are using old API and there seems to be no (at least easy to find) documentation on how to transition to the new one. There are applications which use new API, it's just documentation that didn't really caught up.

Historical context

ncurses FAQ provides extensive details on the subject in this entry.

My misconceptions

I guess someone else might also have them.

What's limiting color pairs

I used to think that color pairs limit exists only at the level of the library implementation, however it's not that hard to find documentation referencing max_pairs entry of terminfo database, which is fetched by curses on call to start_color() into COLOR_PAIRS variable.

Looking up max_pairs variable in man terminfo will give you its capability name making it easy to check how much color pairs you can get with current $TERM setting:

$ tput pairs

You can't just switch to new API

Apparently, you can. Replacing all uses of COLOR_PAIR() with equivalents should be fine and everything should continue to work as before. However, that's not enough to get more than 256 color pairs working, library itself should be built for that (see below). It's also available for many years at least in ncurses, which has it since 2005.

If new API is there, all color pairs are available

No, even if it's there, you might be limited to 256 color pairs at most. Supporting more pairs requires changes in types, which means API/ABI breakage. In ncurses new ABI has version 6, yet this doesn't mean version 6 of the library. ncurses 6 with wide support can be built to support large color pair numbers, see this comment from Thomas Dickey (its maintainer).

Elements of new API

Original API operates on int, which might include the following components ORed together by the client:

  • color pair number
  • attributes (A_BOLD, A_REVERSE, etc.)
  • character

New API accepts these things separately (internal representation might be whatever works) and uses constants for flags that start with WA_ prefix instead of A_. And from the client's perspective there is a new type called cchar_t (for "complex character") and two functions that convert to and from it:

  • setcchar() – packs cchar_t
  • getcchar() – unpacks cchar_t

There are various calls that accept color pair and attributes separately or operate on cchar_t. Names lack consistency, so to say:

  • bkgrndset() is an equivalent of older bkgdset()
  • wattr_set() is an equivalent of older wattrset()
  • etc., see curses.h for interesting functions with cchar_t and then call man on them or apropos 'complex character' (is there a better way?)

Functions of new API also take void *options parameter along with attributes and color pair number, which should be NULL unless you're using ncurses 6.1 or newer which uses it as an extension for using int as color pair number (for true RGB colors; see functions with "extended" in their names).


Window background

Old API:

wbkgdset(win, COLOR_PAIR(pair) | attrs);

New API:

cchar_t cch;
setcchar(&cch, L" ", attrs, pair, NULL);
wbkgrndset(win, &cch);

Window attributes

Old API:

wattrset(win, COLOR_PAIR(pair) | attrs);

New API:

wattr_set(win, attrs, pair, NULL);