Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 13 Terminal E/A und Benutzerschnittstellen für die Konsole
  gp 13.1 termios
    gp 13.1.1 Terminalattribute bearbeiten
    gp 13.1.2 Flags setzen und löschen
    gp 13.1.3 Terminalidentifizierung
    gp 13.1.4 Geschwindigkeitskontrolle – Baudrate von Terminals einstellen
  gp 13.2 terminfo
    gp 13.2.1 terminfo verwenden
    gp 13.2.2 terminfo initialisieren – setupterm()
    gp 13.2.3 Eigenschaften eines Terminals (Finden von capnames) – tigetflag(), tigetnum() und tigetstr()
    gp 13.2.4 Mit terminfo-Eigenschaften arbeiten – putp(), tputs(), tparm()
  gp 13.3 ncurses – Halbgrafik
    gp 13.3.1 ncurses initialisieren
    gp 13.3.2 Tastaturmodus und Ein- und Ausgabe
    gp 13.3.3 Eigenschaft der Fenster
    gp 13.3.4 Scrolling
    gp 13.3.5 Attribute und Farben setzen
    gp 13.3.6 Fensterroutinen
    gp 13.3.7 Mausprogrammierung mit ncurses


Rheinwerk Computing

13.2 terminfo  downtop

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

Rheinwerk Computing

13.2.1 terminfo verwenden  downtop

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:

gp  Boolean – Damit können Sie überprüfen, ob ein Terminal bestimmte Eigenschaften unterstützt oder nicht.
gp  Nummerisch – Nummerische capnames spezifizieren gewöhnlich bestimmte Größenangaben, z. B. wie viele Spalten oder Zeilen ein Terminal hat.
gp  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.


Rheinwerk Computing

13.2.2 terminfo initialisieren – setupterm()  downtop

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.


Rheinwerk Computing

13.2.3 Eigenschaften eines Terminals (Finden von capnames) – tigetflag(), tigetnum() und tigetstr()  downtop

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).


Rheinwerk Computing

13.2.4 Mit terminfo-Eigenschaften arbeiten – putp(), tputs(), tparm()  toptop

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.

 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern