CREATING TEXT MODE
USER INTERFACES WITH PERL
In the late 1997, while I was developing two console based applications,
TAL and ELM-TAG, on Solaris based systems, I ended
up creating a set of useful functions for text mode screen manupulation.
Recently, I decided to post them to my website hopeing that they may be
useful for someone else.
There are many other ways to create fancy text mode user interfaces with
Perl using the off-the-shelf packages specialized for this purpose.
However, if you do not want to carry huge tar balls with your tiny
Perl scripts, I believe that the code endorsed in this article will
provide you everything you would need.
In the sections below, I will briefly go trough the fundemental components
necessary to facilitate user computer interaction in text mode, while
providing code sniplets for each.
1. Clearing the Screen or a Single Line
In order to clean the screen or just a single line, we will use the
following function. Our function, svid/2, is designed for
multi-purposes so please keep in mind that we will re-use that in other
sections as well.
# terminal controls
%scr = (
'f' => 3,
'b' => 4,
'black' => 0,
'red' => 1,
'green' => 2,
'yellow' => 3,
'blue' => 4,
'magenta' => 5,
'cyan' => 6,
'white' => 7,
'normal' => 0,
'bold' => 1,
'reverse' => 7,
'invisible' => 8,
'clear' => '2J',
'clrline' => 'K',
'savepos' => 's',
'returnpos' => 'r',
'mvup' => 'A',
'mvdn' => 'B',
'mvfr' => 'C',
'mvbk' => 'D'
);
sub svid # clr,(f/b) or mode,x
{
print "\x1B[$scr{$_[1]}$scr{$_[0]}m";
}
The scr variable defines a map of flags that we could use in
conjunction with svid/2 function. Here is the function calls you
could use:
svid(clear); # will clear the entire screen
svid(clrline); # will clear the current line
2. Changing the Foreground and Background Color
In order to change the foreground and background color we will use the
svid/2 function which is given in the section above. The
scr variable provides a list of colors that we could use. Here is
the function calls you could use:
svid(red); # will change the foreground to red
svid(yellow,red); # will change the foreground to yellow
# and background to red
3. Using Normal and Reverse Modes Instead of Colors
When you are not sure about the capabilities of the terminal in which your
Perl script will opperate, then you could relay on Normal
and Reverse screen modes. Reverse mode is just reverses the
foreground and background of Normal mode, which provides a medium to
highlight something on the screen, such as the currently selected menu item,
etc.
svid(reverse); # switch to reverse mode
... # print something
svid(normal); # switch back to normal mode
4. Changin the Line Style
You may sometimes need to change the line style, such as making it bold,
or making it invisible when user enters a password. Here is the example
function calls that you could use to accomplish that.
svid(bold); # switch to bold mode
... # print something
svid(invisible); # switch to invisible mode
... # print something
svid(normal); # switch back to normal mode
5. Moving the Cursor to a Location (Low Level Fashion)
Being able to relocate the cursor to a certain location at any time is one
of the most important requirement while creating text mode user interfaces.
In this section, I will show you the example function calls for moving the
cursor in a low level fashion. These function calls are useful, when you do
not know the current position of the cursor. The applications, who do not
clear the screen at the beginning, will mostly relay on using these function
calls.
svid(mvup); # moves to the upper row
svid(mvdn); # moves to the lower row
svid(mvfr); # moves forward one column
svid(mvbk); # moves backward one column
6. Moving the Cursor to a Location (High Level
Fashion)
It may become very hard to relocate the cursor between distant positions
using the function calls given above. You could also use the following
function to relocate the cursor to a given row and column directly.
sub sgoto # column,row
{
print "\x1B[$_[1];$_[0]H";
}
Here is the example function call you would use:
sgoto(5,10); # moves the cursor to 5th column of 10th line
7. Using Anchors in Text Mode
It is very useful to store the current position of the cursor in a safe
place and being able to go back. Here is the example function calls that you
would use for that:
svid(savepos); # save the current position or cursor
sgoto(5,10); # moves the cursor to 5th column of 10th line
... # print something
svid(returnpos); # returns back to the previous position
8. Getting the Dimensions of the Screen
Rather than assuming that the screen will always have the dimensions of
80x25, it may be very beneficial to learn the actual dimensions of the screen
and re-organize the user interface to use the available screen efficiently.
You can use the function given below to detect the dimensions of the screen
in run-time. Due to the differences between Operating Systems, this function
may not operate on every platform. The supported and tested platforms are
AIX, FreeBSD, Linux, HP-UX, DEC OSF, Sun Solaris (SunOS), SCO Unixware,
SGI IRIX, NEXT, and Ultrix. If you get the function working on
a platform not listed here, please let me know.
%os_tiocgwinsz= (
'aix' => 0x40087468,
'freebsd' => 0x40087468,
'linux' => 0x005413,
'hpux' => 0x4008746b,
'dec_osf' => 0x40087468,
'solaris' => 0x005468,
'unixware' => 0x005468,
'irix' => 0x40087468,
'next' => 0x40087468,
'ultrix' => 0x40087468
);
sub sgetwinsz
{
my ($rep_key, $rep, @rep_decoded);
if ($os_tiocgwinsz{$^O} && ioctl(STDIN,$os_tiocgwinsz{$^O},$rep))
{
$rep_key = "ssss";# 4 short
@rep_decoded = unpack($rep_key,$rep); # 0 row 1 col
}
return @rep_decoded;
}
The function getwinsz/0 returns an array consisting of two
elements. The number at index 0 is the available number of rows, the number
at index 1 is the available number of columns. In other words, on a typical
console screen you should get the array { '25' , '80' } as a response.
9. Detecting Screen Dimension Changes During Run-Time
Eventough you check the dimensions of the screen when your application
starts, those dimensions may change during run-time if your application is
running on a virtual terminal window, such as xterm, PuTTY, ssh
clients, etc. You can register a signal listener in your Perl
application to detect any changes during run-time and update your user
interface accordingly. Here is what you need to add to your application:
sub CalibWinsz
{
my @dimensions = sgetwinsz;
paintUI (\@dimensions);
}
$SIG{'WINCH'} = \&CalibWinsz
When a Window Changed signal received by your application, the
CalibWinsz/0 function gets executed. CalibWinsz/0 function
first learns the new dimensions of the screen and re-paint the user interface
based on those numbers.
10. Capability of Using Special Keys (such as arrows)
Conceptual keys, such as arrows, Page UP, Page Down, Home,
Insert, etc. are the fundementals of user interfaces. In this sections I
will provide some code sniplets to demonstrate how those keys could be used
in your Perl application.
sub scbreak # on/off
{
if ($_[0] eq 'off')
{
if ($BSD_STYLE)
{
system "stty cbreak </dev/tty >/dev/tty 2>&1";
}
else
{
system "stty", '-icanon', 'eol', "\001";
}
system "stty -echo";
}
else
{
if ($BSD_STYLE)
{
system "stty -cbreak </dev/tty >/dev/tty 2>&1";
}
else
{
system "stty", '-icanon', 'eol', '^@';
}
system "stty echo";
}
}
We need to use the function given above to set the way our application
receives the special key codes. The example code give below will help you to
understand it much better:
scbreak(off); # turn the break off
while(1)
{
my $key = getc(STDIN); # get the pressed key
$key = getc(STDIN);
if ($key == 27) # catch escape codes
{
$key = getc(STDIN);
if ($key =~ /(\[|O)/)
{
$key = getc(STDIN);
}
}
# you can start placing your if statements
# for keys after this line
if ($key =~ /(B|r)/) {} # key is Down Arrow
if ($key =~ /(A|x)/) {} # key is Up Arrow
if ($key eq '5') {} # key is Page Up
if ($key eq '6') {} # key is Page Down
}
scbreak(on); # turn the break on
To learn the value of other keys on your keyboard, you can use the same
code and just print the value of $key to the screen.
11. Conclusion
In order to see those functions in action, I suggest you to download and
take a look at my two simple Perl programs, TAL and
ELM-TAG.