CHAPTER 15

Terminal Input and Output

Once upon a time, a terminal was a beige-colored box with a monochrome screen and a keyboard. In those days, computer rooms were populated with mainframes capable of supporting many simultaneous user sessions, and so, in order to allow simultaneous users, many terminals could be wired up to the computer, each with its own screen and keyboard. In order to save the computer from having to worry about every little detail of user input, such as scrolling the screen or dealing with the effect of a delete, terminals became progressively smarter, handling most of the minutiae of user input themselves and only sending the user's input to the computer when they hit the Return key. This meant the mainframe itself only had to take note of a user when they actually finished typing a command, rather than for each and every key press.

Terminals quickly became smarter, gaining abilities like being able to reposition the cursor, clear selected parts of the screen, scroll both down and up, and support colors. The object of all this was to relieve the computer of the work of dealing with the screen—instead of moving lines on the display, the computer could just tell the terminal to "insert a line here." Terminal commands took the form of escape sequences, terse sequences of characters precede by an escape.

In the era of the graphical user interface this might all seem entirely historical, but not so. Terminals are inherently bound up with the concept of interactive character input and output. Anywhere that we use a character-based interface, from a Unix xterm to a DOS shell, to the initial dialog on a serial connection to establish a PPP network connection, we are actually working through a virtual terminal that emulates the features of a real hardware terminal. This is why we can enter text and delete it at a command-line prompt without having to actually program code to handle the Delete key—the terminal emulation underlying our shell or application deals with it automatically. Terminal IO programming is a particular subset of general IO programming that is specifically directed towards the needs and issues of communicating through a terminal—that is, reading input from a keyboard and writing output to a screen, whatever that keyboard and screen actually turn out to be.

Determining Whether a Script Is Interactive

Programs fall into two broad categories—those that interact with an actual user, possibly through a terminal, and those that merely carry on relations with other programs and don't require a terminal. Since we often use the same program in both capacities, it is useful to be able to tell, from within the program, how it is being used.

Fortunately, Perl has a function designed precisely for this purpose, -t. This is one of the -X unary operators that variously test file names and filehandles for properties such as being a directory, executable, or writable by us, and so on. -t tests filehandles to see if they are connected to a terminal or not. So by testing STDIN with -t, we can check whether we are getting input from a terminal, and therefore (presumably) a user, or whether our input is coming from a nonterminal device, which most likely means from another program:

$interactive = -t STDIN;

Of course, just because standard input is coming from a terminal does not necessarily mean that standard output is going to one. So, if we want to prompt the user when our program is being used interactively, both STDIN and STDOUT should be checked:

#!/usr/bin/perl
use strict;
use warnings;

my $is_interactive=-t STDIN && -t STDOUT;

print "We are ",($is_interactive?"interactive"
                 :"not interactive")," ";

This short program sets a global variable, $is_interactive, to record whether the input and output of the program is attached to an interactive session. If it is, it will print "We are interactive", or "We are not interactive" otherwise. We can test it in this latter mode by running it through this short test script:

#!/usr/bin/perl
# testinteractive.pl
use strict;
use warnings;

# interactive - passes STDOUT
system "./checkinteractive.pl";

# not interactve -passes end of pipe
open PIPE,"./checkinteractive.pl |";
print <PIPE>;
close PIPE;

As this example illustrates, invoking a program with system does not put it into a noninter-active state because the invoked program is passed the current input and output (and error) filehandles that are active in the invoking program itself—in the first case the output handle seen by checkinteractive.pl is the STDOUT of testinteractive.pl. Opening a program through a piped open is a different matter altogether. The PIPE filehandle is not associated with a terminal, so when we run this script, we see "We are interactive" followed by "We are not interactive."

Reading from the Keyboard

Simple line-based input can be achieved with the <> or readline operator. The standard Perl library also provides enhanced support for reading complete lines with Term::ReadLine, which attempts to use a built-in readline library if one is present, and which supports the idea of command history. However, this limits us to complete lines and requires waiting for the user to type something, since the default behavior of terminals is to wait until Return has been pressed before bothering us with whatever was typed.

If we want to be more responsive, we use the extension module Term::ReadKey. This module handles nonblocking reads, timeouts, allows character to be typed without echoing them back to the screen, and several other useful features besides. Term::Complete is also at our disposal, a module that implements word completion. We'll be taking a look at both of these in due course.

Before embarking on a discussion of Term::ReadKey, it is worth mentioning some of the alternatives. On a Unix system, the ever-versatile stty command provides another way to access and program terminals, and if we only wish to deal with Unix platforms, often simply calling stty is suffice. For example, to disable echoing, we can use

system "stty -echo";

and to reenable it:

system "stty echo";

The advantage of stty is that as a tool provided by the operating system it can reliably deal with local platform issues for us, but it doesn't stand a chance of working on a non-Unix platform. Another utility that is potentially useful if we happen to know we are running inside an X terminal is xwininfo.

For portable applications, an all-Perl solution is a good idea, if only as a backup plan. Since Term::ReadKey works with most, but not all, platforms, we can compromise and use stty where available and Term::ReadKey as a fallback.

Simple Input

The simplest way to read input from the keyboard is the <> readline operator, which reads a single line of text from the current input filehandle. This is usually STDIN, so the following—assuming there is nothing in @ARGV, as discussed last chapter—are equivalent:

print <>;
print <STDIN>;

Note that if we do have something in @ARGV, the first attempts to open the elements of @ARGV as files, whereas the second reads from standard input, so the distinction can be important. Also note that if STDIN isn't a terminal, then either version may block waiting for input if insufficient input has arrived yet. We cover this in more detail shortly.

Controlling Terminal Input with Term::ReadKey

For situations where simple input techniques like <> will not do on their own, we can use the Term::ReadKey module, available from CPAN. This provides a much greater degree of control over the characteristics of the terminal and attempts to provide a degree of cross-platform support, so a script written under Unix still has a good chance of working on Windows or other platforms. As well as providing functionality to read terminal input into our programs, it also provides limited control over the properties of the terminal itself, such as whether or not it should echo typed characters to the screen and determining the key to perform a delete operation.

The Term::ReadKey module tries to implement as many of its features as possible for the platform it finds itself running on. However, not all terminals are created equal, so a DOS shell may not support the same features as a Unix shell. Since Unix has always had a fairly detailed idea of what a terminal ought to be, on account of growing up with them (so to speak), most of these features tend to work on Unix-like systems and, to a lesser degree, on other platforms.

Two particular subroutines in the Term::ReadKey package are available for reading input into our program. The ReadKey subroutine reads and returns a single character from the input available, waiting if necessary for it to be typed. In contrast, ReadLine reads entire lines and returns the input as a string. However, the biggest determinant of the behavior of both subroutines is the read mode of the terminal, which Term::ReadKey also allows us to control. Before discussing actual input, therefore, we need to look at read modes and find out what they do.

Read Modes

Central to the operation of Term::ReadKey is the concept of read mode. The read mode is an aspect of the terminal itself as opposed to the way it is used, and it controls how the terminal receives and processes input characters. There are six modes in total, numbered 0 to 5, with the meanings given in Table 15-1.

Table 15.1 Term::ReadKey Read Modes

Mode Name Meaning
0 restore Restore the original read mode setting.
1 normal Set normal cooked mode. Typed characters are echoed to the screen, and control characters are interpreted.
2 noecho Set no-echo cooked mode. Typed characters are not echoed to the screen. Control characters are interpreted.
3 cbreak Character-break mode. Typed characters are returned immediately to the program and are not echoed to the screen.
4 raw Set raw mode. Control characters are read like normal characters, not interpreted.
5 ultra-raw As with raw, but LF to CR/LF translation is disabled.

Some of these modes are more obvious than others. The normal mode is just what we expect from a normal command line—characters are echoed as we type them and an action is initiated only when we press Return. In addition, control characters are intercepted and handled by the terminal, rather than simply added to the input.

The noecho mode is the same as normal mode, with the exception that characters are not echoed. This makes it suitable for things like password input. By contrast, cbreak mode causes the terminal to immediately return characters as they are entered, without echoing them to the screen. This makes it suitable for single key-press applications like menu selections.

Both the raw and ultra-raw modes disable the handling of control characters by the terminal. Instead, they are sent back like any other typed character for our program to handle. The ultra-raw mode is identical to raw except that it disables the automatic translation of linefeeds into carriage-return/linefeed pairs on Unix systems. Note that this is quite different from the :crlf discipline/layer discussed in the last chapter—here we are concerned with whether or not the cursor actually moves down one line and moves to the start of the next line, a more specific issue than whether or not we have reached the end of a line in a file. If the filehandle supplied to ReadKey or ReadLine happens to be a serial connection, parity is also disabled.

The default setting is 0, which is to say, whatever mode the terminal was in when the program was started. To change the read mode, we use the ReadMode function from Term::ReadKey. This is automatically imported when the module is used and takes a single parameter of either a number or the corresponding named synonyms:

use Term::ReadKey;

ReadMode 1;          # set normal cooked mode
ReadMode 2;          # set no-echo mode
ReadMode 'noecho';   # same as above, but more legible
ReadMode 'restore';  # restore the mode to whatever it started as

The read mode when we start can be any of these values, though it is most likely to be 1. However, if we have been called from another program, then that program may have changed the read mode before calling us. For example, we might be the program another_program being called from this Perl script:

