Using Curses with Python¶
Python’s curses module provides “terminal handling for character-cell displays”.
Here is a “Hello Curses!” program in a file named hello_curses.py:
import curses
from curses import wrapper
def main(stdscr):
stdscr.clear()
stdscr.addstr(11, 32, "Hello CSC 221!")
stdscr.refresh()
stdscr.getch()
wrapper(main)
Notice that we are not using our usual print function here to display
output, nor are we using input. Instead, we create a screen object,
stdscr, and use its addstr method to print a string and its getch
method to get a character.
In our next example, read_chars.py, we save the character returned by
getchar an print out both its character and numeric representation:
import curses
def main(stdscr):
curses.noecho()
stdscr.addstr(9, 25, "Enter a character or '!' to quite: ")
stdscr.refresh()
ch = stdscr.getch()
while ch != ord('!'):
stdscr.clear()
stdscr.addstr(9, 25, "Enter a character or '!' to quite: ")
stdscr.addstr(
12, 18,
f"You entered '{chr(ch)}', which has a numeric value of {ch}."
)
stdscr.move(9, 60)
stdscr.refresh()
ch = stdscr.getch()
if __name__ == "__main__":
curses.wrapper(main)
This method returns the numeric value of the key pressed, so we need to use
Python’s char function convert into a printable character.
Exploring the window environment¶
This next example, interrogate_env.py, uses several of the available curses functions to get information about the window environment:
import curses
COLOR_ORANGE = 8
def display_heading(win):
win.attron(curses.A_STANDOUT)
win.addstr(2, 22, "ncurses")
win.attroff(curses.A_STANDOUT)
win.addstr(2, 22 + len("ncurses"), " Environment Interrogator")
win.box()
win.refresh()
def test_color_support(win):
colors = "YES" if curses.has_colors() else "NO"
win.addstr(2, 5, "Supports color: ")
if colors == "YES":
curses.start_color()
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
win.attron(curses.color_pair(1))
win.addstr(2, 5 + len("Supports color: "), colors)
if colors == "YES":
win.attroff(curses.color_pair(1))
def test_change_color_support(win):
change_color = "YES" if curses.can_change_color() else "NO"
win.addstr(4, 5, "Supports change color: ")
if change_color == "YES":
curses.init_color(COLOR_ORANGE, 1000, 500, 0)
curses.init_pair(2, COLOR_ORANGE, curses.COLOR_BLACK)
win.attron(curses.color_pair(2))
win.addstr(4, 5 + len("Supports change color: "), change_color)
if change_color == "YES":
win.attroff(curses.color_pair(2))
def display_window_sizes(win0, win1, win2):
rows, cols = win0.getmaxyx()
win2.addstr(6, 5, f"Main window size: {rows} by {cols}")
win2_rows, win2_cols = win2.getmaxyx()
win2.addstr(8, 5, f"Current window size: {win2_rows} by {win2_cols}")
win1_rows, win1_cols = win1.getmaxyx()
win2.addstr(10, 5, f"Top window size: {win1_rows} by {win1_cols}")
beg_y, beg_x = win2.getbegyx()
win2.addstr(12, 5, f"Current window top left pos: {beg_y}, {beg_x}")
def main(stdscr):
curses.noecho()
stdscr.refresh()
win1 = curses.newwin(5, 76, 1, 2)
win2 = curses.newwin(15, 46, 6, 18)
display_heading(win1)
test_color_support(win2)
test_change_color_support(win2)
display_window_sizes(stdscr, win1, win2)
win2.box()
win2.refresh()
stdscr.addstr(22, 25, "Enter a character to quit: ")
stdscr.getch()
if __name__ == "__main__":
curses.wrapper(main)
Running this will on a terminal that supports colors and color changes will produce a screen something like this: