Most user interaction with Linux is done from afar via SSH connections and console applications. This, of course, is the continuing legacy of Linux’s “text only” roots, and many of these console applications tend to be “boring” workhorses which spit out line after line of output, with the occasional user input breaking up the monotony.
However, there is no reason console applications cannot be “spiced up” with windowed interfaces that feature colors, formatted text, and text placed – and updated – at various locations in a terminal window.
ncurses and Curses Library in Python
ncurses is a Linux shared library that allows for programmatically updating a terminal screen without having to worry about the particulars of the terminal. It was originally developed for C/C++, but it has been ported to other programming languages, including Python. The name ncurses is short for “new curses,” as this library emulates the original proprietary curses library that was featured in the Unix operating systems which preceded Linux. The ncurses library is also able to handle non-blocking keyboard input without root access.
The ncurses implementation in Python shares its name with the original Unix’s curses library. It is called the Python curses module.
The code examples in this article were run in the following environments:
- Ubuntu 22.04 LTS with Python 3.10.4
- Kali Linux 2022.2 with Python 3.10.4
- Windows 10 Professional with Python 3.10.15
Some of these examples may not work with Windows. The demonstrations in this programming tutorial series – of which this is part one – also presume a basic working understanding of Python on the part of the reader.
Looking to learn Python programming in a class or online course environment? We have a list of the Best Online Courses to Learn Python to help get you started.
What are the Downsides of the curses Library in Python?
There is usually a steep learning curve associated with any software library that facilitates complex tasks, especially if it is built on top of a C-based library that is decades old. curses is no exception, but there are a few shortcuts that can help a beginner hit the ground running quickly. While it would be ideal to jump directly into a “Hello, world!”-style curses-enabled Python program, there are a few major quirks which should at least be briefly explained so that said “Hello, world!” program will stand an even remote chance of working.
Because it would be quite aggravating to not even be able to get a simple “Hello, world!” program working correctly, right? Right.
The biggest obstacles of working with the Python curses library tend to be:
- Missing Libraries
- Terminal Compatibility
- Error Handling
- Starting and Ending ncurses
- Computing Horsepower
Why Use the Python curses Library?
One may ask “Why develop an application in this manner? Why not just develop a graphical application that runs over X11 and be done with it?”
The main argument against developing an X11-based application is that many Linux servers do not bother running with support for X11. Many sysadmins see no need to expend the resources needed to support X11. Good luck with trying to get them to make an exception for a single application!
Additionally, even when X11 is supported, X11 connectivity in Linux is very temperamental (and this is being generous). There are far too many circumstances beyond the control of a developer which can more easily break the functionality of graphical applications via X11 when compared to using the curses module. Additionally, it is much easier to adapt existing text-based Python applications to make use of the curses module as opposed to recoding them to make use of X11.
Missing Libraries in the Python curses Module
While it is not explicitly required that the underlying C-based ncurses library packages need to be installed in order for curses in Python to work correctly, it may be entirely possible that a particular Python implementation may “insist” that the ncurses library packages be installed. In Linux; this can be done by using the following commands:
$ sudo apt-get install libncurses5-dev libncursesw5-dev # ncurses development libraries $ sudo apt-get install libncurses5 # ncurses implementation library $ sudo apt-get install ncurses-doc # ncurses documentation, manual pages
Note: The apt-get command works with Debian-based Linux distributions such as Ubuntu and Kali. For other distributions, the commands yum or dnf may be more appropriate.
The last library, ncurses-doc, provides access to the manual pages for ncurses. These can be accessed via the man command:
$ man ncurses
The curses module is nominally present in Python installations in Linux.
Meanwhile, as ncurses is historically a Linux or Unix offering, it is not “natively” present in Windows. The Python curses module will not work in WIndows until the windows-curses module is installed. This can be done using the command:
C…> pip3 install windows-curses
This gives the output:
Terminal Compatibility and the Python curses Module
In Linux, the ncurses library is wholly integrated into – and dependent upon – the terminal emulator that is used to display the output of the program. As there are many different terminal emulators out there, there may be “trivial” incompatibilities which may prevent ncurses-based programs from working correctly. The same ncurses-based program may work on one terminal emulation setup in a given computer, but break in a different terminal emulation setup in the same computer. There is not really an easy way to fix this aside from going into all sorts of esoteric details about terminal emulation that would be well beyond the scope of this programming tutorial.
Unfortunately, curses-enabled Python programs may inherit these deficiencies.
The popular Windows SSH client Putty is known to have issues with ncurses-based programs. The following terminal emulation software was used for the programs shown in this tutorial:
There are many terminal emulation tweaks that can be used to fix deficiencies in a given terminal emulation package, but the best course of action for a beginner with ncurses in general, or curses in Python in particular, is to simply use a terminal emulator that works correctly right out of the box. Fortunately, there are many freely available terminal emulation clients for Windows, MacOS, or whatever other Operating Environment is being used.
And one other thing about terminals: their size can be variable.
One final note here: in order to invoke Windows PowerShell, type “powershell” without the quotation marks into the Windows Search Bar (next to the Start button) and select “Windows Powershell” when it comes up in the “Best Match” section of the Start Window.
Error Handling the Python curses Library
While the Python curses module is certainly quite effective at returning exceptions when things do not work, it is usually not very forthcoming about why those errors occurred. For example, say a programmer tries to instantiate a Python curses window object with a size larger than the terminal window. This results in an exception, but there is no exception message indicating that this is what the error is. A programmer will have to become very creative with tracing code and debugging in order to effectively learn how to use the Python curses module.
Be aware that starting an ncurses-based program in a terminal window that is too small to accommodate the size of all the window objects is a very common error.
How to Start and End ncurses in Python
All ncurses-based programs, including curses-enabled Python programs, require a specific startup and shutdown procedure even if there are exceptions in the code that result in the program quitting prematurely. If this startup and shutdown procedure is not followed, then the terminal window may not be usable for other commands once the program quits. The startup procedure initializes ncurses and changes the terminal settings so that output can be written to the screen using that object. When this happens, calls to print functions will not work. However, once the ncurses shutdown procedure is complete, calls to print functions will work as before.
Every curses-based Python program must follow the framework below in order to cleanly exit; note where the startup and shutdown procedures begin and end:
# demo-skeleton.py import curses import sys def main(argv): # BEGIN ncurses startup/initialization... # Initialize the curses object. stdscr = curses.initscr() # Do not echo keys back to the client. curses.noecho() # Non-blocking or cbreak mode... do not wait for Enter key to be pressed. curses.cbreak() # Turn off blinking cursor curses.curs_set(False) # Enable color if we can... if curses.has_colors(): curses.start_color() # Optional - Enable the keypad. This also decodes multi-byte key sequences # stdscr.keypad(True) # END ncurses startup/initialization... caughtExceptions = "" try: # Meat of the program goes here. # Below is Python's no-op directive. This is needed because a try block # cannot be empty. pass except Exception as err: # Just printing from here will not work, as the program is still set to # use ncurses. # print ("Some error [" + str(err) + "] occurred.") caughtExceptions = str(err) # BEGIN ncurses shutdown/deinitialization... # Turn off cbreak mode... curses.nocbreak() # Turn echo back on. curses.echo() # Restore cursor blinking. curses.curs_set(True) # Turn off the keypad... # stdscr.keypad(False) # Restore Terminal to original state. curses.endwin() # END ncurses shutdown/deinitialization... # Display Errors if any happened: if "" != caughtExceptions: print ("Got error(s) [" + caughtExceptions + "]") if __name__ == "__main__": main(sys.argv[1:])
When the code is executed in the try block above, any error will allow for the ncurses deinitialization code to execute so that a clean exit will happen. Any messages from exceptions will be displayed after the terminal settings have been restored.
Computing Horsepower Considerations for Python curses
Python curses-enabled programs have the potential to use a tremendous amount of CPU. With this in mind, avoid using devices like the Raspberry Pi or other similar “low horsepower” computers to run the demonstrations shown in this tutorial – they just cannot handle the load.
Final Thoughts on Python curses Configuration and Setup
This first part in our programming tutorial series discussing how to draw with text using the Python curses library showed developers how to setup the curses library in Python, some things to consider with regards to working with curses, and how to start and end curses. We also learned how to create a basic skeleton framework for our curses-based Python applications.
In the next part of this series, we will create our first functioning ncurses and curses Python application and learn how to position text, as well as, draw with text: How to Create a curses-enabled Python Application.