Sehr Umfangreiche Webseite zum Programmieren in C Perl CGI, Skripting, Linux, Systemprogrammierung Terminal Konsole Terminal Ein/Ausgabe ioctlC C++ C/C++ ANSI C Linux Linuxsystemprogrammierung Linuxsystemprogrammieren Terminal Konsole Terminal Ein/Ausgabe ioctl 6. Terminal E/A

(Fortsetzung) 6.1. Terminalattribute (tcgetattr, tcsetattr)           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Wenn Sie jetzt z.B. das Sonderzeichen EOF (STRG+C) verändern wollen, können Sie dies so machen ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

int main()
{
 struct termios term;
 int c;

 if((tcgetattr(STDIN_FILENO, &term)) < 0)
   {
     printf("Fehler bei tcgetattr.......\n");
     exit(0);
   }

 term.c_cc[VEOF] = 27; /*ASCII-Code 27 = ESC == EOF*/

 if((tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)) < 0)
  {
     printf("Fehler bei tcsetattr........\n");
     exit(1);
   }

 while((c=getchar()) != EOF)
     putchar(c);

 return 0;
}

Bei diesem Beispiel haben Sie das Sonderzeichen EOF verändert. Durch ...

term.c_cc[VEOF] = 27;

... bekommt unser Sonderzeichen EOF (VEOF) den ASCII-Code 27 zugewiesen, der das Escape-Zeichen darstellt. Mit ...

if((tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)) > 0)

setzen Sie die Attributsänderung in Kraft. Jetzt folgt der Test, ob es auch funktioniert mit ...

while((c=getchar()) != EOF)
      putchar(c);

So lange bis Sie EOF betätigen, könne Sie nun etwas eingeben. In unserem Beispiel wäre das Programm zu Ende, wenn die Escape (ESC)-Taste gedrückt wird, anstatt wie gewohnt die Tastenkombination STRG+C.

Da nicht jeder sämtlich ASCII-Code Zeichen im Kopf hat, ist in der Headerdatei <termios.h> noch folgendes definiert ...

#ifndef CTRL
   #define CTRL(ch) ((ch)&0x1F)
#endif

Falls dies nicht bei Ihnen vorhanden sein sollte, so wissen Sie ja jetzt wie Sie es selbst definieren können. Somit könnten Sie eine Eingabe eines Zeichen wie z.B. die Tastenkombination STRG+Y folgendermaßen festlegen ...

term.c_cc[VEOF] = CTRL('Y');

Jetzt folgt ein kurzer Abschnitt, wie Sie die einzelnen Flags, die Sie oben in den Tabelle kennengelernt haben, für c_iflag,c_oflag,c_cflag und c_lflag setzen bzw. löschen können ...

Flag(s) setzen:

c_iflag | <flag>               //einzelnes flag setzen
c_iflag | <flag1|flag2|flag3>  //mehrere flags setzen auch mit der ODER Verknüpfung

Flag(s) löschen:

c_iflag & ~<flag>               //einzelnes Flag löschen
c_iflag & ~<flag1|flag2|flag3>  //mehrere Flags löschen

Nun ein etwas längeres Beispiel dazu ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

enum{ERROR=-1,SUCCESS,ONEBYTE};

static struct termios BACKUP_TTY; /*Altes Terminal wiederherstellen*/

/*Eingabekanal wird so umgeändert damit die Tasten einzeln*/
/*abgefragt werden können*/

int new_tty(int fd)
{
 struct termios buffer;

/*Wir erfragen nach den Attributen des Terminals und übergeben diese*/
/*dann an buffer. BACKUP_TTY dient bei Programmende zur wiederherstellung*/
/* der alten Attribute und bleibt somit unberührt.*/

 if((tcgetattr(fd, &BACKUP_TTY)) == ERROR)
     return ERROR;

 buffer = BACKUP_TTY;

/*Lokale Flags werden gelöscht : ECHO = Zeichenausgabe auf Bildschirm*/
/*   ICANON = Zeilenorientierter Eingabemodus*/
/*   ISIG = Terminal Steuerzeichen */

 buffer.c_lflag = buffer.c_lflag & ~(ECHO|ICANON|ISIG);

/*VMIN=Anzahl der Bytes die gelesen werden müssen, bevor read() zurückkehrt*/
/*   In unserem Beispiel 1Byte für 1 Zeichen*/

 buffer.c_cc[VMIN] = 1;

/*Wir setzen jetzt die von uns gewünschten Attribute*/

 if((tcsetattr(fd, TCSAFLUSH, &buffer)) == ERROR)
     return ERROR;

 return SUCCESS;
}


/*Ursprüngliches Terminal wiederherstellen*/

int restore_tty(int fd)
{
 if((tcsetattr(fd, TCSAFLUSH, &BACKUP_TTY)) == ERROR)
    return ERROR;

 return SUCCESS;
}


int main(int argc, char **argv)
{
 int rd;
 char c, buffer[10];

/*Setzen des neuen Modus*/

 if(new_tty(STDIN_FILENO) == ERROR)
   {
     printf("Fehler bei der Funktion new_tty().....\n");
     exit(0);
   }

/*Erste Zeichen lesen*/

 if(read(STDIN_FILENO, &c, 1) < ONEBYTE)
   {
     printf("Fehler bei read.....\n");
     restore_tty(STDIN_FILENO);
     exit(0);
   }


/*Haben wir ein ESC ('\E') gelesen? */

 if(c==27)
   {
/*Jep eine Escape-Sequenz, wir wollen den Rest*/
/*   der Zeichen auslesen*/

     rd=read(STDIN_FILENO, buffer, 4);
     buffer[rd]='\0';        /*String terminieren*/

/*Folgende Werte haben die Funktionstasten in der Term */
/*   F1 = \E[[A  */
/*   F2 = \E[[B   */
/*   PFEIL RECHTS= \E[C  */
/*   PFEIL LINKS = \E[D  */
/*   PFEIL RUNTER= \E[B  */
/*   PFEIL HOCH = \E[A   */
/*   POS 1 = \E[1~       */
/*   BILD RUNTER = \E[6~ */
/*   BILD HOCH = \E[5~ */

     if(strcmp(buffer,"[[A") == SUCCESS)
          printf("F1\n");
     if(strcmp(buffer,"[[B") == SUCCESS)
         printf("F2\n");
      if(strcmp(buffer,"[C") == SUCCESS)
         printf("->\n");
      if(strcmp(buffer,"[D") == SUCCESS)
         printf("<-\n");
      if(strcmp(buffer,"[B") == SUCCESS)
         printf("V\n");
      if(strcmp(buffer,"[A") == SUCCESS)
         printf("^\n");
      if(strcmp(buffer,"[1~") == SUCCESS)
         printf("POS 1\n");
      if(strcmp(buffer,"[6~") == SUCCESS)
         printf("BILD RUNTER\n");
      if(strcmp(buffer,"[5~") == SUCCESS)
         printf("BILD HOCH\n");

/*Nein kein ESC..............*/
   }
 else
   {
     if((c<32) || (c==127))
          printf("%d\n",c); /*Numerischen Wert Ausgeben*/
     else
          printf("%c\n",c); /*Zeichen ausgeben*/
   }
 restore_tty(STDIN_FILENO);

 return SUCCESS;
}

Da ich das Programm ausführlich Dokumentiert habe, möchte ich dazu nicht mehr viel Worte verlieren. Nur sollten Sie zur Ausführung ein ECHTES Terminal öffnen, sonst kann es passieren, wenn Sie z.B. die Taste F1 drücken gar nichts ausgegeben wird. Also wenn Sie jetzt unter X arbeiten drücken sie die Tastenkombination STRG+ALT+F1 und starten Sie das Programm dann von dem echten Terminal.

Nun zu einem weiterem Programm. Wie kann ich eine Taste abfragen oder abfangen? Zuerst müssen Sie das Terminal in den raw-Modus schalten. Ich habe das Programm ausführlich Dokumentiert ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

enum{ERROR=-1, SUCCESS};

static struct termios new_io;
static struct termios old_io;


