Chapter 7 Exercise Set 1: Unix Pipes and Curses¶
Additional Curses Configuration¶
There are a few things that will be useful to setup as we get deeper into our exploration of Curses.
First, The convention is that terminal size should be 80 columns x 24 rows. (The history of this convention is fascinating - see this StackOverflow thread for more context ). It’s possible to resize modern terminal windows, but we’ll want to standardize on these dimensions for many of our upcoming projects.
The other challenging thing about using Curses is that its output takes up the
entire terminal, and uses the stdout system stream to control the output.
This means that, while our program is using curses, we can not use Python’s
standard print() function. There are lots of ways to get around this, but
our favorite is to use named pipes
to redirect output to a different terminal window.
To set this up on a unix system, create a named pipe with this command:
$ mkfifo /tmp/debug_pipe
Then in a new terminal window, type this:
$ tail -f /tmp/debug_pipe
This command will listen to the pipe and print out anything that is written to it.
Here’s some starter Python code that sets the terminal size and writes a sample message to the pipe.
import curses
import subprocess
output_pipe = open("/tmp/debug_pipe", "w")
def main(stdscr):
width = 80
height = 24
rows, cols = stdscr.getmaxyx()
curses.start_color()
curses.resize_term(height, width)
subprocess.call(["/usr/bin/resize", "-s", str(height), str(width)])
output_pipe.write("Resized screen to 80x24\n")
output_pipe.flush()
stdscr.addstr(6, 32, f"Original window size: {rows} by {cols}")
stdscr.addstr(11, 32, "Now, This screen is 24x80!")
stdscr.refresh()
# clear any output that might have come from resize, and then
# wait for user to press another key to exit
curses.flushinp()
stdscr.getch()
if __name__ == "__main__":
curses.wrapper(main)
Getting User Text Input¶
Sometimes we want the user to be able to type text input. This program sets up a messaging interface. We’ll revisit this code in the Chapter 10 exercises.
import curses
def main(stdscr):
# Setup
curses.echo() #make the input visible
height, width = (24,80) #we're hard-coding this for now.
#make sure you manually adjust to 24x80
#before running
# Create a window to display the chat history
chat_height = height-3
chat_win = curses.newwin(chat_height, width, 0, 0)
# Create a window to type and read input
input_win = curses.newwin(3, width, chat_height, 0)
input_win.border()
input_win.addstr(1, 2, "> ")
input_win.refresh()
messages = []
while True:
# Show user input
curses.echo()
#getstr moves the cursor to position (1,4) relative to this window,
# waits for the user to hit the enter key,
# and then returns whatever the user typed.
user_input = input_win.getstr(1, 4, width - 5).decode('utf-8').strip()
#turn off user input until we're done updating the screen
curses.noecho()
if user_input:
# Add your new message to the chat history
messages.append(f"You: {user_input}")
# Redraw full chat history
chat_win.erase()
for i, msg in enumerate(messages[-chat_height:]):
chat_win.addstr(i, 0, msg)
chat_win.refresh()
# overwrite the previous input with a bunch of blanks
input_win.addstr(1, 4, " " * (width - 5))
input_win.refresh()
if __name__ == "__main__":
curses.wrapper(main)