An Introduction to curses in Python

In a few of my posts I have used various ANSI terminal codes to manipulate terminal output, specifically moving the cursor around and changing text colours. These codes are often cryptic, can be cumbersome if used extensively, and cannot be relied upon to work across different platforms. A better solution is to use Python's implementation of the venerable curses library, and in this post I will provide a short introduction to what I consider its core functionalities: moving the cursor around and printing in different colours.

To get started create a folder somewhere convenient and within it create an empty file called cursesdemo.py. Open the file in your favourite editor and type or paste the following code. You can download the source code as a zip or clone/download from Github if you prefer.

Source Code Links

ZIP File
GitHub

cursesdemo.py

import curses


def main():

    """
    The curses.wrapper function is an optional function that
    encapsulates a number of lower-level setup and teardown
    functions, and takes a single function to run when
    the initializations have taken place.
    """

    curses.wrapper(curses_main)


def curses_main(w):

    """
    This function is called curses_main to emphasise that it is
    the logical if not actual main function, called by curses.wrapper.

    Its purpose is to call several other functions to demonstrate
    some of the functionality of curses.
    """

    w.addstr("-----------------\n")
    w.addstr("| codedrome.com |\n")
    w.addstr("| curses demo   |\n")
    w.addstr("-----------------\n")
    w.refresh()

    printing(w)

    # moving_and_sleeping(w)

    # colouring(w)

    w.addstr("\npress any key to exit...")
    w.refresh()
    w.getch()


def printing(w):

    """
    A few simple demonstrations of printing.
    """

    w.addstr("This was printed using addstr\n\n")
    w.refresh()

    w.addstr("The following letter was printed using addch:- ")
    w.addch('a')
    w.refresh()

    w.addstr("\n\nThese numbers were printed using addstr:-\n{}\n{:.6f}\n".format(123, 456.789))
    w.refresh()


def moving_and_sleeping(w):

    """
    Demonstrates moving the cursor to a specified position before printing,
    and sleeping for a specified period of time.
    These are useful for very basic animations.
    """

    row = 5
    col = 0

    curses.curs_set(False)

    for c in range(65, 91):

        w.addstr(row, col, chr(c))
        w.refresh()
        row += 1
        col += 1
        curses.napms(100)

    curses.curs_set(True)

    w.addch('\n')


def colouring(w):

    """
    Demonstration of setting background and foreground colours.
    """

    if curses.has_colors():

        curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_RED)
        curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_GREEN)
        curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_CYAN)

        w.addstr("Yellow on red\n\n", curses.color_pair(1))
        w.refresh()

        w.addstr("Green on green + bold\n\n", curses.color_pair(2) | curses.A_BOLD)
        w.refresh()

        w.addstr("Magenta on cyan\n", curses.color_pair(3))
        w.refresh()

    else:

        w.addstr("has_colors() = False\n");
        w.refresh()


main()

main, curses.wrapper and curses_main

At the top of the file we import curses, and then we have the main function. Starting and stopping curses the "hard way" involves calling curses.initscr() and curses.endwin() respectively, along with exception handling and extra function calls if you want to print in colour. A simpler way is to call curses.wrapper with a function to call when the necessary initialization has been done, which is what I have done here. A convention which I have adopted is to have a single line of code in main calling curses.wrapper, and a function called curses_main which is passed to and therefore called by curses.wrapper. The curses_main function can be considered the "real" main function as it does all the stuff you would do in main if you were not using curses.wrapper.

The wrapper function creates a curses.window object which is passed to the function it calls. This is used to access all the curses methods, as we will see in a moment.

The curses_main function firstly prints out a heading using the curses addstr method. All output from curses is printed to a buffer rather than the actual screen so we need to call refresh after addstr. Skipping the three function calls for the time being we then print out a message asking the user to hit any key to exit (not forgetting to call refresh) and then call getch. This sits and waits for a keypress before we reach the end of the program. If we didn't do this curses would end and clear the screen immediately so we probably wouldn't see anything.

Printing

Now lets look at the printing function. As I mentioned above the curses_main function takes a curses.window argument from curses.wrapper - this is then passed to any other functions needing to use curses. In the printing function we first use addstr in its simplest form, just passing a string constant. Next we use addch which prints a single character. Finally we use addstr in a more complex form combined with the string format method to output an integer and a floating point number. You can see from this that addstr can be used to print out anything you can print with raw Python.

Moving and Sleeping

So far we haven't done anything you can't do much more easily without curses, but lets move on to the moving_and_sleeping function which prints out the alphabet across and down the screen. After initializing a couple of variables for the position we hide the cursor so it doesn't get in the way and annoy us during the animation. Then we loop from 65 to 91 (the ASCII codes for A to Z) and print out each letter. The addstr method can optionally take a row and a column to print at, the form I am using here. We then increment row and col to move down and across before calling napms (nap for milliseconds). After the loop we mustn't forget to show the cursor again.

Printing in Colour

The final function is called colouring and is the most complex of the lot. Using "raw" curses without wrapper we would need to call start_color but wrapper does that for us. We still need to call has_colors()though and if this returns false I have just printed out a message, although for an actual application it would be better to provide a non-coloured fallback.

Text is actually printed in a combination of foreground and background colours so curses maintains a list of pairs which you can initialize with your choice of colours for later use. Here I have set three pairs using curses.init_pair - the first argument is the index and the second and third are the foreground and background colours respectively. (No, green on green isn't a mistake!) You have eight colours available in total:

  • curses.COLOR_BLACK
  • curses.COLOR_RED
  • curses.COLOR_GREEN
  • curses.COLOR_YELLOW
  • curses.COLOR_BLUE
  • curses.COLOR_MAGENTA
  • curses.COLOR_CYAN
  • curses.COLOR_WHITE

Having set up a few colour pairs we can pass them as the second argument to addstr, as you can see in the next few lines of code. Note that when using the green-on-green pair it is OR'ed with curses.A_BOLD. This does not create a bold effect in the true sense like this but merely makes the colour brighter. There are a few other constants you can try such as A_BLINK and A_UNDERLINE but support is patchy and they are probably best avoided.

Running the Program

Let's run the three functions one at a time. In curses_main uncomment printing(w) and run the program with this command:

Runing the program

python3.7 cursesdemo.py

The output is:

Program output part 1 - printing

-----------------
| codedrome.com |
| curses demo   |
-----------------

This was printed using addstr

The following letter was printed using addch:- a

These numbers were printed using addstr:-
123
456.789000

press any key to exit...

Now uncomment moving_and_sleeping(w) and run again, which will give you this:

Program output part 2 - moving_and_sleeping

-----------------
| codedrome.com |
| curses demo   |
-----------------

A
 B
  C
   D
    E
     F
      G
       H
        I
         J
          K
           L
            M
             N
              O
               P
                Q
                 R
                  S
                   T
                    U
                     V
                      W
                       X
                        Y
                         Z

press any key to exit...

What you don't see above is that the letters appeared gradually every 100 milliseconds which, along with moving the cursor to an arbitrary position, demonstrates how to implement rudimentary animation. Now uncomment colouring(w) in curses_main and run for the third time. This is what we get:

Program output part 3 - colouring

-----------------
| codedrome.com |
| curses demo   |
-----------------

Yellow on red

Green on green + bold

Magenta on cyan

press any key to exit...

This has been a very quick tour of some of the most useful things curses can do for you. If you want more details please go to https://docs.python.org/3/howto/curses.html.

Leave a Reply

Your email address will not be published. Required fields are marked *