/*Funktion schaltet das Terminal in den raw-Modus:*/
/*Folgende Eingabeflags werden gelöscht : BRKINT, ICRNL, INPCK, ISTRIP, IXON*/
/*Folgende Kontrollflags werden gelöscht: CSIZE, PERENB*/
/*Folgende Ausgabeflags werden gelöscht : OPOST*/
/*Folgende Kontrollflags werden gelöscht: ECHO, ICANON, IEXTEN, ISIG*/
/*Gesetzt wird das Kontrollflag CS8 was Bedeuteted das ein Zeichen 8 Bit breit ist*/
/*Steuerzeichen : Leseoperation liefert 1 Byte VMIN=1 VTIME=1 */

int raw(int fd)
{
/*Sichern unseres Terminals*/
 if((tcgetattr(fd, &old_io)) == ERROR)
     return ERROR;

 new_io = old_io;

/*Wir verändern jetzt die Flags für den raw-Modus*/

 new_io.c_iflag = new_io.c_iflag & ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON);
 new_io.c_oflag = new_io.c_iflag & ~(OPOST);
 new_io.c_cflag = new_io.c_cflag & ~(CSIZE|PARENB);
 new_io.c_lflag = new_io.c_lflag & ~(ECHO|ICANON|IEXTEN|ISIG);
 new_io.c_cflag = new_io.c_cflag | CS8;
 new_io.c_cc[VMIN] = 1;
 new_io.c_cc[VTIME]= 0;

/*Jetzt setzten wir den raw-Modus*/

 if((tcsetattr(fd, TCSAFLUSH, &new_io)) == ERROR)
       return ERROR;

 return SUCCESS;
}


/*Funktion zur Abfrage einer Taste*/

int getch()
{
 int c;

 if(raw(STDIN_FILENO) == ERROR)
   {
     printf("Fehler bei der Funktion raw......\n");
     exit(0);
   }

 c = getchar();

/*Alten Terminal-Modus wiederherstellen*/

 tcsetattr(STDIN_FILENO, TCSANOW, &old_io);

 return c;
}


int main()
{
 int zeichen;

 printf("Bitte 'q' drücken um das Programm zu beenden!\n");
 while((zeichen=getch()) != 'q');

 return 0;
}

Die Funktion raw() schaltet das Terminal in den raw-Modus und mit der Funktion getch() können Sie dann nach einer einzelnen oder irgendeiner Taste abfragen. Den großen Overhead der Funktion raw() hätten wir uns ersparen können, da Linux/Unix auf dafür eine Funktion für uns bereithält ...

int cfmakeraw(struct termios *tty_zeiger);

Diese Funktion die ebenfalls in der Headerdatei <termios.h> definiert und damit ist es uns einfacher möglich das Terminal in den raw-Modus zu setzen. Wollen wir das Beispiel mal auf das Programm zuvor anwenden ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

enum{ERROR=-1,SUCCESS};
typedef struct termios TTY;

int kbhit()
{
 TTY new_io, old_io;
 int c;

 if((tcgetattr(STDIN_FILENO, &old_io)) == SUCCESS)
   {
      cfmakeraw(&new_io); //setzen den raw-Modus für new_io
      tcsetattr(STDIN_FILENO, TCSANOW, &new_io);
   }
 else
   {
      printf("Konnte den raw-Modus nicht setzen!!!\n");
      exit(0);
   }

 c = getchar();

/*Ürspüngliche Eigenschaft des Terminals wieder herstellen*/
 tcsetattr(STDIN_FILENO, TCSANOW, &old_io);

 return c;
}

int main()
{
  printf("Bitte Taste drücken!!\n");
  while(kbhit() == SUCCESS);
  printf("Danke\n");

  return 0;
}

Natürlich will ich Ihnen nicht vorenthalten, wie Sie ein Terminal in den cbreak-Modus schalten können. Im cbreak-Modus müssen Sie ledeglich die beiden lokalen Flags zur Ausgabe ECHO und den kanonischen Modus ICANON löschen. Oder genauer, die Bits auf 0 setzen. Im cbreak-Modus haben Sie wieder die Möglichkeit Steuerzeichen zu verwenden (z.B. STRG+C) ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

enum{ERROR=-1, SUCCESS};

static struct termios new_io;
static struct termios old_io;


/*Funktion schaltet das Terminal in den cbreak-Modus: */
/*   Kontrollflag ECHO und ICANON auf 0 setzen        */
/*   Steuerzeichen : Leseoperation liefert 1 Byte VMIN=1 VTIME=1 */

int cbreak(int fd)
{
/*Sichern unseres Terminals*/
  if((tcgetattr(fd, &old_io)) == ERROR)
        return ERROR;

  new_io = old_io;

/*Wir verändern jetzt die Flags für den cbreak-Modus*/

  new_io.c_lflag = new_io.c_lflag & ~(ECHO|ICANON);
  new_io.c_cc[VMIN] = 1;
  new_io.c_cc[VTIME]= 0;

/*Jetzt setzten wir den cbreak-Modus*/

  if((tcsetattr(fd, TCSAFLUSH, &new_io)) == ERROR)
         return ERROR;

  return SUCCESS;
}


/*Funktion zur Abfrage einer Taste*/

int getch_cbreak()
{
  int c;

  if(cbreak(STDIN_FILENO) == ERROR)
    {
      printf("Fehler bei der Funktion cbreak......\n");
      exit(0);
    }

  c = getchar();

/*Alten Terminal-Modus wiederherstellen*/

  tcsetattr(STDIN_FILENO, TCSANOW, &old_io);

  return c;
}


int main()
{
  int zeichen;

  printf("Bitte 'q' drücken um das Programm zu beenden!\n");
  while((zeichen=getch_cbreak()) != 'q');

  return 0;
}

6.2. Terminal-Idenfizierung (ctermid, ttyname)           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Um den Namen des Terminals zu erfragen, gibt es die Funktion ...

char *ctermid(char *term_zeiger);

Die Funktion gibt die Adresse zurück, in der der Name des Kontrollterminals steht. Dies dürfte meistens /dev/tty sein. Für den term_zeiger sollten Sie mindestens L_ctermid Bytes Speicherplatz reservieren. Die Konstante L_ctermid ist in der Headerdatei <stdio.h> definiert. Beispiel ...

char terminalname[L_ctermid];

printf("Terminalname = %s \n",ctermid(terminalname));

Hierzu nun ein kurzes Beispiel der Funktion ctermid ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

char termname[L_ctermid];

char *tty_name()
 {
     return ctermid(termname);
 }


int main()
{
  printf("Terminalname = %s\n",tty_name());
  return 0;
}

Wenn Sie die Adresse des Terminal-Pfadnamen benötigen, dann gibt es die Funktion ...

char *ttyname(int fd);

Bei Fehler liefert diese Funktion den NULL-Zeiger zurück ...

#include <stdio.h>
#include <unistd.h>
#include <termios.h>


char *tty_name()
 {
    return ttyname(STDIN_FILENO);
 }


int main(int argc, char **argv)
{
  printf("Nachricht von Terminal %s\n%s",tty_name(),*++argv);
  return 0;
}

Öffnen Sie nun zwei Pseudo-Konsolen unter X. Ermitteln Sie zuerst mit diesem Programm den Pfad der Konsole. In meinem Beispiel hat Konsole 1 den Pfad /dev/pts/2 und Konsole 2 den Pfad /dev/pts/3

Führen Sie nun das Programm folgendermaßen aus (Wir gehen mal davon aus, das Programm oben heißt "sende")-> Schreiben Sie in der Konsole 1 (/dev/pts/2) folgendes ...

sende hallo > /dev/pts/3

Auf der Konsole /dev/pts/3 steht jetzt ...

Nachricht von Terminal /dev/pts/2
hallo

Wenn Sie wollen können Sie jetzt vom Terminal /dev/pts/3, wie eben gemacht, eine Nachricht an /dev/pts/2 senden. Dies funktioniert daher, da Sie mit ...

ttyname(STDIN_FILENO)

... den Terminalnamen der Standardeingabe abfragen. Sie können ja mal anstatt der Standardeingabe die Standardausgabe abfragen (STDOUT_FILENO).

Weiter mit 6.3. Erfragen und Setzen der Baudrate