Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

Portable Screen Handling


MAY95: Portable Screen Handling

Portable Screen Handling

ANSI escape sequences for portable screen operations

Matt Weisfeld

Matt is a senior system test engineer at Allen-Bradley Company and is responsible for the development of test software on VAX/VMS, UNIX, DOS, and other platforms. He can be contacted at 1938 Aldersgate Dr., Lyndhurst, OH 44124.


The term "portable screen interface" is, in most cases, an oxymoron. Of all the areas relating to software portability, screen handling is quite possibly the least portable. This is not surprising, since screen operations are very hardware (terminal) dependent. Although standards such as X Window are available, programming in any windowing environment is anything but trivial. Even curses, a text-based screen-handling package portable to many platforms, requires some overhead. However, in many applications, simple screen operations (such as bolding, underlining, and the like) are all that is required.

In this article, I'll present methods for using ANSI escape sequences to make screen operations easier while increasing portability in the process. On the software side, I'll focus on C; for the hardware, I'll include Intel 386/486, DEC VAX, Sun, and HP. Source code and programmer notes for Basic, Fortran, and Pascal are available electronically; see "Availability," page 3.

The C code presented here operates on DOS, VAX/VMS, and UNIX (Sun SLC and HP-UX). These platforms represent a wide variety of configurations: The VAX and the Sun SLC use VT100/200/300 terminal emulation, the Sun SLC C compiler is non-ANSI, and the HP-UX workstation (Apollo series 700) does not support ANSI escape sequences. While ANSI mode applies to the VT100/200/300 terminals, VT52 mode is not ANSI compatible. The Fortran, Basic, and Pascal examples were compiled and executed in the VAX/VMS environment.

Using ANSI Escape Sequences

