13.2 terminfo
Häufige Fragen, wenn es um die Programmierung von Konsolenprogrammen geht, drehen sich um die Bildschirmsteuerung wie z. B. das Löschen eines Bildschirms, das Positionieren des Cursors oder die Ausgabe in einer anderen Farbe. Viele Programmierer haben meistens schon mit so genannten Semigrafik-Bibliotheken Erfahrungen unter MS DOS mit der Borland-Bibliothek conio.h gesammelt.
Zwar kann man auch die ANSI-Steuersequenzen verwenden – was somit unabhängig vom Betriebssystem wäre, aber davon kann nur abgeraten werden. Wenn dabei ein Terminal nichts mit ANSI-Steuersequenzen anzufangen weiß, wird beim Programmstart bereits ein wilder Zeichensalat ausgegeben.
Damit eine portable Bildschirmsteuerung (unter Linux und UNIX) möglich wurde, mussten dem Rechner Informationen über die Steuerung von verschiedenen Terminaltypen übergeben werden. Dies wurde mit einer Datenbank von Terminaleigenschaften – genannt termcap – realisiert. Dabei wurden in einer Datei (/etc/termcap) Eigenschaften von Terminals wie u. a. Zeilen- und Spaltenzahl, Unterscheidung zwischen Groß- und Kleinschreibung, Bildschirm löschen etc. hinterlegt. Der Programmierer konnte nun auf diese Datei zugreifen, um für das aktuelle Terminal die Steuerzeichen zu ermitteln. Damit dies nicht so umständlich wurde, steht eine eigene Bibliothek dafür zur Verfügung.
Im Laufe der Zeit wurden immer mehr neue Terminaltypen zur termcap-Datei hinzugefügt, womit, zum Nachteil, die Datei immer umfangreicher wurde und somit automatisch die Suche nach einem entsprechenden Eintrag immer länger dauerte. Dem Programmierer wurde es mit den nicht unbedingt selbstklärenden Zweizeichennamen auch nicht unbedingt einfach gemacht.
Um diesen Nachteil wettzumachen, wurde ein anderes Konzept entwickelt. Dies wurde mit der Terminaldatenbank terminfo und der Bibliothek curses realisiert. Die Dateien für terminfo befinden sich gewöhnlich im Verzeichnis /usr/lib/terminfo/* oder /usr/share/terminfo/*. Darin befindet sich für jedes Terminal eine eigene Datei (in alphabetisch untergeordneten Verzeichnissen) und nicht wie bei termcap alles in einer (Binär-)Datei.
Beide Datenbanken, termcap und terminfo, verwenden aber dennoch denselben Namen. Zum Beispiel hat unter einem Window-Manager die Konsole meistens den Namen xterm, weshalb sich in der termcap-Datei und auch im terminfo-Verzeichnis /usr/lib/terminfo/x ein entsprechender Namen mit den entsprechenden Eigenschaften befindet. Welches Terminal gerade bei Ihnen läuft, können Sie aus der Umgebungsvariablen TERM ermitteln:
$ echo $TERM
xterm
13.2.1 terminfo verwenden
Für jeden möglichen Terminaltypen beinhaltet terminfo eine Reihe von Terminalfähigkeiten (capabilities) und -Features – auch bekannt als capname (verzeihen Sie mir bitte die Eindeutschung mancher Begriffe – mir sind die engl. Begriffe geläufiger). Die so genannten capnames lassen sich in die drei folgenden Kategorien einteilen:
|
Boolean – Damit können Sie überprüfen, ob ein Terminal bestimmte Eigenschaften unterstützt oder nicht. |
|
Nummerisch – Nummerische capnames spezifizieren gewöhnlich bestimmte Größenangaben, z. B. wie viele Spalten oder Zeilen ein Terminal hat. |
|
String – Hier wird entweder, wenn nötig, eine Escape-Sequenz angegeben, um bestimmte Eigenschaften verwenden zu können, oder es wird etwas ausgegeben, wenn ein Anwender eine bestimmte Taste drückt. |
In der folgenden Tabelle finden Sie einige (der vielen) Fähigkeiten, über die terminfo verfügt. Für einen kompletten Überblick sollten Sie die Manual Page (man terminfo) lesen.
Tabelle 13.6
Einige capenames und deren Bedeutung
capname
|
Type
|
Bedeutung
|
am
|
boolean
|
Terminal verfügt über eine automatische Randbegrenzung (rechts).
|
bm
|
boolean
|
Terminal verfügt über eine automatische Randbegrenzung (links).
|
col
|
Nummerisch
|
Anzahl der Spalten des aktuellen Terminals
|
lines
|
Nummerisch
|
Anzahl der Zeilen des aktuellen Terminals
|
colors
|
Nummerisch
|
Anzahl der Farben des aktuellen Terminals
|
pairs
|
Nummerisch
|
Anzahl der Farbenpaare (Schrifthintergrund) des akt. Terminals
|
clear
|
String
|
Löscht den Bildschirm und setzt den Cursor an den Anfang.
|
cl
|
String
|
Löscht von der aktuellen Position bis zum Ende der Zeile.
|
ed
|
String
|
Löscht von der aktuellen Position bis zum Ende des Bildschirms.
|
cup
|
String
|
Setzt den Cursor in Zeile und Spalte.
|
flash
|
String
|
Löst einen visuellen Alarm (Flash Screen) aus.
|
bel
|
String
|
Löst einen akkustischen Alarm aus.
|
kf[0–63]
|
String
|
F[0–63]-Funktionstasten
|
cr
|
String
|
Wagenrücklauf (Carriage Return)
|
cud1
|
String
|
Cursor um eine Zeile herunter
|
cub1
|
String
|
Cursor um eine Spalte nach links
|
cuf1
|
String
|
Cursor um eine Spalte nach rechts
|
cuu1
|
String
|
Cursor um eine Zeile nach oben
|
Um ein Programm mit terminfo zu erstellen, müssen Sie die Headerdateien <curses.h> und <term.h> mit einbinden. Außerdem benötigen Sie die Bibliothek curses, die mit dem Compilerflag -lcurses hinzugelinkt wird.
13.2.2 terminfo initialisieren – setupterm()
Um terminfo zu verwenden, muss zuerst die Datenstruktur initialisiert werden. Dies lässt sich recht einfach mit der Funktion setupterm() erledigen:
#include <curses.h>
#include <term.h>
int setupterm(const char *term, int fd, int *error_return);
Mit dem Parameter term geben Sie die Schnittstelle für den Terminaltyp an. Wenn Sie hierfür NULL angeben – was häufig der Fall ist –, liest setupterm() den Typ aus der Umgebungsvariablen $TERM. Ansonsten wird das Terminal verwendet, worauf die Adresse term verweist. Alle anschließenden Ausgaben auf das Terminal werden auf dem Filedeskriptor fd gemacht (was meistens stdout/1 ist). Wenn für error_return nicht NULL angegeben wird, ist entweder 1 in der Adresse abgelegt, was anzeigt, dass alles glatt verlief. Ist der Wert hierbei 0, konnte das referenzierte Terminal term nicht in der terminfo-Datenbank gefunden werden, oder error_return referenziert –1, wenn die terminfo-Datenbank überhaupt nicht gefunden wurde. Geben Sie für error_return hingegen NULL an, gibt setupterm() bei einem Fehler eine Diagnosemeldung zurück und beendet sich anschließend. Die Funktion setupterm() gibt OK bei Erfolg, ansonsten ERR bei einem Fehler zurück.
13.2.3 Eigenschaften eines Terminals (Finden von capnames) – tigetflag(), tigetnum() und tigetstr()
Mit folgenden drei Funktionen (der Kategorie entsprechend) können Sie die Eigenschaften eines Terminals ermitteln:
#include <term.h>
#include <curses.h>
int tigetflag( const char *capname ); // boolen
int tigetnum ( const char *capname ); // numerisch
char *tigetstr( const char *capname ); // string
Mit der Funktion tigetflag() wird überprüft, ob die Terminaleigenschaft von capname vom aktuellen Terminaltyp unterstützt wird. So liefert tigetflag("am") 1 zurück, wenn der Terminaltyp über eine automatische Randbegrenzung verfügt. Bei einem Fehler wird -1 zurückgegeben.
Die Funktion tigetnum() liefert die Ganzzahl einer Eigenschaft zurück. Unterstützt der aktuelle Terminaltyp z. B. acht Farben, so liefert ein Funktionsaufruf von tigetnum("colors") den Wert 8 zurück. Bei einem Fehler wird -1 zurückgegeben.
Die Funktion tigetstr() gibt die Eigenschaft zurück, die vom Tastaturtreiber generiert wird, wenn z. B. eine Cursortaste gedrückt wurde. Dabei wird das ESCAPE-Zeichen als 27 dekodiert – anstatt, wie in der Term-Infodatei dargestellt, mit \E – weshalb Sie das Zeichen mit \\E selbst angeben müssen, damit es ausgegeben wird (siehe das nachfolgende Beispiel). Bei einem Fehler gibt diese Funktion NULL zurück.
Hierzu ein Listing, wie Sie die Eigenschaften eines Terminals abfragen können. Für mehr Eigenschaften sollten Sie, wie bereits erwähnt, die Manual Pages von terminfo zurate ziehen und einzelne Eigenschaften hier einbauen.
/* terminfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <term.h>
#include <curses.h>
#define CAPS 4
int main (void) {
int i, ret = 0;
char *buf;
const char *boolcaps[CAPS] = {"am", "bm", "bce", "km"};
const char *numcaps[CAPS]={"cols", "lines", "colors", "pairs"};
const char *strcaps[CAPS] = {"cup", "kf2", "flash", "hpa"};
/* terminfo initialisieren */
ret = setupterm (NULL, fileno (stdin), NULL);
if (ret != 0) {
perror ("setupterm()");
exit (EXIT_FAILURE);
}
/* Eigenschaften abfragen, ob vorhanden */
for (i = 0; i < CAPS; i++) {
ret = tigetflag (boolcaps[i]);
if (ret == -1)
printf ("%s wird nicht unterstützt\n", boolcaps[i]);
else
printf ("%s wird unterstützt\n", boolcaps[i]);
}
printf ("\n");
/* Eigenschaften abfragen, die einen nummerischen */
/* Wert zurückliefern */
for (i = 0; i < CAPS; i++) {
ret = tigetnum (numcaps[i]);
if (ret == -1)
printf ("%s wird nicht unterstützt\n", numcaps[i]);
else
printf ("%s ist %d\n", numcaps[i], ret);
}
printf ("\n");
/* Eigenschaften abfragen, die vom Tastaturtreiber */
/* generiert werden */
for (i = 0; i < CAPS; i++) {
buf = tigetstr (strcaps[i]);
if (buf == NULL)
printf ("%s wird nicht unterstützt\n", strcaps[i]);
else
printf ("%s ist \\E%s\n", strcaps[i], &buf[1]);
}
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o terminfo terminfo.c -lncurses
$ ./terminfo
am wird unterstützt
bm wird nicht unterstützt
bce wird unterstützt
km wird unterstützt
cols ist 101
lines ist 15
colors ist 8
pairs ist 64
cup ist \E[%i%p1 %d;%p2 %dH
kf2 ist \EOQ
flash ist \E[?5h$<100/>
hpa ist \E[%i%p1 %dG
Bis auf die kryptischen Escape-Sequenzen sollte Ihnen das Verstehen der Ausgabe hier nicht schwer fallen. Aber was hat es mit den Escape-Sequenzen auf sich? Als Beispiel soll hierbei cup verwendet werden, womit Sie einen Cursor auf eine bestimmte Zeile und Spalte setzen können.
\E[%i%p1 %d;%p2 %dH
Man spricht dabei von einem parametrisierten String. So benötigt cup zwei Parameter – nämlich die Zeile und die Spalte –, um zu wissen, wohin mit dem Cursor. Der Escape-String sieht schlimmer aus, als er ist. Zerlegt man diesen in Worten, sieht das so aus:
\E[%i%p1 %d;%p2 %dH
Entspricht:
Escape [ <zeile>; <spalte> H
Hinweis Wollen Sie alle Eigenschaften des aktuellen Terminaltyps auf dem Bildschirm ausgeben lassen, können Sie die Anwendung infocmp starten.
|
Ich möchte Sie aber jetzt gar nicht dazu auffordern, mit den Escape-Sequenzen zu experimentieren, sondern Ihnen lediglich die Bedeutung der einzelnen (kryptisch aussehenden) Zeichen erklären, um anschließend die Funktionen zu verwenden, die Ihnen das ganze Gewusel ersparen können.
Tabelle 13.7
Zerlegung des Escape-Strings
Zeichen
|
Bedeutung
|
\E
|
Escape auf dem Bildschirm ausgeben
|
[
|
Zeichen '[' auf dem Bildschirm ausgeben
|
%i
|
Inkrementiert alle %p-Werte um 1.
|
%p1
|
Legt den Wert von p1 auf einen internen terminfo-Stack.
|
%d
|
Holt den Wert p1 vom terminfo-Stack und gibt diesen auf dem Bildschirm aus.
|
;
|
';' auf dem Bildschirm ausgeben.
|
%p2
|
Legt den Wert von p2 auf einen internen terminfo-Stack.
|
%d
|
Holt den Wert p2 vom terminfo-Stack und gibt diesen auf dem Bildschirm aus.
|
H
|
Gibt 'H' auf dem Bildschirm aus.
|
Dies sieht sehr kompliziert aus. Sie können sich jetzt hinsetzen und ein paar hundert Zeilen Code in diesem Stile schreiben – oder Sie tun es mir gleich und belassen es hierbei und wenden sich den nun folgenden Funktionen zu, die Ihnen das Denken »abnehmen« (weshalb Sie den Escape-String nicht unbedingt verstehen müssen).
13.2.4 Mit terminfo-Eigenschaften arbeiten – putp(), tputs(), tparm()
Jetzt, wo Sie die terminfo-Eigenschaften abfragen können, werden Sie diese wohl auch anwenden wollen. Hierfür können folgende drei Funktionen verwendet werden:
#include <term.h>
#include <curses.h>
int putp( const char *str );
int tputs( const char *str, int affcnt, int (*putc)(int) );
char *tparm( const char *str, long p1, long p2, ..., long p9 );
Mit den Funktionen putp() und tputs() geben Sie die (mit tigetstr()) präparierten Escape-Strings auf dem Bildschirm aus. Die Funktion putp() gibt str auf die Standardausgabe aus und ist gleichwertig zum Funktionsaufruf tputs(str, 1, putchar). Die Funktion tputs() ist ein wenig umfangreicher. affcnt entspricht der Anzahl der Zeilen, wenn str ein mehrzeiliger Escape-String ist. putc ist ein Funktionszeiger auf eine putchar()-ähnliche Funktion, die einzelne Zeichen ausgibt.
Um z. B. den Cursor auf eine bestimmte Zeile und Spalte zu setzen, werden hierzu parametrisierte Strings (im Beispiel von cup sind dies %p1 und %p2) verwendet. Da die Funktion tigetstr() nur einen String als Parameter erwartet, ist hierzu eine Funktion mit einer flexibleren Anzahl an Argumenten nötig, die den String für putp() bzw. tputs() noch entsprechend anpasst, da es ja neben diesem Beispiel (cup) auch parametrisierte Strings mit einer anderen Anzahl von Argumenten gibt. Dies erledigt man mit der Funktion tparm(). Damit wird der parametrisierte String mit den Parametern p1 bis p9 angepasst – sprich, im Falle von cup, mit den Werten der Zeile und Spalte belegt. Es liegt außerdem in Ihrer Verantwortung, die Funktion tparm() mit entsprechend vielen (%p1 ... %p9) Parametern in der richtigen Reihenfolge zu belegen.
Das folgende Listing stellt eine Erweiterung des vorherigen Beispiels dar und zeigt Ihnen, wie Sie die Funktionen tparm() und putp() anwenden können. Es werden dabei die Funktionen zum Löschen eines Bildschirms, zum Setzen des Cursors auf eine bestimmte Zeile und Spalte und zum kurzen Blinken des Bildschirms demonstriert.
/* terminfo2.c */
#include <stdio.h>
#include <stdlib.h>
#include <term.h>
#include <curses.h>
#define CAPS 4
static void clrscr (void) {
const char *ptr = tigetstr ("clear");
putp (ptr);
}
static void mvcursor (int zeile, int spalte) {
const char *cap = tigetstr ("cup");
putp (tparm (cap, zeile, spalte));
}
static void myflash (void) {
const char *flash = tigetstr ("flash");
putp (flash);
putp (flash);
}
int main (void) {
int i, ret = 0;
char *buf;
const char *boolcaps[CAPS] = { "am", "bm", "bce", "km" };
const char *numcaps[CAPS]={"cols", "lines", "colors", "pairs"};
const char *strcaps[CAPS] = { "cup", "kf2", "flash", "hpa" };
ret = setupterm (NULL, fileno (stdin), NULL);
if (ret != 0) {
perror ("setupterm()");
exit (EXIT_FAILURE);
}
// Bildschirm löschen
clrscr ();
for (i = 0; i < CAPS; i++) {
ret = tigetflag (boolcaps[i]);
mvcursor (i + 2, 10);
if (ret == -1)
printf ("%s wird nicht unterstützt\n", boolcaps[i]);
else
printf ("%s wird unterstützt\n", boolcaps[i]);
}
// Cursor neu positionieren
mvcursor (10, 10);
printf ("Weiter mit ENTER\n");
getchar ();
clrscr ();
for (i = 0; i < CAPS; i++) {
ret = tigetnum (numcaps[i]);
mvcursor (i + 2, 10);
if (ret == -1)
printf ("%s wird nicht unterstützt\n", numcaps[i]);
else
printf ("%s ist %d\n", numcaps[i], ret);
}
mvcursor (10, 10);
printf ("Weiter mit ENTER\n");
getchar ();
clrscr ();
for (i = 0; i < CAPS; i++) {
buf = tigetstr (strcaps[i]);
mvcursor (i + 2, 10);
if (buf == NULL)
printf ("%s wird nicht unterstützt\n", strcaps[i]);
else
printf ("%s ist %\\E%s\n", strcaps[i], &buf[1]);
}
mvcursor (10, 10);
printf ("Weiter mit ENTER\n");
getchar ();
// Bildschirm kurz blinken lassen
myflash ();
clrscr ();
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o terminfo2 terminfo2.c -lncurses
$ ./terminfo2
---[Bildschirm wird gelöscht]---
am wird unterstützt
bm wird nicht unterstützt
bce wird unterstützt
km wird unterstützt
Weiter mit ENTER
...
Sie haben hier einen kleinen Einblick zu Schnittstellen einer relativ niedrigen Ebene zur Manipulation von Terminals erhalten. Für mehr Details sollten Sie auf entsprechende Manual Pages terminfo(5), termios(3), termcap(5) und term(5) zurückgreifen – oder Sie verwenden den auf den nächsten Seiten beschriebenen (einfacheren) Weg mit der curses-Bibliothek.
|