How to use terminal color palette with curses
Question:
I can’t get the terminal color palette to work with curses.
import curses
def main(stdscr):
curses.use_default_colors()
for i in range(0,7):
stdscr.addstr("Hello", curses.color_pair(i))
stdscr.getch()
curses.wrapper(main)
This python script yields the following screen:
However, I do have more colors in my gnome-terminal palette. How can I access them within curses?
Answers:
I currently put these lines in front of my script.
curses.use_default_colors()
for i in range(0, curses.COLORS):
curses.init_pair(i, i, -1);
I don’t know if it is the best solution, but at least it yields some color pairs that are consistent with the terminal color palette.
The terminal ‘color palette’ is set by the terminal application itself to map default curses colours to application-specific ‘interpretations’. If you use red, the terminal can choose to display that as burgundy or cherry red, or if the user so desires, something completely different.
In other words, just use the curses colours (combined with or without the bright or blink modifiers) and things should Just Work.
I believe that the curses.use_default_colors()
call merely makes transparency available; it is a direct call to the use_default_colors()
ncurses API function. ncurses colors are otherwise palette based; you need to set your own color attributes per pair number with curses.init_pair()
calls, then select a color pair with curses.color_pair()
from the palette to display text with that specific pair; or build text attributes directly for a given addstr()
call.
The following I figured out by experiment on my own pc (Ubuntu 14.04, python 3).
- There are 256 colors (defined by the first 8 bits).
- The other bits are used for additional attributes, such as highlighting.
- Passing the number -1 as color falls back to the default background and foreground colors.
- The color pair 0 (mod 256) is fixed on (-1, -1).
- The colors 0 till 15 are the terminal palette colors.
Consider the following testing code.
Add this to your .bashrc
:
# Set proper $TERM if we are running gnome-terminal
if [ "$COLORTERM" == "gnome-terminal" ]
then
TERM=xterm-256color
fi
Put this in a python file and run it.
import curses
def main(stdscr):
curses.start_color()
curses.use_default_colors()
for i in range(0, curses.COLORS):
curses.init_pair(i + 1, i, -1)
try:
for i in range(0, 255):
stdscr.addstr(str(i), curses.color_pair(i))
except curses.ERR:
# End of screen reached
pass
stdscr.getch()
curses.wrapper(main)
Running it will yield the following output.
As you see, the colors pairs 1-16 are the terminal color palette for foreground colors.
I don’t have the rep-points to submit this as a comment to
Chiel ten Brinke’s excellent answer, so I’ll offer here a more useful version of his color script:
import curses
def main(stdscr):
curses.start_color()
curses.use_default_colors()
for i in range(0, curses.COLORS):
curses.init_pair(i + 1, i, -1)
stdscr.addstr(0, 0, '{0} colors available'.format(curses.COLORS))
maxy, maxx = stdscr.getmaxyx()
maxx = maxx - maxx % 5
x = 0
y = 1
try:
for i in range(0, curses.COLORS):
stdscr.addstr(y, x, '{0:5}'.format(i), curses.color_pair(i))
x = (x + 5) % maxx
if x == 0:
y += 1
except curses.ERR:
pass
stdscr.getch()
curses.wrapper(main)
You can use the culour
package by installing with:
pip install culour
And then you can use it to print with color to curses:
culour.addstr(window, "colored string")
curses.use_default_colors()
merely sets the default fg or bg colors to -1, from the man page "init_pair(x,COLOR_RED,-1)
will initialize pair x as red on default background and init_pair(x,-1,COLOR_BLUE)
will initialize pair x as default foreground on blue."
I always assumed that curses supported only the 8 named "curses.COLOR_…" and usually that’s enough but I wanted some spice in my apps so a short time searching found me here. Most likely the majority of terms will support 256 color, and you can use @Hristo Eftimov’s code above to just print what ever is supported. I decided to make an alternate color chooser which will show examples of x color number as foreground and background.
Arrow keys left/right or keys a/d to change which attribute to alter, +/- to incr/decr the color number, q or esc to quit.
#!/usr/bin/python
from traceback import format_exc
import sys, os, time, re, curses
import locale
locale.setlocale(locale.LC_ALL, '')
os.environ.setdefault('ESCDELAY', '250')
os.environ["NCURSES_NO_UTF8_ACS"] = "1"
move_dirs = {curses.KEY_DOWN : (1, 0), curses.KEY_UP : (-1, 0), curses.KEY_RIGHT : (0, 1), curses.KEY_LEFT : (0, -1),
ord('s') : (1, 0), ord('w') : (-1, 0), ord('d') : (0, 1), ord('a') : (0, -1)}
colors = {'white': curses.COLOR_WHITE, 'red': curses.COLOR_RED, 'green': curses.COLOR_GREEN,
'yellow': curses.COLOR_YELLOW, 'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
'cyan': curses.COLOR_CYAN, 'black': curses.COLOR_BLACK}
class SuspendCurses():
def __enter__(self):
curses.endwin()
def __exit__(self, exc_type, exc_val, tb):
newscr = curses.initscr()
newscr.refresh()
curses.doupdate()
def cp(i):
return curses.color_pair(i)
def set_pairs(fg, bg):
curses.init_pair(1, fg, colors['black'])
curses.init_pair(2, fg, colors['yellow'])
curses.init_pair(3, fg, colors['white'])
curses.init_pair(4, fg, colors['red'])
curses.init_pair(5, colors['black'], bg)
curses.init_pair(6, colors['yellow'], bg)
curses.init_pair(7, colors['white'], bg)
curses.init_pair(8, colors['red'], bg)
def main_loop(stdscr):
ret = 0
EXIT = False
try:
curses.curs_set(1) #set curses options and variables
curses.noecho()
curses.cbreak()
maxc = curses.COLORS
maxy, maxx = stdscr.getmaxyx()
if maxy < 10 or maxx < 65:
with SuspendCurses():
print('Terminal window needs to be at least 10h by 65w')
print('Current h:{0} and w:{1}'.format(maxy, maxx))
ret = 1
EXIT = True
stdscr.refresh()
h, w = 10, 65
test_win = curses.newwin(h, w, 0, 0)
stdscr.nodelay(1)
test_win.leaveok(0)
test_win.keypad(1)
test_win.bkgd(' ', cp(0))
test_win.box()
cursor = [2, 0]
test_win.move(2, 2+cursor[1]*20)
fgcol, bgcol = 1, 1
set_pairs(fgcol, bgcol)
test_win.refresh()
cursor_bounds = ((0,0),(0,1))
teststr = '! @ # $ % ^ & * _ + - = '
k, newk = 1, 2
while not EXIT:
if k > -1:
test_win.clear()
if k in move_dirs.keys(): #move cursor left or right with wrapping
cursor[1] += move_dirs[k][1]
if cursor[1] > cursor_bounds[1][1]: cursor[1] = cursor_bounds[1][0]
if cursor[1] < cursor_bounds[1][0]: cursor[1] = cursor_bounds[1][1]
if k == 45: #decr currently selected attr
if cursor[1] == 0:
fgcol -= 1
if fgcol < 0: fgcol = maxc-1
else:
bgcol -= 1
if bgcol < 0: bgcol = maxc-1
set_pairs(fgcol, bgcol)
if k == 43: #incr currently selected attr
if cursor[1] == 0:
fgcol += 1
if fgcol > maxc-1: fgcol = 0
else:
bgcol += 1
if bgcol > maxc-1: bgcol = 0
set_pairs(fgcol, bgcol)
if k in (ord('q'), 27):
EXIT = True
test_win.addstr(1, 10, '{0} colors supported'.format(maxc), cp(0))
test_win.addstr(2, 2, 'FG: {0} '.format(fgcol), cp(0))
test_win.addstr(2, 32, 'BG: {0} '.format(bgcol), cp(0))
for i in range(1,5):
test_win.addstr(3+i, 2, teststr, cp(i))
test_win.addstr(3+i, 32,teststr, cp(i+4))
test_win.move(1, 2+cursor[1]*30)
test_win.box()
test_win.refresh()
curses.napms(10)
newk = stdscr.getch()
if newk != k:
k = newk
except KeyboardInterrupt:
pass
except:
ret = 1
with SuspendCurses():
print(format_exc())
finally:
return ret
if __name__ == '__main__':
try:
_ret = curses.wrapper(main_loop)
except Exception as e:
print(e)
finally:
print('Exit status ' + str(_ret))
sys.exit(_ret)
Screenshot:
Late answer, but might help out other curses newbies.
curses.start_color
(called by curses.wrapper
) initializes 8 basic colors, so one would assume you can just use curses.color_pair(i)
to display colors. However, curses does not only have color
s, it also has color_pair
s, and only the later can be used to display color, but start_color
only initializes color
s, and use_default_colors
only initializes color number -1.
This means that after this point you do not have any color pairs set up, only colors. You have to set up the color pairs manually before you can draw. You can also change the definitions of the induvidual colors if you wish, but as others have demonstrated you usually already have quite a few set up.
TL;DR:
If you struggle with no colors displayed after calling wrapper
or start_color
and use_default_colors
, you might be missing the fact that you have to set up color pairs with init_pair
before drawing in color.
I can’t get the terminal color palette to work with curses.
import curses
def main(stdscr):
curses.use_default_colors()
for i in range(0,7):
stdscr.addstr("Hello", curses.color_pair(i))
stdscr.getch()
curses.wrapper(main)
This python script yields the following screen:
However, I do have more colors in my gnome-terminal palette. How can I access them within curses?
I currently put these lines in front of my script.
curses.use_default_colors()
for i in range(0, curses.COLORS):
curses.init_pair(i, i, -1);
I don’t know if it is the best solution, but at least it yields some color pairs that are consistent with the terminal color palette.
The terminal ‘color palette’ is set by the terminal application itself to map default curses colours to application-specific ‘interpretations’. If you use red, the terminal can choose to display that as burgundy or cherry red, or if the user so desires, something completely different.
In other words, just use the curses colours (combined with or without the bright or blink modifiers) and things should Just Work.
I believe that the curses.use_default_colors()
call merely makes transparency available; it is a direct call to the use_default_colors()
ncurses API function. ncurses colors are otherwise palette based; you need to set your own color attributes per pair number with curses.init_pair()
calls, then select a color pair with curses.color_pair()
from the palette to display text with that specific pair; or build text attributes directly for a given addstr()
call.
The following I figured out by experiment on my own pc (Ubuntu 14.04, python 3).
- There are 256 colors (defined by the first 8 bits).
- The other bits are used for additional attributes, such as highlighting.
- Passing the number -1 as color falls back to the default background and foreground colors.
- The color pair 0 (mod 256) is fixed on (-1, -1).
- The colors 0 till 15 are the terminal palette colors.
Consider the following testing code.
Add this to your .bashrc
:
# Set proper $TERM if we are running gnome-terminal
if [ "$COLORTERM" == "gnome-terminal" ]
then
TERM=xterm-256color
fi
Put this in a python file and run it.
import curses
def main(stdscr):
curses.start_color()
curses.use_default_colors()
for i in range(0, curses.COLORS):
curses.init_pair(i + 1, i, -1)
try:
for i in range(0, 255):
stdscr.addstr(str(i), curses.color_pair(i))
except curses.ERR:
# End of screen reached
pass
stdscr.getch()
curses.wrapper(main)
Running it will yield the following output.
As you see, the colors pairs 1-16 are the terminal color palette for foreground colors.
I don’t have the rep-points to submit this as a comment to
Chiel ten Brinke’s excellent answer, so I’ll offer here a more useful version of his color script:
import curses
def main(stdscr):
curses.start_color()
curses.use_default_colors()
for i in range(0, curses.COLORS):
curses.init_pair(i + 1, i, -1)
stdscr.addstr(0, 0, '{0} colors available'.format(curses.COLORS))
maxy, maxx = stdscr.getmaxyx()
maxx = maxx - maxx % 5
x = 0
y = 1
try:
for i in range(0, curses.COLORS):
stdscr.addstr(y, x, '{0:5}'.format(i), curses.color_pair(i))
x = (x + 5) % maxx
if x == 0:
y += 1
except curses.ERR:
pass
stdscr.getch()
curses.wrapper(main)
You can use the culour
package by installing with:
pip install culour
And then you can use it to print with color to curses:
culour.addstr(window, "colored string")
curses.use_default_colors()
merely sets the default fg or bg colors to -1, from the man page "init_pair(x,COLOR_RED,-1)
will initialize pair x as red on default background and init_pair(x,-1,COLOR_BLUE)
will initialize pair x as default foreground on blue."
I always assumed that curses supported only the 8 named "curses.COLOR_…" and usually that’s enough but I wanted some spice in my apps so a short time searching found me here. Most likely the majority of terms will support 256 color, and you can use @Hristo Eftimov’s code above to just print what ever is supported. I decided to make an alternate color chooser which will show examples of x color number as foreground and background.
Arrow keys left/right or keys a/d to change which attribute to alter, +/- to incr/decr the color number, q or esc to quit.
#!/usr/bin/python
from traceback import format_exc
import sys, os, time, re, curses
import locale
locale.setlocale(locale.LC_ALL, '')
os.environ.setdefault('ESCDELAY', '250')
os.environ["NCURSES_NO_UTF8_ACS"] = "1"
move_dirs = {curses.KEY_DOWN : (1, 0), curses.KEY_UP : (-1, 0), curses.KEY_RIGHT : (0, 1), curses.KEY_LEFT : (0, -1),
ord('s') : (1, 0), ord('w') : (-1, 0), ord('d') : (0, 1), ord('a') : (0, -1)}
colors = {'white': curses.COLOR_WHITE, 'red': curses.COLOR_RED, 'green': curses.COLOR_GREEN,
'yellow': curses.COLOR_YELLOW, 'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
'cyan': curses.COLOR_CYAN, 'black': curses.COLOR_BLACK}
class SuspendCurses():
def __enter__(self):
curses.endwin()
def __exit__(self, exc_type, exc_val, tb):
newscr = curses.initscr()
newscr.refresh()
curses.doupdate()
def cp(i):
return curses.color_pair(i)
def set_pairs(fg, bg):
curses.init_pair(1, fg, colors['black'])
curses.init_pair(2, fg, colors['yellow'])
curses.init_pair(3, fg, colors['white'])
curses.init_pair(4, fg, colors['red'])
curses.init_pair(5, colors['black'], bg)
curses.init_pair(6, colors['yellow'], bg)
curses.init_pair(7, colors['white'], bg)
curses.init_pair(8, colors['red'], bg)
def main_loop(stdscr):
ret = 0
EXIT = False
try:
curses.curs_set(1) #set curses options and variables
curses.noecho()
curses.cbreak()
maxc = curses.COLORS
maxy, maxx = stdscr.getmaxyx()
if maxy < 10 or maxx < 65:
with SuspendCurses():
print('Terminal window needs to be at least 10h by 65w')
print('Current h:{0} and w:{1}'.format(maxy, maxx))
ret = 1
EXIT = True
stdscr.refresh()
h, w = 10, 65
test_win = curses.newwin(h, w, 0, 0)
stdscr.nodelay(1)
test_win.leaveok(0)
test_win.keypad(1)
test_win.bkgd(' ', cp(0))
test_win.box()
cursor = [2, 0]
test_win.move(2, 2+cursor[1]*20)
fgcol, bgcol = 1, 1
set_pairs(fgcol, bgcol)
test_win.refresh()
cursor_bounds = ((0,0),(0,1))
teststr = '! @ # $ % ^ & * _ + - = '
k, newk = 1, 2
while not EXIT:
if k > -1:
test_win.clear()
if k in move_dirs.keys(): #move cursor left or right with wrapping
cursor[1] += move_dirs[k][1]
if cursor[1] > cursor_bounds[1][1]: cursor[1] = cursor_bounds[1][0]
if cursor[1] < cursor_bounds[1][0]: cursor[1] = cursor_bounds[1][1]
if k == 45: #decr currently selected attr
if cursor[1] == 0:
fgcol -= 1
if fgcol < 0: fgcol = maxc-1
else:
bgcol -= 1
if bgcol < 0: bgcol = maxc-1
set_pairs(fgcol, bgcol)
if k == 43: #incr currently selected attr
if cursor[1] == 0:
fgcol += 1
if fgcol > maxc-1: fgcol = 0
else:
bgcol += 1
if bgcol > maxc-1: bgcol = 0
set_pairs(fgcol, bgcol)
if k in (ord('q'), 27):
EXIT = True
test_win.addstr(1, 10, '{0} colors supported'.format(maxc), cp(0))
test_win.addstr(2, 2, 'FG: {0} '.format(fgcol), cp(0))
test_win.addstr(2, 32, 'BG: {0} '.format(bgcol), cp(0))
for i in range(1,5):
test_win.addstr(3+i, 2, teststr, cp(i))
test_win.addstr(3+i, 32,teststr, cp(i+4))
test_win.move(1, 2+cursor[1]*30)
test_win.box()
test_win.refresh()
curses.napms(10)
newk = stdscr.getch()
if newk != k:
k = newk
except KeyboardInterrupt:
pass
except:
ret = 1
with SuspendCurses():
print(format_exc())
finally:
return ret
if __name__ == '__main__':
try:
_ret = curses.wrapper(main_loop)
except Exception as e:
print(e)
finally:
print('Exit status ' + str(_ret))
sys.exit(_ret)
Screenshot:
Late answer, but might help out other curses newbies.
curses.start_color
(called by curses.wrapper
) initializes 8 basic colors, so one would assume you can just use curses.color_pair(i)
to display colors. However, curses does not only have color
s, it also has color_pair
s, and only the later can be used to display color, but start_color
only initializes color
s, and use_default_colors
only initializes color number -1.
This means that after this point you do not have any color pairs set up, only colors. You have to set up the color pairs manually before you can draw. You can also change the definitions of the induvidual colors if you wish, but as others have demonstrated you usually already have quite a few set up.
TL;DR:
If you struggle with no colors displayed after calling wrapper
or start_color
and use_default_colors
, you might be missing the fact that you have to set up color pairs with init_pair
before drawing in color.