Escape sequences are at the heart of these screen-handling functions, and I rely on the ANSI standard whenever possible. (See Table 1 for a list of ANSI escape sequences.) For example, the ANSI escape sequence for reverse video is ESC[7m. For C, ESC is \033. Thus the sequence for reverse video is "\033[7m". The escape sequence, which is an octal value, is set from C by printf ("\033[7m");. All ANSI escape sequences in C begin with the escape character (\033) and the left bracket. Following the bracket are the parameters specific to the particular sequence with a semicolon (;) separating one parameter from another. Finally, there is a single-letter, case-sensitive command name. In the reverse-video sequence, the m sets graphics mode on. Graphics mode has the text attributes listed in Table 1(d). Other parameters include foreground and background colors.

Creating header-file definitions makes the sequences more readable, as in scrlibs.h (Listing One). For example, the definition for reverse video is static char reverse[]={"\033[7m"}; so that the code to set reverse video now reads printf (reverse);. Figure 1 illustrates graphics-mode definitions.

On platforms with a color monitor (in this case, only my DOS machine supports color), various color options are available. To bold characters with the foreground color cyan, for example, use static char bold[]={"\033[36;1m"; where 36 represents the color cyan. Check the DOS manual for the color codes.

The flexibility of this approach is evident when commands such as printf ("Here %swe%s go again\n", bold, normal); are used to print the word we in bold.

Non-ANSI Escape Sequences

Not all platforms support the ANSI standard--the HP-UX system, for example, does not--and the header files and the code must use #ifdefs. HP-UX definitions are listed in Figure 2. Though different, the HP-UX escape sequences behave in a manner similar to the ANSI sequences. However, beware of subtle but significant differences. Some HP-UX platforms do not provide a bolding mechanism, so this implementation substitutes the half-bright attribute. Blink-attribute support also depends on the platform.

Designing the Screen Library

While using printf() to set escape sequences works in many applications, they sometimes become cumbersome or even unclear. This is especially true when the screen function requires more than one sequence to perform its task. For example, suppose you need to set the bold attribute (on and off). In Example 1(a), a call to do_bold() sets the bold attribute for all output to the screen. Likewise, do_normal() clears all attributes to normal.

It is important to remember that output is not limited to stdout or even the screen for that matter. By passing the stream as a parameter, you can use any stream (such as stderr) for output. Be aware that printing these attribute sequences to a file will not produce the same effects as printing to the screen. Try using a filename for the stream and view the results with an editor or on paper. Instead of getting the anticipated graphics attribute, the sequences [1mbold video, [0m appear in the file. Example 1(b) is the code for do_bold(). The file scrlibs.c (Listing Two) contains this and the other functions that make up the C library described here. The other graphics functions are almost identical to do_bold().

Since C buffers I/O, it is important to include fflush(). Without it, the attributes may not appear as expected. Not having to execute an fflush() after each printf() is yet another reason for using functions to set screen attributes.

Absolute Cursor Movement

Nongraphics attributes behave in a manner consistent with the graphics attributes. For example, the ANSI definition to clear the physical screen (stdout) is "\033[nJ", where n is: 0 (the default), to erase from the cursor position to the end of the display; 1, to erase from the beginning of the display to the cursor (inclusive); or 2, to erase the entire display and return the cursor to home position.

On some platforms (specifically, the non-ANSI), the screen is cleared from the current cursor position. If the cursor is halfway down the screen, only the bottom half of the screen is cleared. To clear the entire screen, the cursor must be at screen location (0,0). The ANSI escape sequence, in printf format, for moving the cursor to an absolute screen position is "\033[%d;%dH". Note that the string includes two %d escape sequences, specifying the screen's line and column numbers, respectively. For portability, the routines will all place the cursor at the home position prior to clearing the screen. The ANSI definitions to clear the screen are in Example 2(a), while the HP-UX definitions are in Example 2(b).

Another difference between ANSI and HP-UX sequences is the order of the parameters. In the ANSI sequence, the screen location is mapped by the coordinates (row,col). However, the HP-UX sequence maps to (col,row). Fortunately, this is addressed in the function called cursor_pos() and is hidden from you; see Example 3(a). To place the cursor in position (0,0), cursor_home() is built; see Example 3(b). The erase_display() function that clears the screen must use either cursor_home() or cursor_pos(0,0) to place the cursor in the proper position; see Example 3(c).

Relative Cursor Movement

The cursor_pos() function represents absolute addressing. This means that the cursor will go directly to the x,y screen coordinates specified. Relative addressing operates with respect to the current cursor position--either up, down, left, or right. For example, if the cursor is at row 5, moving the cursor up two relative positions places the cursor at row 3. Example 4(a) lists the ANSI sequences for relative addressing, while Example 4(b) lists the HP-UX sequences.

The code for cursor_up is in Example 4(c). The parameter move is simply the number of coordinates that the cursor moves in the up direction. The other relative addressing functions are similar (see Listing Two).

The C Test Program

Listing Three is test.c, a simple test program that illustrates how the screen functions operate. After clearing the screen, the program tests the reverse, bold, and blink attributes. The do_normal() function clears each one prior to calling the next. Finally, calls to the relative addressing functions move the cursor around the screen. The C function sleep() provides time to view the cursor movements.

When building the executable, you must specify the platform by defining either VMS, HP-UX, SLC, or BCC in the build file (the DCL procedure, makefile, or integrated environment). Some platforms, like VMS, define these by default.

All definitions are found in scrlibs.h (Listing One). If a compiler is ANSI-compliant, then use the directive #define ANSI_COMPLIANT; otherwise, use the directive #define K_R_COMPILER. Likewise, if the platform supports ANSIescape sequences, you should use #define ANSI_SEQUENCES. For the HP-UX platform, however, the directive is #define HPUX_ SEQUENCES. Finally if the terminal used is a VT100, then use the directive #define VT100.

Keeping all this information in the header file ensures that no definitions will be omitted or incorrect. It also provides the needed portability.

Conclusion

Although screen-handling operations are not very portable, building screen libraries can overcome many portability problems. The libraries presented here give you the flexibility to use the escape sequences in a more straightforward manner.

Each platform supports different levels of functionality. For example, the VT100 terminals support double-height and double-width capabilities, while DOS does not support character insertions and deletions. In this case, you would have to write code to perform this task. As always, your specific needs, and the capabilities of each platform, dictate what must be included in the library.

Regardless of the implementation, building screen libraries makes basic, text-based screen operations more portable, more readable, and much easier to manage.

Table 1: Subset of ANSI escape sequences (p1=integer). (a) Cursor-control escape sequences; (b) editing escape sequences; (c) page-control escape sequences; (d) graphics escape sequences (ESC[p1m).

(a)
Cursor Forward (CUF)                   ESC[p1C
Cursor Backward (CUB)                  ESC[p1D
Cursor Down (CUD)                      ESC[p1B
Cursor UP (CUU)                        ESC[p1A
Cursor Position (CUP)                  ESC[p1;p2H
Horizontal & Verticle Position (HVP)   ESC[p1;p2f
Save Cursor Position (SCP)             ESC[s
Restore Cursor Position (RCP)          ESC[u
Cursor Position Report (CPR)           ESC[p1;p2R
Device Status Report (DSR)             ESC[6p1
Wrap at End-of-Line                    (wrap) ESC[7h
                                       (no wrap) ESC[7l
(b)
Insert Line (IL)            ESC[p1L
Insert/Replace Mode (IRM)   (insert) ESC[4h
                            (replace) ESC[4l
Delete Character (DCH)      ESC[p1P
Delete Line (DL)            ESC[p1M
Erase Line (EL)             ESC[p1K
Erase Display (ED)          ESC[p1J
(c)
Next Page (NP)              ESC[p1U
Previous Page (PP)          ESC[p1V
(d)
0     All attributes off
1     Bold on
4     Underscore (monochrome display)
5     Blink on
7     Reverse video on
8     Concealed on

Figure 1: Graphics-mode definitions.

static char normal[] = {"\033[0m"};
static char bold[]   = {"\033[1m"};
static char blink[]  = {"\033[5m"};

Figure 2: HP-UX definitions.

static char bold[]      = {"\033&dH"};
static char reverse[]   = {"\033&dK"};
static char underline[] = {"\033&dD"};
static char blink[]     = {"\033&dA"};
static char normal[]    = {"\033&d@"};

Example 1: (a) Setting the bold attribute for screen output; (b) code for do_bold().

(a)
printf ("this is not bold\n");
do_bold(stdout);
printf ("this is bold\n");
do_normal(stdout)
printf ("this is not bold again\n");
(b)
void do_bold(stream)
FILE *stream;
{
    fprintf (stream, bold);
    fflush(stream);
    return;
}

Example 2: (a) ANSI definitions to clear the screen; (b) HP-UX definitions.

(a)
static char curpos[]    = {"\033[%d;%dH"};
static char erasedisp[] = {"\033[J"};
(b)
static char curpos[]    = {"\033&a%dx%dY"};
static char erasedisp[] = {"\033J"};

Example 3: (a) The cursor_pos() function; (b) positioning the cursor to the home position using cursor_home(); (c) erase_display() clears the screen.

(a)
void cursor_pos(row,col)
int row, col;
#ifdef HPUX
    printf (curpos, col,row);
#else
    printf (curpos, row,col);
#endif
    fflush(stdout);
    return;
}
(b)
void cursor_home()
{
    cursor_pos(0,0);
    fflush(stdout);
    return;
}
(c)
void erase_display()
{
    cursor_home();
    printf (clear);
    fflush(stdout);
    return;
}

Example 4: (a) ANSI sequences for relative addressing; (b) the HP-UX sequences; (c) code for cursor_up.

(a)
static char curfor[]    = {"\033[%dC"};
static char curback[]   = {"\033[%dD"};
static char curdown[]   = {"\033[%dB"};
static char curup[]     = {"\033[%dA"};
(b)
static char curfor[]    = {"\033&a+%dC"};
static char curback[]   = {"\033&a-%dC"};
static char cursedown[] = {"\033&a+%dR"};
static char curseup[]   = {"\033&a-%dR"};
(c)
void cursor_up(move)
int move;
{
   int i;
    printf (curup, move);
    fflush(stdout);
    return;
}

Listing One


/***************************************************
  FILE NAME   : scrlibs.h                         
  AUTHOR      : Matt Weisfeld                     
  DESCRIPTION : header file for scrlibs.c         
***************************************************/
#ifdef VMS
#define ANSI_COMPILER
#define ANSI_SEQUENCES
#define VT100
#endif

#ifdef SLC
#define UNIX
#define K_R_COMPILER
#define ANSI_SEQUENCES
#define VT100
#endif

#ifdef BCC
#define DOS
#define ANSI_COMPILER
#define ANSI_SEQUENCES
#endif

#ifdef HPUX
#define UNIX
#define ANSI_COMPILER
#define HPUX_SEQUENCES
#endif

#ifdef ANSI_COMPILER

void do_bold(FILE *);
void do_normal(FILE *);
void do_blink(FILE *);
void do_reverse(FILE *);

void cursor_right(int);
void cursor_left(int);
void cursor_down(int);
void cursor_up(int);
void cursor_pos(int, int);
void cursor_home(void);

void erase_display(void);

#else

void do_bold();
void do_normal();
void do_blink();
void do_reverse();

void cursor_right();
void cursor_left();
void cursor_down();
void cursor_up();
void cursor_pos();
void cursor_home();
void cursor_bottom();

void erase_display();

#endif

#ifdef ANSI_SEQUENCES

#ifdef DOS
static char bold[]  = {"\033[36;1m"};
#else
static char bold[]  = {"\033[1m"};
#endif
static char normal[]    = {"\033[0m"};
static char blink[]     = {"\033[5m"};
static char reverse[]   = {"\033[7m"};

static char curright[]  = {"\033[%dC"};
static char curleft[]   = {"\033[%dD"};
static char curdown[]   = {"\033[%dB"};
static char curup[]     = {"\033[%dA"};
static char curpos[]    = {"\033[%d;%dH"};
static char erasedisp[] = {"\033[2J"};

#endif

#ifdef HPUX_SEQUENCES

static char bold[]  = {"\033&dH"};
static char normal[]    = {"\033&d@"};
static char blink[]     = {"\033&dA"};
static char reverse[]   = {"\033&dK"};
static char underline[] = {"\033&dD"};

static char curfor[]    = {"\033&a+%dC"};
static char curback[]   = {"\033&a-%dC"};
static char curdown[]   = {"\033&a+%dR"};
static char curup[]     = {"\033&a-%dR"};
static char curpos[]    = {"\033&a%dx%dY"};

static char erasedisp[] = {"\033J"};

#endif


Listing Two


/***************************************************
  FILE NAME   : scrlibs.c                         
  AUTHOR      : Matt Weisfeld                     
  DESCRIPTION : screen handling libraries         
***************************************************/
#include <stdio.h>
#include "scrlibs.h"

void do_bold(stream)
FILE *stream;
{
    fprintf (stream, bold);
    fflush(stream);
    return;
}
void do_normal(stream)
FILE *stream;
{
    fprintf (stream, normal);
    fflush(stream);
    return;
}
void do_blink(stream)
FILE *stream;
{
    fprintf (stream, blink);
    fflush(stdout);
    return;
}
void do_reverse(stream)
FILE *stream;
{
    fprintf (stream, reverse);
    fflush(stdout);
    return;
}
void cursor_right(move)
int move;
{
    int i;

    printf (curright, move);
    fflush(stdout);
    return;
}
void cursor_left(move)
int move;
{
    int i;

    printf (curleft, move);
    fflush(stdout);
    return;
}
void cursor_down(move)
int move;
{
    int i;

    printf (curdown, move);
    fflush(stdout);
    return;
}
void cursor_up(move)
int move;
{
    int i;

    printf (curup, move);

    fflush(stdout);
    return;
}
void cursor_pos(row,col)
int row,col;
{

#ifdef HPUX
    printf (curpos, col,row);
#else
    printf (curpos, row,col);
#endif
    fflush(stdout);
    return;
}
void cursor_home()
{
    cursor_pos(0,0);
    fflush(stdout);
    return;
}
void cursor_bottom()
{
    cursor_pos(23,0);
    fflush(stdout);
    return;
}
void erase_display()
{
    int i;

    cursor_home();
    printf (erasedisp);
    fflush(stdout);
    return;
}


Listing Three


/***************************************************
  FILE NAME   : test.c                            
  AUTHOR      : Matt Weisfeld                     
  DESCRIPTION : test file for screen libraries    
***************************************************/
#include <stdio.h>
#include "scrlibs.h"
#ifdef DOS
#include <dos.h>
#endif

main()
{
    erase_display();
    do_bold(stdout);
    printf ("bold video\n");
    do_normal(stdout);
    do_reverse(stdout);
    printf ("reverse video\n");

    do_normal(stdout);
    do_blink(stdout);
    printf ("blink video\n");
    do_normal(stdout);
    printf ("normal video\n");

    printf ("\n");

    cursor_right(20);
    printf ("Right");
    cursor_left(10);
    printf ("Left");
    cursor_down(10);
    printf ("Down");
    cursor_up(5);
    printf ("Up");

    cursor_bottom();
    return;
}



Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.