Ali Onur Cinar
Articles

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.


Your Comments


12/31/69 17:00

Name:
Comment:

Valid XHTML 1.0! Valid CSS! FuseBox Inside This is my Google PageRank. - SmE Rank free service Powered by Scriptme

This page was last updated on Sun January 21 2007 06:54:53 PM