Use ANSI escape codes for screen control
We will need some kind of screen building technique here for the various upcoming projects. Parilux needs a simple screen that shows the states of the LED's, the inputs and the I2C interface. To do this, there are several methods:
A first source
To wet your appetite I show a first little program. It clears the screen and does some fancy printing. It's not a high level program, so I do not need to explain what is does and how it does it. Read it:
MODULE cls;
IMPORT InOut;
PROCEDURE CtrlSeq (seq : ARRAY OF CHAR);
BEGIN
InOut.Write (33C);
InOut.WriteString (seq);
InOut.WriteBf
END CtrlSeq;
BEGIN
CtrlSeq ('[2J');
CtrlSeq ('[0;0H');
CtrlSeq ('[31;41m'); InOut.Write (' ');
CtrlSeq ('[33;44m'); InOut.WriteString (' ');
CtrlSeq ('[31;41m'); InOut.Write (' ');
InOut.WriteString ("This is line 1"); InOut.WriteLn; InOut.WriteBf;
CtrlSeq ('[31;40m');
InOut.WriteString ("This is line 2"); InOut.WriteLn; InOut.WriteBf;
CtrlSeq ('[32m');
InOut.WriteString ("This is line 3"); InOut.WriteLn; InOut.WriteBf;
CtrlSeq ('[33m');
InOut.WriteString ("This is line 4"); InOut.WriteLn; InOut.WriteBf;
END cls.
What it does is shown below. The screen is cleared, the cursor goes to the home position and some printing is
performed in different colours. This program was written within one hour after having printed the ANSI codes.
ANSI is basic. But it's also fast..
The ANSI codes
In the table below are the ANSI control codes as they exist for the xterm terminal (most widely used in Linux and X11).
| ANSI code | Function | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ESC[2J | Clear screen | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[K | Clear from cursor position to end of line | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#;@H | Move cursor position to line '#', column '@' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#m |
Define screen colours and attributes. These control sequences can be chained by using a semicolon:
'ESC[1;32m' combines the actions of ESC[1m and ESC[32m. Below are the parameters for this function and
their meanings:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[s | Save cursor position for later recall | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[u | Recall previously saved cursor position | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#A | Move cursor up over '#' lines | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#B | Move cursor down over '#' lines | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#C | Move cursor right over '#' columns | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#D | Move cursor left over '#' columns | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ESC[#;@R | Reports current cursor position (line and column) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
If you look at the ANSI control codes you can get an indication how the curses package got it's name. In most comics and cartoons, the # and @ tokens are acronyms for swearings and curses. At least, this is my reasoning.
Make it modular
After we found out how it worked, it's getting time to pour the lot in a set of predefined functions. That's what I did in the 'Try1' program listed below.
MODULE Try1;
IMPORT InOut, Strings, NumConv;
TYPE AttrMode = (Normal, Bright, Underlined, Blinking, Reverse, Invisible);
Colours = (Black, Red, Green, Yellow, Blue, Magenta, Cyan, White);
PROCEDURE CtrlSeq (seq : ARRAY OF CHAR);
BEGIN
InOut.Write (33C);
InOut.WriteString (seq);
InOut.WriteBf
END CtrlSeq;
PROCEDURE Cls;
BEGIN
CtrlSeq ('[2J')
END Cls;
PROCEDURE Cursor (lin, col : CARDINAL);
VAR str, tmp : ARRAY [0..9] OF CHAR;
ok1, ok2 : BOOLEAN;
BEGIN
str := '[';
NumConv.Num2Str (lin, 10, tmp, ok1);
Strings.Append (str, tmp);
Strings.Append (str, ';');
NumConv.Num2Str (col, 10, tmp, ok2);
Strings.Append (str, tmp);
Strings.Append (str, 'H');
IF ok1 AND ok2 THEN
CtrlSeq (str)
END
END Cursor;
PROCEDURE Attribute (attr : AttrMode);
BEGIN
CASE attr OF
Bright : CtrlSeq ('[1m') |
Underlined : CtrlSeq ('[4m') |
Blinking : CtrlSeq ('[5m') |
Reverse : CtrlSeq ('[7m') |
Invisible : CtrlSeq ('[8m')
ELSE
CtrlSeq ('[0m')
END
END Attribute;
PROCEDURE Foreground (colour : Colours);
BEGIN
CASE colour OF
Black : CtrlSeq ('[30m') |
Red : CtrlSeq ('[31m') |
Green : CtrlSeq ('[32m') |
Yellow : CtrlSeq ('[33m') |
Blue : CtrlSeq ('[34m') |
Magenta : CtrlSeq ('[35m') |
Cyan : CtrlSeq ('[36m')
ELSE
CtrlSeq ('[37m')
END
END Foreground;
PROCEDURE Background (colour : Colours);
BEGIN
CASE colour OF
Black : CtrlSeq ('[40m') |
Red : CtrlSeq ('[41m') |
Green : CtrlSeq ('[42m') |
Yellow : CtrlSeq ('[43m') |
Blue : CtrlSeq ('[44m') |
Magenta : CtrlSeq ('[45m') |
Cyan : CtrlSeq ('[46m')
ELSE
CtrlSeq ('[47m')
END
END Background;
BEGIN
Cls;
Cursor (0, 0);
Background (Red); InOut.Write (' ');
Background (Black); Attribute (Bright); Foreground (Yellow);
InOut.WriteString (' ');
Background (Red); Attribute (Normal); Foreground (Cyan);
InOut.Write (' ');
InOut.WriteString ("This is line 1"); InOut.WriteLn; InOut.WriteBf;
Foreground (Red);
Background (Black);
InOut.WriteString ("This is line 2"); InOut.WriteLn; InOut.WriteBf;
Foreground (Green);
InOut.WriteString ("This is line 3"); InOut.WriteLn; InOut.WriteBf;
Foreground (Yellow);
Attribute (Bright);
InOut.WriteString ("This is line 4"); InOut.WriteLn; InOut.WriteBf;
Foreground (White);
Background (Black);
Attribute (Normal);
END Try1.
The output is as follows. Do some tests with the placement of the 'Normal' attribute. It is not just the
reverse of 'Bright'. It will reset ALL other colour and attribute settings to not bright white on black.
Until now, the ANSI related subroutines are:
Make a module out of the functions.
Now that we know how to make ANSI functions, it is time to modularize them. So I made an ANSI library consisting of ANSI.DEF and ANSI.MOD. Below is the source of ANSI.DEF (the definition module). As you can see, I broke my word and have added two more derived functions.
DEFINITION MODULE ANSI; TYPE AttrMode = (Normal, Bright, Underlined, Blinking, Reverse, Invisible); Colours = (Black, Red, Green, Yellow, Blue, Magenta, Cyan, White); PROCEDURE Cls; (* Clear the view screen area *) PROCEDURE Cursor (lin, col : CARDINAL); (* Set cursor to line = lin, column = col *) PROCEDURE Attribute (attr : AttrMode); (* Set foreground attribute to 'attr' *) PROCEDURE Foreground (colour : Colours); (* Set foreground colour to 'colour' *) PROCEDURE Background (colour : Colours); (* Set background colour to 'colour' *) PROCEDURE Colour (fg, bg : Colours; attr : AttrMode); (* Set foreground, background and attribute *) PROCEDURE Fill (lin, col, len : CARDINAL); (* put cursor to (lin, col) and print 'len' spaces *) END ANSI.Not very difficult. This is the public part of the module. It contains the function prototypes (*as they are called in a lesser language) and the TYPEs that the user needs to know.
Below is the implementation module: the actual runtime routines that the user does not need to be aware of when writing the software.
IMPLEMENTATION MODULE ANSI;
IMPORT InOut, Strings, NumConv;
CONST Escape = 33C;
PROCEDURE CtrlSeq (seq : ARRAY OF CHAR);
BEGIN
InOut.Write (Escape);
InOut.WriteString (seq);
InOut.WriteBf
END CtrlSeq;
PROCEDURE Cls;
BEGIN
CtrlSeq ('[2J')
END Cls;
PROCEDURE Cursor (lin, col : CARDINAL);
VAR str, tmp : ARRAY [0..9] OF CHAR;
ok1, ok2 : BOOLEAN;
BEGIN
str := '[';
NumConv.Num2Str (lin, 10, tmp, ok1);
Strings.Append (str, tmp);
Strings.Append (str, ';');
NumConv.Num2Str (col, 10, tmp, ok2);
Strings.Append (str, tmp);
Strings.Append (str, 'H');
IF ok1 AND ok2 THEN
CtrlSeq (str)
END
END Cursor;
PROCEDURE Attribute (attr : AttrMode);
BEGIN
CASE attr OF
Bright : CtrlSeq ('[1m') |
Underlined : CtrlSeq ('[4m') |
Blinking : CtrlSeq ('[5m') |
Reverse : CtrlSeq ('[7m') |
Invisible : CtrlSeq ('[8m')
ELSE
CtrlSeq ('[0m')
END
END Attribute;
PROCEDURE Foreground (colour : Colours);
BEGIN
CASE colour OF
Black : CtrlSeq ('[30m') |
Red : CtrlSeq ('[31m') |
Green : CtrlSeq ('[32m') |
Yellow : CtrlSeq ('[33m') |
Blue : CtrlSeq ('[34m') |
Magenta : CtrlSeq ('[35m') |
Cyan : CtrlSeq ('[36m')
ELSE
CtrlSeq ('[37m')
END
END Foreground;
PROCEDURE Background (colour : Colours);
BEGIN
CASE colour OF
Black : CtrlSeq ('[40m') |
Red : CtrlSeq ('[41m') |
Green : CtrlSeq ('[42m') |
Yellow : CtrlSeq ('[43m') |
Blue : CtrlSeq ('[44m') |
Magenta : CtrlSeq ('[45m') |
Cyan : CtrlSeq ('[46m')
ELSE
CtrlSeq ('[47m')
END
END Background;
PROCEDURE Colour (fg, bg : Colours; attr : AttrMode);
BEGIN
Foreground (fg);
Background (bg);
Attribute (attr)
END Colour;
PROCEDURE Fill (lin, col, len : CARDINAL);
VAR n : CARDINAL;
BEGIN
Cursor (lin, col);
FOR n := 1 TO len DO InOut.Write (' ') END
END Fill;
END ANSI.
If you defined a TYPE or a CONST, you do not need to define it again in the implementation module.
Testing the new library
I modified the Try1 program and renamed it Try2. It ought to be identical to Try1. You be the judge of that. Here is the source for Try2.mod:
MODULE Try2;
IMPORT InOut, ANSI;
FROM ANSI IMPORT AttrMode, Colours;
BEGIN
ANSI.Cls;
ANSI.Cursor (0, 0);
ANSI.Background (Red); InOut.Write (' ');
ANSI.Background (Black); ANSI.Attribute (Bright); ANSI.Foreground (Yellow);
InOut.WriteString (' ');
ANSI.Attribute (Normal); ANSI.Background (Red); ANSI.Foreground (Cyan);
InOut.Write (' ');
InOut.WriteString ("This is line 1"); InOut.WriteLn; InOut.WriteBf;
ANSI.Foreground (Red);
ANSI.Background (Black);
InOut.WriteString ("This is line 2"); InOut.WriteLn; InOut.WriteBf;
ANSI.Foreground (Green);
InOut.WriteString ("This is line 3"); InOut.WriteLn; InOut.WriteBf;
ANSI.Foreground (Yellow);
ANSI.Attribute (Bright);
InOut.WriteString ("This is line 4"); InOut.WriteLn; InOut.WriteBf;
ANSI.Foreground (White);
ANSI.Background (Black);
ANSI.Attribute (Normal);
END Try2.
As you see, I used a mix of qualified and unqualified imports. I could have refrained from the unqualified
imports if I would have used qualified names for the colours and attributes (like ANSI.red, ANSI.Bright) but I
was too lazy for that.
Page created 16 June 2007,
Page equipped with GoogleBuster technology