use Term::ReadKey;

ReadMode 'noecho';   # suppress output
system("another_program");
ReadMode 'restore';   # restore terminal to original settings

If we do change the terminal read mode, then it is a good idea to restore it again before we finish, since it is not restored automatically just because we exit. Recall that it is a property of the terminal, not our program or the Term::ReadKey module. If we do not restore the mode, we can cause problems for the program that called our code, or even the user if we run it directly. For example, if we were to run the preceding script from a shell and omit the ReadMode 'restore';, then it would return to the user with echoing disabled, so that any further commands typed would be invisible to them. Obviously this is not a good thing, so to be sure that no unexpected incidents occur, we should restore the read mode as a matter of course.

Restoring settings is one good use for an END block. With the following additional definition, we can ensure that our program will restore the terminal before it exits wherever in the program it does so, so long as it exits normally:

END {
   ReadMode 'restore';
}

To handle abnormal exits, we can do the same thing in __DIE__ and signal handlers.

The restore mode has one additional property; when used, it causes the next mode to be set to become the default. In the preceding case, our program exits before this can become relevant, but the following sequence of calls shows it in action:

ReadMode 0;   # 'restore' - restores to the default mode
ReadMode 1;   # sets mode 1, changes default mode to 1
ReadMode 4;   # sets mode 4 ('raw' mode)
ReadMode 0;   # restores the mode to mode 1

It is important to realize that since the read mode is a property of the terminal, it affects all kinds of input, including the <> readline operator, not just the ReadKey and ReadLine subroutines covered next.

Reading a Single Character

Frequently we want to read not a line, but a single character from the keyboard, to make a menu selection, for example. We might think to use the built-in function getc, but while this only reads one character, it still requires a return for the terminal emulation to process it and feed it to our program. A more flexible solution is the ReadKey subroutine from Term::ReadKey:

$character = ReadKey 0;

The subroutine ReadKey takes two parameters: a mode and an optional filehandle. If no filehandle is specified, ReadKey defaults to standard input—the preceding is equivalent to

$character = ReadKey 0, STDIN;

The mode of ReadKey, which is not related to the terminal read mode, can be one of 0, as shown here, -1, or a positive value:

  • Mode 0 is the blocking mode—ReadKey will wait indefinitely until we enter at least a return or interrupt the program. Any platform that Perl can run on supports this mode. This is equivalent to Perl's built-in getc.
  • A mode of -1 causes ReadKey to enter nonblocking mode. In this case, ReadKey does not wait for input but grabs whatever is in the input buffer and returns the first character immediately. If nothing is available, presumably because we have not typed anything, ReadKey returns undef.
  • A mode with a positive value represents a wait of the given number of seconds, which can be fractional. For example, to wait for half a second (but return sooner if a key is pressed in the time limit), we could use
    $character = Readkey 0.5;

Note Using very small delay values in a loop is generally a bad idea unless essential, since it forces the operating system to do extra work polling for input.


On exit, $character will contain the first character that we typed, or more correctly, the first character that was available in the keyboard buffer, which may already have characters waiting (and which can be filled by things other than the keyboard, to boot).

Contrary to what we might expect, ReadKey does not necessarily return immediately after a character is typed. In fact, it may appear to wait until we enter return. This is not due to an error in our program but to a property of the terminal itself. In order to persuade the terminal to react instantly, we also need to use another read mode, cbreak. The following short script demonstrates how cbreak mode can be combined with ReadKey to react instantly to a typed character:

#!/usr/bin/perl
# hitakey.pl
use warnings;
use strict;

use Term::ReadKey;

ReadMode 'cbreak';

print "Hit a key: ";
my $selection = ReadKey 0;

print "You typed $selection ";
ReadMode 'restore';

With cbreak mode, the familiar effect of seeing typed characters immediately echoed to the screen does not occur. We can use this fact to create a prompt that checks the entered value before we echo it (or choose not to). For example, we could implement a screen-based menu system with selections from 1 to 9 and check the user's input with a script like this (we have omitted the actual menu options for brevity, since they're not the point here):

#!/usr/bin/perl
# menu.pl
use warnings;
use strict;

use Term::ReadKey;

ReadMode 'cbreak';

print "Enter an option 1 to 9: ";
my $selection = 0;
do {
   $selection = int (ReadKey 0);
} until ($selection >= 1 and $selection <= 9);

print "You typed $selection ";
ReadMode 'restore';

The cbreak mode can be used with any of the modes of ReadKey to produce interesting effects. For example, this script echoes each character as we type it, but it also prints a dot for every half second that nothing is typed:

#!/usr/bin/perl
# dot.pl
use warnings;
use strict;

use Term::ReadKey;
ReadMode 'cbreak';

# enable autoflush
$| = 1;

my $char;
do {
    $char = ReadKey 0.5;
    print defined($char) ? $char : '.';
} until (lc($char) eq 'q'),

ReadMode 'restore';

To actually get our characters to appear on the screen when we print them, we also have to put Perl into autoflush mode by setting the autoflush variable $| to 1. Without this, we would see nothing until we entered either a return (which incidentally does not quit the program) or a lower or uppercase q to stop the program. Eventually the amount of output would become large enough to trigger an automatic write, but this is hardly the effect we are looking for. We enable autoflush to achieve immediate output.

It so happens that the raw and ultra-raw modes will also work with instant effect when used with ReadKey, but this is not their intended purpose, and in fact they may cause unexpected side effects when used in this way. The real point of the raw and ultra-raw modes is to disable the processing of control characters by the terminal, which will be covered in the forthcoming section "Getting and Setting Control Characters."

Single characters can also be retrieved using the getch method from the Term::Screen module. This is a third-party module that provides a lot of terminal screen handling features that are otherwise inconvenient to write. We cover this topic in the forthcoming section "Writing to the Screen."

Reading Complete Lines

Whereas ReadKey returns single characters to us, complete lines are read by ReadLine. However, this subroutine only works completely on Unix platforms (on others it works in blocking mode but not with nonblocking, instead returning an error warning that nonblocking mode is not supported). For other platforms, we can use the Term::ReadLine module instead, which is discussed later. Not to be confused with Term::ReadLine, Term::ReadKey's ReadLine subroutine is very similar to using the <> operator, but with the additional flexibility of being able to avoid waiting for input.

Like ReadKey, ReadLine takes a reading mode as an argument, followed by an optional filehandle. To use it in its simplest guise, to read from standard input, we just write

$input=ReadLine 0;   # implicitly read from STDIN
$input=ReadLine 0, *STDIN; # with an explicit filehandle

This is (more or less) identical to writing

$input = <STDIN>;

However, like ReadKey, ReadLine also accepts a mode of -1 for nonblocking input and a positive value for a timeout in seconds. Using nonblocking causes ReadLine to read whatever is in the input buffer, up to the first return, and to return that. If nothing is in the input buffer or there is an input but not a complete line, then ReadLine will return the undefined value. Note that this is still true even if we have cbreak set as the terminal read mode, since that only causes the terminal to return characters to ReadLine immediately—ReadLine still wants to see an end-of-line character before it returns anything to us.

A positive value causes ReadLine to wait for a set length of time for input and return undef if nothing was entered in the prescribed time limit. For example:

print "Give your name - you have 10 seconds to comply: ";
$name = ReadLine 10;
die "Too Slow! Access Denied" unless defined $name;

If a nonblocking mode is used, it applies to the first character only—once a character is typed, the call will block until a return is entered. This applies even if we hit Delete.


Note For more advanced ways to read lines of input from the user, including history lists and editable command lines, skip ahead to the section "Advanced Line Input with Term::ReadLine."


Passwords and Invisible Input

If we set the read mode to either cbreak or noecho, we can write a script that accepts input from the user but does not echo the characters typed back to the terminal. The cbreak mode is more suitable for single character input like the hitakey.pl example offered earlier. The mode noecho is more suitable for entering complete lines, where we do not want to know what the user is typing until they press Return. A good example of this kind of application is a password entry prompt:

#!/usr/bin/perl
# password.pl
use warnings;
use strict;

use Term::ReadKey;

ReadMode 'noecho';
print "Enter your password: ";
my $password = ReadLine 0;
print "Thanks! ";
ReadMode 'restore';

This makes use of the ReadLine subroutine that comes with Term::ReadKey to read a complete line from the user. Note that we could equally have used cbreak mode here, the only difference being that with cbreak, characters would have been sent back to our program as soon as they were typed. We can't use the password until we have all of it, so in this case cbreak gains us nothing and just causes extra work for the computer.

Finding and Setting the Screen Size

Sometimes it is useful to know how big the terminal screen actually is, for example, when paging output. This might actually be a screen size, or it might correspond to the effective screen size of a terminal window on a graphical desktop. Whatever the screen actually is, we can determine its size with GetTerminalSize, which takes a filehandle and returns four values describing the size. Standard output is used if no handle is passed, except for Windows, where the handle is obligatory:

($cwidth, $cheight, $pwidth, $pheight) = GetTerminalSize STDOUT;

Astute readers will have noticed this doesn't have much to do with reading key presses or any kind of input. Rather, it is supported by Term::ReadKey since it is a useful thing to be able to find out. Since it is an output issue rather than an input one (screens are not generally much use for input), the supplied filehandle ought to be open for output and should correspond to some kind of terminal; serial ports don't have display dimensions.

The width and height of the screen in characters is returned in the first two values, with the width and height of the screen in pixels returned in the second two (though this will be zero in cases where the screen doesn't support pixels). Since we are primarily concerned with character IO here, we can discard the second two values and just write

($width, $height) = GetTerminalSize;

We can use our knowledge of the screen height to page through output, writing exactly a screenful of output before pausing. The following script does exactly this and uses ReadKey in cbreak mode to allow the user to scroll another screenful by pressing any key. As a small improvement on this basic design, we have prefixed each output line with its line number and have also checked the pressed key and exited immediately if it is either q or Q:

#!/usr/bin/perl
# page.pl
use warnings;
use strict;

use Term::ReadKey;

my ($width, $height) = GetTerminalSize;

my $count = 0;

ReadMode 'cbreak';

while (<>) {
   print "$.: $_";   # $. added to make example more interesting
   if (++$count == $height) {
      last if lc(ReadKey 0) eq 'q';
      $count = 0;
   }
}

ReadMode 'restore';

Having demonstrated how to page text in Perl, it is worth pointing out that all operating systems offer a pager program (more or less on Unix, for instance), so if all we want to do is page output, we can call that program and feed it the output we want to page using a piped open.

On Unix systems, we can also set the screen size using SetTerminalSize, supplying the same four arguments. We may not care about the pixel size (and attempting to set it frequently doesn't really do anything), but SetTerminalSize requires we set it anyway, so it is a good idea to preserve the old values and reuse them just in case:

# get screen size
($x, $y, $w, $h) = GetTerminalSize;

# set screen size to 80x24
SetTerminalSize (80, 24, $w, $h);

This does not, fairly obviously, actually change the size of the screen, but indicates to the terminal what size we would prefer it to be. In some cases, like a Unix shell window running under the X Window system, this might cause the shell window to actually resize itself, but we cannot depend on that behavior. Otherwise, the only effect of SetTerminalSize is to cause the operating system to notify any other programs that it is reading that terminal through a SIGWINCH (window change) signal.

It may happen that another application will try to change the terminal size in a similar manner to the way that SetTerminalSize does. This might happen, for example, if the user resizes a terminal window on the desktop. In this case, we may receive a SIGWINCH signal ourselves (assuming that our platform supports it—Windows does not). Ordinarily, this would be ignored, but we can respond to it and handle the new screen size with a signal handler:

# establish global width/height
($width, $height) = GetTerminalSize;

# set up a signal handler to catch screen size changes
$oldwinch = $SIG{WINCH};

sub window_changed {
    ($width, $height) = GetTerminalSize;

    # handle the new size - Text::Wrap might be useful here...
    redraw($width, $height);

    # call the old handler by its code reference, if there was one
    &$oldwinch if $oldwinch;

    # in case the OS clears the handler
    $SIG{WINCH} = &window_changed;
}

$SIG{WINCH} = &window_changed;

As an alternative to Term::ReadKey, the Term::Size and Term::Screen packages available from CPAN also provide functionality for getting or setting the terminal size—see later.

Serial Connections and Terminal Speed

It might happen to be that the terminal we are talking to is actually a serial connection connected to a program or user elsewhere. If we expect to be handling potentially slow connections, we could then modify the amount of data we actually send, giving slow connections more condensed information. In these cases, we can find out the speed of the terminal with GetSpeeds, which takes a filehandle as an argument (or defaults standard input otherwise) and returns two values, namely the input speed and the output speed:

($baud_in, $baud_out) = GetSpeeds SERIAL_CONNECTION;

If the filehandle is not a serial connection, then this call returns an empty list, which in the preceding case results in two undefined scalars.

Serial connections are also affected by the raw and ultra-raw read modes. Specifically, the terminal will attempt to override any parity settings, if any are in effect. This can be slightly surprising if we are reading a serial connection in raw mode, but since setting parity is now something of a rarity, it is unlikely to be that much of a problem in practice.

Line Ending Translation

As we are now well aware, different operating systems do not use the same line terminator. Under Unix, it is simply a linefeed, or LF for short. Under Windows, it is a return (to be technically accurate carriage return) and linefeed, or CR LF for short. The Macintosh and VMS are different again, using LF CR. Needless to say, this can be confusing.

In order to deal with the possibility of seeing CR LF (or LF CR) rather than LF on the end of lines, terminals usually convert CR LF into LF when they see it, returning only the LF to the application (on some systems this is done in cooperation with the underlying C library too).

Most of the time this is just what we need, because it eliminates another source of compatibility issues for us without us having to worry about it. However, if we actually want to turn this feature off, we can do so by putting the terminal into ultra-raw mode:

ReadMode 'raw';       # regular 'raw' mode
ReadMode 'ultra-raw'; # disables CR/LF->LF translation

The catch is that ultra-raw mode is in all other respects like raw mode, which is not necessarily what we wanted to use. On a Windows system, this is exactly the same as the regular raw mode, since Windows applications expect to see CR LF and so do not remove the CRs. On a Unix system, this disables the translation so that a CR LF sent to the terminal is passed unchecked as CR LF to the application.

Getting and Setting Control Characters

One of the principal differences between a terminal (whether it be a real screen-and-keyboard terminal or a window on a desktop) and regular IO devices is that terminals can handle a lot of things by themselves without needing the program connected to them to tell them how to do so. An obvious example of this is echoing typed characters to the screen; the terminal deals with that automatically without our involvement.

Less obvious but more interesting are terminal operations like pausing and resuming scrolling, and interrupting the running application. Each terminal operation may be associated with a control character, such as Ctrl-C to interrupt a program or Ctrl-S to pause scrolling. When a terminal is in a cooked mode, which is to say any read mode other than raw or ultra-raw, each time a character is typed the terminal looks it up in the list of operations to see if it triggers a particular event. (On Unix, these definitions are kept in the /etc/termcap, /etc/terminfo, and etc/gettydefs files.)

Using the Term::ReadKey subroutine GetControlChars we can, on platforms that support it, access the list of terminal operations and which keys are associated with which functions. Like most of the subroutines in Term::ReadKey, an optional filehandle can be supplied; otherwise, standard input is used. A list of key-value pairs is returned by GetControlChars, with the key name being the hash key and the key code associated with it being the value. For example, to find out which key code generates an interrupt, we can use

%controlchars = GetControlChars;
print "INTERRUPT is ", ord($controlchars{INTERRUPT});

Similarly, we can dump out the entire list to the screen with a small program, though since not all platforms support this function, we might not actually get any output:

#!/usr/bin/perl
# getcontrolchars.pl
use strict;
use warnings;

use Term::ReadKey;

my %controlchars = GetControlChars;
foreach (sort keys %controlchars) {
    print "$_ =>" , ord ($controlchars{$_}), " ";
}

Executing this script produces a list resembling (but possibly not exactly the same, depending on where and on what we run the program) the following:


DISCARD => 15
EOF => 4
EOL => 0
EOL2 => 0
ERASE => 127
ERASEWORD => 23
INTERRUPT => 3
KILL => 21
MIN => 1
QUIT => 28
QUOTENEXT => 22
REPRINT => 18
START => 17
STOP => 19
SUSPEND => 26
SWITCH => 0
TIME => 0

This list of operations and characters comes from the terminal and represents the internal mapping that it is using to process control characters. Many of the operations returned in the list may not be assigned and so have a character value of zero (or 255, depending on the platform) in the returned array—these characters are discarded by the terminal.


Tip If the terminal is not a real terminal, which is usually the case, what it receives may already have been processed by something else first. For instance, the X Window system defines its own character mapping (the xrdb utility can do this), which takes effect before our terminal emulation even sees the character. Likewise, PC keyboards actually generate 16-bit values, which are translated by the operating system into characters before we get to see them.


On Unix platforms only, we can also alter which control characters trigger which operations, using SetControlChars. This takes a list of key-value pairs as arguments and applies them to the terminal's built-in list. Each pair consists of a name, as returned by GetControlChars, followed by the character or character value. A value of zero disables the operation. For example, we can redefine or disable the Delete key by setting the ERASE operation:

SetControlChars ERASE => 0;   # disables delete
SetControlChars ERASE => 2;   # sets delete to Control-B

In the following program, we extract and print the list of control characters, alter some of them, and then print it out again. Note that the attempted alterations will not produce any effect on Windows systems (and will, in fact, generate an error):

#!/usr/bin/perl
# setcontrolchars.pl
use warnings;
use strict;

use Term::ReadKey;
my %oldcontrolchars = GetControlChars;

sub dump_list {
    my %controlchars = GetControlChars;
    foreach my $key (sort keys %controlchars) {
        print "$key => ",ord($controlchars{$key})," ";
    }
    print " ";
}
dump_list;

# disable interrupt, suspend and erase (delete)
# change eof to whatever suspend is (i.e., Ctrl-D to Ctrl-Z)
SetControlChars INTERRUPT => 0,
                EOF => $oldcontrolchars{SUSPEND},
                SUSPEND => 0,
                ERASE => 0;

dump_list;

# reset control characters to their old values
SetControlChars %oldcontrolchars;

dump_list;

This program disables the interrupt operation, which is normally bound to Ctrl-C, and changes the end-of-file (or more accurately end-of-transmission, EOT) character, by default Ctrl-D under Unix, to Ctrl-Z, which is more like Windows.

Advanced Line Input with Term::ReadLine

Perl provides a module called Term::ReadLine as part of the standard library. Although it does little by itself, it provides an interface to a system readline library, if one is installed. Two libraries are currently available (at least on Unix systems), the standard Perl readline library and the much more capable third-party GNU Readline library. GNU Readline is also available under Windows as a Cygwin package.

Whichever library is installed, we can use Term::ReadLine to access it—if the GNU library is installed, Term::ReadLine will automatically use it, so we do not have to cater for different libraries in our own code. The module Term::ReadLine will work with no underlying readline library, but few of the advanced features supported by a real readline library, like editable command lines or command-line history traversal, will be available. By writing our programs to use Term::ReadLine, however, we can transparently and automatically make use of these features if our program is run on a system where they are installed.

Also supported by Term::ReadLine are several standard methods, listed in Table 15-2, which we can call on terminal objects created with the module.

Table 15.2 Term::ReadLine Methods

Name Function
ReadLine Return the name of the underlying library module.
new Create a new terminal object.
readline Read a line. This is the central method of the module.
addhistory Add a new line to the input line history.
IN/OUT Return the input and output filehandles of the terminal, respectively. See also findConsole.
MinLine Set a limit on the shortest length of an input line before it is allowed in the input line history.
findConsole Return an array of two strings containing the appropriate file name strings for opening the input and output, respectively. See also IN/OUT.
Attribs Return a reference to a hash of internal configuration details.
Features Return a reference to a hash of supported features.

In addition, Term::ReadLine supplies some stub methods. Without an underlying library, these have no useful effect, but if one is present, they will become active and perform the relevant function. The stubs ensure that we can call these methods even if the library we are using doesn't support one or more of them. If we want to check if a given method is supported (as we probably should), we can use the Features method to find out (see Table 15-3).

Table 15.3 Term::Readline Feature Methods

Name Function
tkRunning Enable or disable the tk event loop while waiting for input. Perl-Tk only.
ornaments Enable, disable, or change the decoration of the prompt and input text when using readline.
NewTTY Switch the terminal to a new pair of input and output filehandles.

In addition to the standard methods, the underlying library may define other methods that are unique to it. The GNU library in particular defines a very extensive set of calls, in fact more than we have time to touch on here. For full details, see perldoc Term::ReadLine::Gnu.

Each library adds its own set of features to the list returned by Features, so we can also test for them before trying to use the corresponding methods. Before calling any methods, however, we first have to create a terminal object.

Creating a Terminal Object

The Term::ReadLine module is object oriented, so to use it we first instantiate a terminal object. We give this object a name, presumably a descriptive name for the program, and then optionally typeglobs for the filehandles that we wish to use for input and output:

use Term::ReadLine;

# use STDIN and STDOUT by default
$term = new Term::ReadLine "Demo";
# use filehandles IN and OUT explicitly
$term = new Term::ReadLine "Demo", *IN, *OUT;

# use a serial connection (same filehandle for both input and output)
$serialterm = new Term::ReadLine "Remote", *SERIAL, *SERIAL;

Once we have created a terminal object, we can use it to both read and write to the terminal. The following script shows the general idea:

#!/usr/bin/perl
# termobject.pl
use warnings;
use strict;

use Term::ReadLine;

my $term = new Term::ReadLine "My Demo Application";
print "This program uses ", $term->ReadLine," ";

my $input=$term->readline("Enter some text: ");
print "You entered: $input ";

First we load in the Term::ReadLine module. Then, just for curiosity's sake, we use the ReadLine method to determine the name of the underlying package, if any. Then we use the readline method (note the difference in case) to read a line from the terminal, optionally supplying a prompt.

When this program is run, it causes Term::ReadLine to look for and load an underlying readline library if it can find one. If it can, it passes control to it for all other functions. The library in turn provides editing functionality for the actual input of text. If we are using the GNU library, we can take advantage of its more advanced features like editable command lines automatically, since they are provided for us by the library. In our own code, we don't have to raise a finger, which is the point, of course.

If we happen to be using a terminal that isn't connected to standard input and standard output, we need to direct output to the right filehandle, which means passing print the right filehandle. The preceding happens to work because the terminal is connected to standard out, so a simple print works. We really ought to direct output to the terminal's output, irrespective of whether it is standard out not. Fortunately, we can find both the input and output filehandles from the terminal object using the IN and OUT methods:

$input_fh = $term->IN;
$output_fh = $term->OUT;

print $term->OUT "This writes to the terminal";

Once created, the filehandles used by the terminal can (usually) be changed with the newTTY method. This takes two typeglobs as arguments and redirects the terminal to them:

$term->newTTY *NEWIN *NEWOUT;

It is possible, though unlikely, that this will not work if the library does not support the switch. To be sure of success, we can interrogate Term::ReadLine to find out whether the newTTY feature (or indeed any feature) is actually supported.

Supported Features

Due to the fact that different readline implementations support different features of Term::ReadLine, we can interrogate the module to find out what features are actually supported. The Features method provides us with this information, as we briefly mentioned earlier. It returns a reference to a hash with the keys being the features supported:

#!/usr/bin/perl
# readlinefeatures.pl
use warnings;
use strict;

use Term::ReadLine;

my $term = new Term::ReadLine "Find Features";
my %features = %{$term->Features};

print "Features supported by ",$term->ReadLine," ";
foreach (sort keys %features) {
    print " $_ => $features{$_} ";
}

Let's execute this script with Term::ReadLine::Gnu (available from CPAN) installed and review its output:

> perl readlinefeatures.pl

Features supported by Term::ReadLine::Gnu
    addHistory =>    1
    appname =>    1
attribs =>    1
    autohistory =>    1
    getHistory =>    1
    minline =>    1
    newTTY =>    1
    ornaments =>    1
    preput =>    1
    readHistory =>    1
    setHistory =>    1
    stiflehistory =>    1
    tkRunning =>    1
    writeHistory =>    1

We should not confuse these features with callable methods. Some, though not all, of these features correspond to methods supported by the underlying library. Unfortunately, the feature names do not always match the names of the methods that implement them. For example, the method to add a line of history is addhistory, not addHistory.

We can use the feature list to check that a feature exists before trying to use it. For example, to change a terminal to use new filehandles, we could check for the newTTY feature, using it if present, and resort to creating a new object otherwise:

sub switch_tty {
    my ($term,*IN,*OUT) = @_;
    $features = $term->Features;
    if ($features->{newTTY}) {
        $term->newTTY(*IN,*OUT);
    } else {
        $name = $term->appname;
        $term = new Term::ReadLine $name,*IN,*OUT;
    }
    return $term;
}

Regardless of which features are actually supported, Term::ReadLine defines stub methods for a selected subset so that calling them will not cause an error in our program, even if they don't have any useful effect.

Setting the Prompt Style and Supplying Default Input

The readline method takes a prompt string as its argument. This string is printed to the screen using ornaments (if any have been defined) that alter the look of the prompt and the entered text. For example, GNU Readline underlines the prompt text by default.

Ornamentation can be enabled, disabled, or redefined with the ornaments method. Enabling and disabling the currently defined ornaments is achieved by passing 1 or 0 (a true or false value) to ornaments:

#!/usr/bin/perl
# ornament.pl
use warnings;
use strict;

use Term::ReadLine;

my $term = new Term::ReadLine "Ornamentation";

# disable ornaments
$term->ornaments(0);
my $plain = $term->readline("A plain prompt: ");
print "You entered: $plain ";

# enable default ornaments
$term->ornaments(1);
my $fancy = $term->readline("A fancy prompt: ");
print "You entered: $fancy ";

Alternatively, the current ornamentation can be redefined by passing four parameters containing terminal capabilities (as deduced by the Term::Cap module—see later) as a string. The first two are applied before and after the prompt, and the second two before and after the input text. For example:

# define ornaments (md = bold, me = normal)
$term->ornaments('md, me, ,'),
$userd = $term->readline("A user-defined prompt: ");
print "You entered: $userd ";

In this example, we have used md, which is the terminal capability code for bold, and me, which is the terminal capability code to return to normal. We don't want to change the input line, so we have left those two entries blank. If we have no termcap library, this will fail, since Term::Cap is used to determine how to handle ornaments. To enable the ornaments subroutine to work without generating a warning, add the following:

# disable warnings for platforms with no 'termcap' database
$Term::ReadLine::termcap_nowarn = 1;

The GNU version of readline supports a second optional parameter that contains the default input text. This is known as the preput text, and we can test to see if it is supported by checking for the preput feature. Since passing extra parameters is not an error, not checking is fine—we simply won't see the default text. Here is a short example of supplying some default text to the readline method:

#!/usr/bin/perl
# defaulttext.pl
use warnings;
use strict;

use Term::ReadLine;

my $term = new Term::ReadLine "Default Input";

my $input = $term->readline("Enter some text: ", "Default Text");
print "You entered: $input ";

If the preput text is supplied and the library supports it, the input line is automatically filled with the default text. The user can then either delete and replace or edit (because GNU readline supports in-line editing) the default text or just press Return to accept it.

Command-Line History

The Term::ReadLine module provides support for command-line history—that is, a record of what has been typed beforehand. This can be used to allow the user to step backward or forward through previous commands, typically using the up and down cursor keys. This functionality is provided automatically (assuming a library is present which supports it), so again we do not have to do anything special to provide it.

We can control the history several ways, however. First, we can lie about previously entered commands by using the addhistory method:

$term->addhistory("pretend this was previously entered text");

If we have the GNU readline library, we can also remove a line from the history by giving its line number to remove_history:

$term->remove_history(1);   # remove line 1 from history, GNU only

We can also, using the GNU library, retrieve the whole history as an array with GetHistory:

@history = $term->GetHistory;

We can then step through this array and pass the index numbers to remove_history if desired. Of course, to prevent the line numbers changing as we proceed, traversing the array in reverse order is recommended. For example, this loop traverses the history array, removing all lines that have less than three characters:

@history = $term->GetHistory;
# traverse in reverse order, to preserve indices in loop
foreach my $item (reverse 0..$#history) {
    $term->remove_history($item) if length($history[$item])<3);
}

We can actually do this automatically with the standard MinLine method, which should work regardless of the underlying library. Additionally, we can disable the history entirely by passing an undefined value to it:

$term->MinLine(3);   # only record lines of three plus characters
$term->MinLine(undef);   # disable history

The GNU readline library goes far beyond these features, however. It also provides support for editing, moving around, saving, loading, and searching the history. For a complete, if terse, list of available features, see the Term::ReadLine::Gnu documentation.

Word Completion

Some shell programs support the concept of completion, where the shell attempts to deduce the rest of a partially entered word from the first few letters. We can provide the same feature within our own Perl programs with either the GNU Readline library or the somewhat simpler (but less able) Term::Complete module.

This module supplies the Complete function, which takes a prompt and a list of words for matching against. This is an example of how we might use it:

#!/usr/bin/perl
# complete.pl
use warnings;
use strict;

use Term::Complete;

my @terms = qw(one two three four five six seven eight nine ten);
my $input = Complete("Enter some number words: ",@terms);

print "You entered: $input ";

Completion is triggered by pressing the Tab key. When this occurs, Term::Complete attempts to match the text entered so far against one of the words in the completion list. If there is a unique match, it fills in the rest of the word. For example, if we were to enter e and then press Tab, Term::Complete would automatically fill in ight for eight, since that is the only word that begins with e.

If there is not a unique match, then Tab will produce no useful effect. Instead, we can enter Ctrl-D to have Term::Complete return a list of valid matches. If we were to enter t then Ctrl-D, then the available completions two, three, and ten would be displayed, one per line:


Enter some number words: t^D
ten
three
two
Enter some number words: t

The Term::Complete module supplies several functions that allow various keys to be redefined, with the curious exception of Tab for completion. None of these functions are exported by the module, so we must access them via their full names. They are as listed in Table 15-4.

Table 15.4 Term::Complete Functions

Function Keystroke Action
Term::Complete::complete Ctrl-D List matching completions, if any.
Term::Complete::kill Ctrl-U Erase whole line.
Term::Complete::erase1 Delete Delete last character.
Term::Complete::erase2 Backspace Delete last character.

The Term::ReadLine::Gnu package provides a more comprehensive completion mechanism but depends on the GNU Readline library being installed on the system. This may be more trouble than we want to go to, especially on a non-Unix or non-Windows system. It is also a much more involved library to program and is beyond the scope of this book—consult the documentation and the manual page for the bash shell (which makes extensive use of GNU Readline) if available.

Writing to the Screen

Perl provides a lot of different approaches to writing to a terminal screen, from simply printing to standard output, through low-level terminal control modules like Term::Cap and the POSIX::Termios interface, to very high-level modules like the third-party Curses module. Somewhere in the middle we can find modules like Term::ANSIColor and the third-party Term::Screen, which provide a slightly simpler interface to the features of the low-level modules.

The Term::ANSIColor module handles the specific problem of using colors and other text attributes like blinking, bold, or underline. Other commands can be sent to the screen by interrogating the terminal capabilities with Term::Cap. However, since Term::Cap is a rather low-level module, the third-party Term::Screen module provides a few of these facilities in a more convenient form. If we plan to do a lot of screen output, however, such as writing a complete text-based GUI application, then we might want to look at the Curses module.

Terminal Capabilities

Terminal capabilities ultimately determine the number of ways we can talk to it. There are many different kinds of terminals, both real and simulated, each with its own particular range of features. Even if a terminal supports all the usual features, it may not do it in the same way as another. In order to make sense of the huge range of possible terminals and different terminal features and options, Unix machines make use of a terminal capability or termcap database. A given terminal has a terminal type associated with it. Interested applications can look up items in the database to find out how to tell the terminal to do things like move the cursor or change the color of text.

We send commands to terminals in the form of ANSI escape sequences, a series of characters starting with an escape (character 27, or e). To switch on blue text, for example, we could use

print "e[34m--this is blue text--e[0m ";

Of course, this relies on the terminal supporting ANSI escape sequences, which is likely on a Unix system but is not the case for a DOS shell—if characters like e[32m appear on the screen, it's a safe bet that ANSI isn't supported, so the rest of this discussion is likely to be academic.

Remembering that e[...m is the sequence for changing screen colors and that 34 is the number for blue is hardly convenient, however. Worse, while things like the color blue are standard across all terminals (all color terminals, that is), many other terminal capabilities vary widely in the precise escape sequences that control them. For that reason, rather than write escape sequences explicitly, we use Term::Cap to find them out for us.

Using Term::Cap

The Term::Cap module is an interface to the terminal capability or termcap database commonly found on Unix systems that allows us to issue commands to terminals based on what kind of terminal we are using. To use it, we create a terminal capability object using Term::Cap, then pass that object information about what we want to do, along with the filehandle of the terminal we want to do it on. In order to use the module, therefore, we first have to create a terminal capability object (or termcap object for short) that points to the entry in the termcap database that we want to use. We also need a termcap database for Term::Cap to work with, so again, this is academic for platforms that do not possess one.

Creating a Termcap Object

Using the Tgetent method, Term::Cap creates terminal capability objects. In order to use it, we must pass it a hash reference, which it blesses, populates with capability strings, and returns to us. In order to work out which entry to look up, Tgetent needs to know the terminal name, for example, ansi for a standard ANSI terminal, vt100 for a terminal that adheres to the VT100 standard, and so on. Unix shell tools like xterm use their own terminal mode, for example, xterm, which is a superset of the ANSI terminal that also knows a few things particular to living inside a window, such as resizing the screen.

In general, we want Term::Cap to look up the entry for whatever terminal it is our program happens to be running in, which it can normally deduce from the environment. To tell Tgetent to look at the environment, we pass it an anonymous hash containing a key-value pair of TERM and undef:

#!/usr/bin/perl
# anonhash.pl
use warnings;
use strict;

use Term::Cap;

# create a terminal capability object - warns of unknown output speed
my $termcap = Term::Cap->Tgetent({ TERM => undef });

print "Capabilities found: ",join(',',sort(keys %{$termcap}))," ";

Just to demonstrate what this actually does, we have looked at the hash that the termcap object actually is and printed out its keys. That's usually a rather rude way to treat an object, but it serves to illustrate our point. Run from a Unix xterm window, this program produces the following output:


OSPEED was not set, defaulting to 9600 at ./termcap1.pl line 7
Capabilities found:
OSPEED,PADDING,TERM,TERMCAP,_AL,_DC,_DL,_DO,_IC,_LE,_RI,_UP,_ae,_al,_am,_as,_bc,
_bl,_cd,_ce,_cl,_cm,_co,_cr,_cs,_ct,_dc,_dl,_do,_ei,_ho,_ic,_im,_is,_it,_k1,_k2,
_k3,_k4,_k5,_k6,_k7,_k8,_k9,_kI,_kN,_kP,_kb,_kd,_ke,_kh,_kl,_km,_kr,_ks,_ku,_le,
_li,_md,_me,_mi,_mr,_ms,_nd,_pc,_rc,_sc,_se,_sf,_so,_sr,_ta,_te,_ti,_ue,_up,_us,
_xn,_xo


Note Note that we avoided actually printing out the values of the hash, which contain the ANSI escape sequences themselves, because printing them would cause the terminal to react to them. This would likely confuse it and probably leave it in an unusable state. Escape sequences are not interesting to look at in any case; the whole point of the termcap database is that we don't need to look at them directly.


To safely dump out the values of the capabilities, we can substitute the escape characters with a plain text equivalent like <ESC> that will not be interpreted by the terminal. For the terminally curious (so to speak), this loop will achieve that:

foreach my $cap (sort keys %$termcap) {
    my $value=$termcap->{$_};
    $value =˜ s/e/<ESC>/g;
    print "$cap => $value ";
}

Disregarding the warning (which we'll come to in a moment) and the uppercased entries, each of the underscore prefixed entries is a capability of this terminal, and the value of that entry in the hash is the ANSI escape sequence that creates that effect. By using this object, we can generate valid escape sequences to do the things we want without worrying about what the correct sequence is for any given terminal. In fact, we can disregard the terminal type altogether most of the time, which is the idea, of course.

If we want to pretend we're an ANSI terminal rather than anything else, or we just happen to know that the terminal we're using (remembering that we could be in some sort of shell window that doesn't correspond to any specific terminal) happens to be ANSI compatible, we could write

$termcap = Term::Cap->Tgetent({TERM =>'ansi'});

Tgetent also seeks the output speed of the terminal because terminal capabilities may be defined to be dependent on the speed of the connection. If it isn't told, it will complain with a warning but retrieve the terminal capability information anyway based on an assumed speed of 9600 bps. In order to silence this, we can feed it a speed from the POSIX module:

#!/usr/bin/perl
# speed.pl
use warnings;
use strict;

use POSIX;
use Term::Cap;

# set the line speed explicitly - but 'POSIX::B9600' may not be defined
my $termcap1 = Term::Cap->Tgetent({
   TERM => undef,
   OSPEED => POSIX::B9600
});

Better, we can use the POSIX::Termios package to ask the terminal directly, then feed that value to Term::Cap:

# interrogate the terminal for the line speed, no need for a constant
my $termios = new POSIX::Termios;
$termios->getattr(fileno(STDOUT));
my $termcap2 = Term::Cap->Tgetent({
   TERM => undef,
   OSPEED => $termios->getospeed
});

The POSIX::Termios is a very low-level way to control a terminal directly. Modules like Term::ReadKey use it, along with Term::Cap, behind the scenes to perform many of their functions. We can also use it directly, and we will cover it in more detail later. Of course, this might seem a pointless exercise if we know we will be talking to a screen or a shell window that doesn't have a line speed, but Term::Cap is not able to assume that, and it can't determine this from the terminal type, since that's just an emulation. Additionally, we should not assume that our application won't one day run on a real terminal on the end of a real serial connection.

Clearing the Screen, Moving the Cursor, and Other Tricks

Given an object representing the terminal's capabilities, we can use it to make the terminal do things. One of the first obvious things to do is clear the screen. The terminal capability code for that is cl, so we feed that to the Tputs method of Term::Cap along with the number 1 to tell Tputs to generate a code that does clear the screen (rather than doesn't, strangely—the command needs a parameter), and the filehandle of the terminal:

$termcap->Tputs('cl', 1, *STDOUT);

Similarly, to move the cursor about, we use the cm (cursor move) capability with the Tgoto method. For instance, to move the cursor to position x = 3 and y = 5, we would use

$termcap->Tgoto('cm', 3, 5, *STDOUT);

Then, to write some text in bold, we could use

$termcap->Tputs('md', 1, *STDOUT);
print "Bold Text";

We can use any capability of the terminal in this way, so long as we have a thorough knowledge of terminal capability codes. Since that is rather a lot of work to go to, we might be given to wonder if someone has already done all this work and parceled up the most common features for us. Fortunately, someone has—see Term::Screen and the Curses module later in the chapter.

Writing in Colors

Most terminal emulations support the ANSI standard for escape sequences that control aspects of the terminal such as cursor position or the appearance of text. Unlike a lot of other escape sequences, the color and text attribute sequences are fairly standard across all terminals. Instead of writing ANSI sequences by hand or resorting to Term::Cap, we can make use of the Term::ANSIColor module from CPAN instead.

This module works by defining an attribute name for each of the escape sequences related to text representation, such as bold for bold text and on_red for a red background. We can use these attributes to create text in any style we like, with the twin advantages of simplicity and legibility.

There are two basic modes of operation. The first is a functional one, using the subroutines color and colored to generate strings containing ANSI escape sequences, passing the names of the attributes we want to create as the first argument. The second uses constants to define each code separately, and we will see how to use it shortly. Before we look at a short example of using the Term::ANSIColor module, a short word of warning. The effects of using the module will be dependent on the settings of your terminal. For example, if your terminal is set to print red on white, the following example will exhibit no noticeable difference. In the meantime, here is a short example of how color can be used to generate red text on a white background:

#!/usr/bin/perl
# text.pl
use warnings;
use strict;

use Term::ANSIColor;

print color('red on_white'),'This is Red on White';

The argument to color is a list of attribute names, in this case the attributes red and on_white to produce red-on-white text. Here we have passed them as space-separated terms in a string, but we can also supply the attributes as a list:

@attributes = 'red';
push @attributes, 'on_white';
print color(@attributes), 'Or supply attributes as a list...';

Note that color does not print anything by itself. It returns a string to us and leaves it to us to print it or otherwise use it. Similarly, here is how we can produce bold underlined text:

print color('bold underline'),'This is bold underlined';

We can generate any of the color sequences supported by the ANSI standard using Term::ANSIColor, all of which have been given convenient textual names for easy access. Table 15-5 lists all the names defined by Term::ANSIColor. Note that several attributes have synonyms, and that case is irrelevant—RED works as well as red.

Table 15.5 Term::ANSIColor Attributes

Name Action
clear, reset Reset and clear all active attributes.
bold Start bolding.
underline, underscore Start underlining.
blink Start blinking.
reverse Reverse both foreground and background colors logically. If no colors are set, invert white on black to black on white.
concealed Conceal text.
black, red, green, yellow, blue, magenta Put text into given color. May be combined with a background color.
on_black, on_red, on_green, on_yellow
on_blue, on_magenta
Put text background into given color. May be combined with a foreground color.

The problem with color is that it does not switch off what it switches on. If we run the preceding programs, then whatever escape sequences were in effect at termination continue on after the end of the program. This means that without special care and attention, we can end up typing bold underlined green on cyan text at our shell prompt, or even worse. In order to prevent this, we need to remember to clear the active attributes before we finish:

print color('red on_white'),'This is Red on White', color('reset'),

This is another case where an END block is potentially useful, of course:

# make sure color is switched off before program exits
END { print color('reset') }

However, passing reset to the end of all our print statements is clumsy. The colored function solves this problem by automatically adding escape codes to clear the active attributes to the end of the returned string, at a slightly higher cost in terms of characters output. With colored, we don't need to reset the screen ourselves, so the following is visually equivalent but simpler to write than the previous example:

print colored('This is Red on White', 'red on_white'),

The text to be encapsulated comes first, so we can still pass attributes as a list if we like:

my @attributes = ('red', 'on_white'),
print colored('Or as a list of attributes...', @attributes);

It is important to realize, however, that reset (or the synonymous clear) resets all active ANSI sequences, not just the ones we issued last. This is more obvious with color than colored, which might give the impression that we can switch from green-on-black to red-on-white and back to green-on-black using a reset, which is not the case:

print colored('green on black, colored('red on white'), 'and back', 'green'),

In this example, the and back will be white on black, not green, because the reset generated by the internal call to colored overrides the original color setting and then resets it. The reset added by the outer call to colored is therefore redundant.

The function colored has been written with multiline text in mind. Ordinarily, it places codes at the beginning and end of the string passed as its first argument. If, however, the package variable $Term::ANSIColor::EACHLINE is set to a string of one or more characters, then colored splits the line on each occurrence of this separator and inserts codes to clear and then reestablish the passed attributes on either side of it. The most obvious use of this feature is, of course, to set the separator to , as in

#!/usr/bin/perl
# multicolor.pl
use warnings;
use strict;

use Term::ANSIColor;

$Term::ANSIColor::EACHLINE = " ";

my $text = "This is an example of multiline text coloring ";

print colored($text, 'bold yellow'),

There is no reason why we should just apply this to lines. As a slightly different example of what we can do, here is a way to display binary numbers with the 1s emphasized in bold. It works by making the separator 0 and using bold cyan as the attribute:

#!/usr/bin/perl
# boldbin.pl
use warnings;
use strict;

use Term::ANSIColor;

my $number = rand 10_000_000;
# my $bintext = sprintf '%b', $number;   # if Perl >=5.6
my $bintext = unpack 'B32', pack('d', $number);

$Term::ANSIColor::EACHLINE ='0';

print colored($bintext, 'bold cyan'),

The second mode of operation bypasses color and colored entirely by importing symbols for each attribute directly into our own namespace with the :constants label. To use this mode, we need to import the constants from the module by using the :constants tag:

use Term::ANSIColor qw(:constants);

By doing this, we create a host of constant subroutines, one for each attribute. The constants are all uppercase, so instead of calling color or colored, we can now print out attributes directly. Here is red-on-white text, generated using constants:

#!/usr/bin/perl
# constants.pl
use warnings;
use strict;

use Term::ANSIColor qw(:constants);

print RED, ON_WHITE, "This is Red on White", RESET;

The values of these constants are strings, so we can also concatenate them with the . operator:

$banner = BLUE.ON_RED.UNDERSCORE."Hello World".RESET;
print $banner," ";

As these examples show, constants suffer from the same problem as color, in that we have to explicitly switch off whatever attributes we switch on. However, if we leave out the commas and set the variable $Term::ANSIColor::AUTORESET, then the module will automatically figure things out and add the reset for us:

# automatically append reset code
$Term::ANSIColor::AUTORESET = 1;

# look, no commas!
print RED ON_WHITE "This is Red on White";

This is clever, but mysterious. We might be given to wonder how this works, since this doesn't look like a legal print statement. The answer is that the "constants" are not strings but subroutines. Unlike constants declared with the use constant pragma, these subroutines take arguments, and without parentheses they behave like list operators, absorbing whatever is to their right. With the commas in place, each subroutine is called without arguments. Without the commas removed, each is called with the rest of the statement as its argument and returns a string based on its own name prefixed to whatever the rest of the line returned. By working out if they were called with or without arguments, each subroutine can work out whether it needs to append a reset or not. Perl evaluates this statement right to left, so the argument to RED is the return value from ON_WHITE.

Having explained the way this works, we might choose to avoid this syntax, since RED in this example looks exactly like a filehandle to the reader (Perl knows it is a subroutine, of course). As an alternative, we can define the output record separator to produce a similar effect and retain the commas:

local $/ = RESET;   # automatically suffix all 'print' statements with a reset
print RED, ON_WHITE, "This is Red on White";

The advantage of the "constants" approach is that Perl can check our code at compile time, rather than at run time, since a misspelled constant will cause a syntax error unlike an attribute string passed to color or colored. The disadvantage is that we end up with a lot of attribute constants in the namespace of our code, which isn't always desirable.

Higher-Level Terminal Modules

The Term::Cap module gives us the ability to send a range of commands to terminals without having to worry about what kind of terminal we are actually talking to, although it is a little too low level for convenient use. Fortunately, there are several third-party solutions that build on basic terminal capabilities to make our job easier. Just two are mentioned here—Term::Screen, which is a friendly wrapper around Term::Cap that implements many of the most common functions in an easy-to-use form, and Curses, a terminal programming library vast enough that it has entire books dedicated to it.

Term::Screen

The third-party Term::Screen module (available at the nearest CPAN mirror) encapsulates a lot of the most common terminal functionality into a simpler and easy-to-use form, if we want to spend significant time exerting control over the terminal screen and want to avoid writing all our own Term::Cap subroutines. Although it is not a standard module, it does use Term::Cap to do all the actual work, so wherever Term::Cap works, Term::Screen ought to. It actually uses the Unix stty command to do the dirty work, so it won't work for other platforms. However, it is designed to be subclassed, so an MS-DOS module is a distinct possibility, if not supplied (at least, not yet).

As an example, here is how we can clear the screen and move the cursor with Term::Screen. Notice that it is a lot simpler and more legible than the Term::Cap version we saw earlier:

#!/usr/bin/perl
# movecurs.pl
use warnings;
use strict;

use Term::Screen;

my $terminal = new Term::Screen;
$terminal->clrscr();
$terminal->at(3,4);
$terminal->puts("Here!");
$terminal->at(10,0);
$terminal->puts("Hit a key...");
my $key = $terminal->getch();
$terminal->at(10,0);
$terminal->puts("You pressed '$key'");
$terminal->at(11,0);

This example also demonstrates the getch method, an alternative to using ReadKey from the Term::ReadKey module that we covered earlier. getch is more convenient, since it doesn't involve messing about with the terminal read mode, but of course it requires having Term::Screen installed. Of course, Term::ReadKey is not a standard module either.

As an added convenience, Term::Screen's output methods are written so that they return the terminal object created by new Term::Screen. This means they can be used to call other methods, allowing us to chain methods together. For example, we could have concatenated much of the preceding example script into

$terminal->clrscr->at(3,4)->puts("Here!")->at(10,0)->puts("Hit a key...");

The Term::Screen module provides a toolkit of methods for working with terminals. Table 15-6 summarizes these, along with the arguments they take.

Table 15.6 Term::Screen Methods

Name Action
resize (rows, cols) Resize the screen, in the same way that Term::ReadKey's SetTerminalSize does.
at (row, col) Move the cursor to the given row and column of the screen.
normal Set the text style to normal. For example, $terminal->normal() puts the terminal into normal.
bold Set the text style to bold.
reverse Set the text style to reverse.
clrscr Clear the screen and move the cursor to 0,0.
clreol Clear from the cursor position to the end of the line.
clreos Clear from the cursor position to the end of the screen.
il Insert a blank line before the line the cursor is on.
dl Delete the line the cursor is on. Lower lines move up.
ic (char)
exists_ic
Insert a character at the cursor position. Remainder of line moves right. The method exists_ic returns true if this actually exists as a termcap capability, false otherwise.
dc
exists_dc
Delete the character at the cursor position. Remainder of line moves left. The method exists_dc returns true of this actually exists as a termcap capability, false otherwise.
echo
noecho
Enable or disable echoing of typed characters to the screen, in the same way that Term::ReadKey's ReadMode does.
puts (text) Print text to the screen. Identical to print except that it can be chained with at as illustrated previously.
getch Return a single character in raw mode.
key_pressed See if a key has been pressed without actually reading it.
flush_input Clear any current data in the input buffer.
stuff_input Insert characters into the input of getch for reading. Note that this only works for getch—it does not put characters into the real input buffer.
def_key (cap, keycode) Define a function key sequence. char is the character generated by the definition (and read by getch). keycode is what the function key actually generates. The definition causes keycode to be translated into char by the terminal.
get_fn_keys Define a default set of function key definitions.

All of these methods are fairly self-explanatory, with the exception of def_key. This programs the function keys (which includes the cursor keys and keys like Insert and Home) of a keyboard to return a given character, if the terminal is programmable. The keycode is a particular escape sequence such as e[11˜ for function key one or e[A for the up cursor key, and the cap is a terminal capability such as ku for cursor up. That is, to swap the up and down cursor keys, we could write

$terminal->def_key('ku', "e[B˜");
$terminal->def_key('kd', "e[A˜");

A list of common escape sequences generated by function keys can be found in the Term::Screen module itself, inside the get_fn_keys method. As well as being informative, when called this also resets the definitions to their defaults, handy if we just swapped our cursor keys around:

$terminal->get_fn_keys();

This is certainly useful, but it does have one liability—with the exception of the ic and dc methods, it assumes that the given capability exists, and does not check for it. However, since all the capabilities it supports are fairly standard, it is likely that they will work as advertised.

If even Term::Screen is not up to the task, we might consider turning to the very capable and feature-rich Curses module. However, Curses depends on an implementation of the curses library on our platform—Term::Screen only needs Term::Cap, which is a standard Perl library module.

The Curses Library

The Curses library is the granddaddy of all screen manipulation libraries. It supports everything we have discussed so far, including all the tricky details of terminal capabilities, mouse input, and text-based windows as well as many other features. Most Unix platforms have a Curses library installed, and several ports exist to other platforms such as Windows, including the free GNU ncurses implementation.

Perl supports Curses libraries through the Curses module, available as usual from any CPAN mirror. As well as the Curses library, the module also supports the extension Panel library, which adds overlapping window support to Curses. Assuming that we have these libraries and the Curses module installed, we can write windowed terminal applications using Curses. The actual features of a given Curses library depend on the implementation (early implementations do not support Windows, for example), but all libraries support the same general set of operations.

The C interface to Curses tends to support four different subroutines for each function: the basic feature, the basic feature within a window, the basic feature combined with a cursor movement, and the basic feature combined with a cursor movement within a window. For example, the addstr subroutine, which writes text to the screen, comes in four different flavors, each with a different set of arguments, as these four different C statements demonstrate:

addstr("Text..."); /* write text at cursor */
mvaddstr(3, 5 ,"Text"); /* move cursor, write text */
waddstr(window, "Text..."); /* in window write text at cursor */
mvwaddstr(window, 3, 5, "Text"); /* in window move cursor, write text */

The Curses module simplifies this by providing an object-oriented interface for programming windows and by wrapping all of the variants into one Perl subroutine. In order to work out which one we actually want, the Curses module merely inspects the number of arguments and their type.

Since Curses is a huge library, we cannot hope to document it all here. Instead, we will look at a few simple examples that demonstrate the basics of writing Curses applications in Perl.

A Simple Curses Application

A simple Curses program starts with initscr, which initializes the screen for use by Curses. After this, we can configure the terminal in any way we like, for example, to switch echoing off. We can send output to the screen with addstr, followed by refresh to tell Curses to actually draw it. Finally, when we are finished, we call endwin to reset the terminal for normal use again. Here is a short example program that lists environment variables one by one and shows the basic structure of a Curses program:

#!/usr/bin/perl
# curses1.pl
use warnings;
use strict;

use Curses;

initscr();   # initialize the screen to use curses
cbreak();   # go into 'cbreak' mode
noecho();   # prevent key presses echoing
# move and addstr as separate actions
attron(A_BOLD|A_UNDERLINE);
move(2,5);
addstr("Environment Variable Definitions:");
attroff(A_BOLD|A_UNDERLINE);
move(15,5);
addstr("Hit a key to continue, 'Q' to finish...");

# enable color
start_color();

# define some color pairs
init_pair(1, COLOR_WHITE,COLOR_BLACK);
init_pair(2, COLOR_YELLOW,COLOR_BLACK);
init_pair(3, COLOR_BLACK,COLOR_CYAN);

OUTER: while (1) {
    foreach (sort keys %ENV) {
       attron(COLOR_PAIR(3));  # set black-on-cyan
       addstr(5,8," $_ ");     # move and write variable name
       clrtoeol();             # delete anything beyond it
       attron(COLOR_PAIR(2));  # set yellow-on-black
       addstr(6,8,$ENV{$_});   # move and write variable value
       clrtoeol();             # delete anything beyond it
       move(9,79);             # move the cursor out of the way
       refresh();              # send output to the screen
       last OUTER if (lc(getch) eq 'q'),
   }
}

attron(COLOR_PAIR(1)); # set white-on-black
move(9,5);
addstr("All Done");
refresh();             # send output to the screen

END { endwin; }        # end Curses properly even on abnormal termination

This is what this application looks like:

image

There are a few points to note about this application:

  • First, every Curses program that does not create windows (see later) must start with initstr. Similarly, all Curses programs must end with endwin to reset the terminal into a usable state afterwards.
  • Second, nothing appears on the screen until we call refresh. Up until this point, Curses carries out all the changes in an internal buffer, recording what parts of the screen have been changed. When we call refresh, all the changes—and only the changes—made since the last refresh (or initscr) are sent to the terminal in one go. This allows Curses to make efficient use of bandwidth in situations that involve a slow serial or network connection.
  • Third, if no coordinates are specified to subroutines that output to the screen, then the current cursor position is used instead. This can be seen earlier, for example, in the move and addstr pairs.
  • Fourth, we can enable and disable text attributes like bold or underline by using attron and attroff. The different attributes can be numerically ored together to switch several attributes on or off together.
  • Fifth, to use colors in Curses, we need to first call start_color. Curses handles colors in pairs, setting both foreground and background at once. We define color pairs using init_pair, which takes an arbitrary pair number and a foreground and background color. These pairs can be used with attorn (and attroff) using the COLOR_PAIR macro. The whole interface is more sophisticated than this quick example, of course.
  • Last, as we discussed earlier, most Curses features take variable numbers of arguments and determine what to do based on how many there are. To write at a given position of the screen, we can therefore use move and then addstr with one argument or supply the coordinates and the string as three arguments to addstr. The clrtoeol subroutine also accepts coordinates, though we haven't used them here.

Now that we have seen how to create a Curses application that manages the whole screen, we can look at creating and managing windows in Curses.

Curses Windows

The Curses module has two modes of operation. The simple mode, which we have just seen, is suitable for full-screen programs where the entire screen is used as a single window. The object-oriented version is useful if we are programming Curses windows, where each window is effectively a small screen in its own right. In this case, we do not use initscr but create and call methods on window objects. The homogenization of the Curses module interface means that we still use the same names for the methods as we did for the functions. Here is how we create a window in Curses:

# create a new 20x10 window, top left corner at coordinates 5,5
my $window = new Curses (10, 20, 5, 5);

Once we have created a window, we can move the cursor around in it, write to it, refresh it, and set attributes on it, just as we can with the whole screen. Here is a short program that creates a window, writes to it, and then moves it across the screen each time we press a key:

#!/usr/bin/perl
# curses2.pl
use warnings;
use strict;

use Curses;

# create a 3x20 window with top corner at 0,0
my $window = new Curses(3, 20, 0, 0);

cbreak();   # go into 'cbreak' mode
noecho();   # prevent key presses echoing
curs_set(0) # hide the cursor

# define some colors
start_color();
init_pair(1,COLOR_YELLOW,COLOR_BLUE);
init_pair(2,COLOR_GREEN,COLOR_BLUE);
# put something in the window
$window->attron(COLOR_PAIR(1));
$window->clear();
$window->box(0,0);
$window->attron(COLOR_PAIR(2));
$window->addstr(1, 1, " This is a Window ");
$window->attroff(COLOR_PAIR(2));

$window->refresh();
getch;

foreach (5..25) {
    $window->mvwin($_, $_);
    $window->refresh();
    last if (lc(getch) eq 'q'),
}

END { endwin; }   # end Curses properly even on abnormal termination

Another Curses feature used for the first time in this program is the curs_set function, which controls the visibility of the cursor. As this is a windowed application, we pass it a value of zero to render the cursor invisible. This is what the application looks like after we have entered a few key presses:

image

One irritating aspect of this application is that it leaves a trail of old window borders across the screen as the window moves. To avoid this trailing effect, we would need to delete the old window first—fortunately Curses's refresh mechanism means that blanking the whole block and then replacing the window in its new position is as efficient as trying to work out the parts that have changed. However, this doesn't take account of the possibility that something else might be underneath the window. To solve that problem, we can make use of Panels, an extension to Curses frequently supplied as a secondary library that provides intelligent windows that can stack and overlap.

Third-Party Extensions to Curses

Curses is a popular library and has been around for some time now. Consequently, a lot of other libraries and Perl modules have been written that build on Curses to provide everything from simple menus to complete GUI toolkits. Checking CPAN for modules providing more advanced Curses-based features is well worth the time before embarking on a Curses-based project—the Curses::Forms module (for data entry) and Curses::Widgets module (for button bars, dialog boxes, and other "GUI" components) in particular. Other Curses modules include Cdk, which uses a third-party C library built on Curses, and PV.

Programming the Terminal Directly with POSIX

For occasions when modules like Term::ReadKey won't do, and we have to program the terminal directly, we can use the POSIX module. However, while many platforms support some POSIX compliance, they may not be able to handle programming the terminal directly. On Unix, these techniques will usually work; other platforms may not be so fortunate.

Whereas Term::Cap interrogates the termcap database, POSIX::Termios is concerned with the terminal itself. That is, Term::Cap can tell us what the Delete key ought to be; POSIX::Termios can tell us what it actually is right now. To use it, we first need to create a termios object, then associate that object with the filehandle of the terminal we want to manipulate:

use POSIX;
my $termios = new POSIX::Termios;

We then tell the object to read the attributes of a terminal by passing a filehandle number to getattr. This can be either a simple integer such as 0 for STDIN, 1 for STDOUT, or 2 for STDERR, or a file number extracted from a filehandle with fileno:

# three different ways to get the attributes of STDIN
$termios->getattr;
$termios->getattr(0);
$termios->getattr(fileno(STDIN));

Once we have the attributes in our termios object, we can retrieve them with functions like getospeed (for the terminal output speed) and getcc (for the control characters) and set them with functions like setospeed and setcc. All this just changes values in a hash, however. Once we have finished modifying the attributes, we set them on a filehandle by using setattr. Here is a short program that redefines a few properties of STDIN and STDOUT by way of an example:

#!/usr/bin/perl
# termios.pl
use warnings;
use strict;

use POSIX qw(:termios_h);

my $stdin = fileno(STDIN);
my $stdout = fileno(STDOUT);

print " Interrogating STDIN: ";
my $termios_stdin = new POSIX::Termios;
$termios_stdin->getattr($stdin);

# redefine the erase (delete) key
print " Erase key is ", $termios_stdin->getcc(VERASE), " ";
print "Set Erase to Control-D: ";
$termios_stdin->setcc(VERASE,4);
print " Erase key is ", $termios_stdin->getcc(VERASE), " ";

# set the terminal to no-echo
my $lflag = $termios_stdin->getlflag;
printf " Local flag is %b ", $lflag;
# Perl<5.6: print " Local flag is ",unpack("B16",pack('n',$lflag))," ";
# print "Set Terminal Mode to Noecho ";
$termios_stdin->setlflag($lflag & ˜(ECHO | ECHOK));
printf " Local flag is %b ",$termios_stdin->getlflag;

# Perl<5.6: print " Local flag is ",
# unpack("B16",pack('n', $termios_stdin->getlflag))," ";
# set changes on STDIN
print "Setting STDIN from termios object ";
$termios_stdin->setattr($stdin,POSIX::TCSANOW);

# restore original local flag (enable echo)
$termios_stdin->setlflag($lflag | ECHO | ECHOK);
printf " Local flag is %b ",$termios_stdin->getlflag;
# Perl<5.6: print " Local flag is
# ",unpack("B16",pack('n', $termios_stdin->getlflag))," ";
print "Setting STDIN from termios object ";
$termios_stdin->setattr($stdin, POSIX::TCSANOW);

print " Interrogating STDOUT: ";
my $termios_stdout = new POSIX::Termios;
$termios_stdout->getattr($stdout);
my $old_stdout=new POSIX::Termios;
$old_stdout->getattr($stdout);
# set the output speed
print " Output speed is ",$termios_stdout->getospeed," ";
print "Set speed to 9600 bps: ";
$termios_stdout->setospeed(POSIX::B9600);
print " Output speed is ", $termios_stdout->getospeed," ";

# set changes on STDOUT
print "Setting STDOUT from termios object ";
$termios_stdout->setattr($stdout, POSIX::TCSANOW);

When run, this script should produce output similar to the following:


Interrogating STDIN:
        Erase key is 127
Set Erase to Control-D:
        Erase key is 4
        Local flag is 1000101000111011
Set Terminal Mode to Noecho
        Local flag is 1000101000010011
Setting STDIN from termios object
        Local flag is 1000101000111011
Setting STDIN from termios object

Interrogating STDOUT:
        Output speed is 15
Set speed to 9600 bps:
        Output speed is 13
Setting STDOUT from termios object

For more information on the POSIX module and in particular the subroutines and arguments of the POSIX::Termios module, consult the system POSIX manual page and the Perl documentation under perldoc POSIX.

Summary

In this chapter, we saw how to communicate with a terminal, real or virtual, and learned how to determine whether a script is interactive. Controlling terminal input using the Term::ReadKey module was covered, as well as how to read individual characters from the keyboard (or any input device) interactively, without waiting for the Return key to be pressed. After this, we moved on to reading complete lines and looked at the various ways the Term::ReadLine module expands the capabilities of Perl's built-in readline operator to handle passwords and invisible input, serial connections, line ending translation, and control characters. We also looked at interrogating the terminal to discover its size.

The latter part of the chapter discussed terminal capabilities and the creation of terminal objects, their supported features, and maintaining a history of command lines. We also looked at the Term::Screen module, saw how to write in colors and use ornaments, and then looked at the Curses library, the basis for many terminal-oriented applications. Finally, we explored ways to program a terminal directly via POSIX, when no higher-level interface can do the job.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset