webentwicklung-frage-antwort-db.com.de

C nicht blockierende Tastatureingabe

Ich versuche, ein Programm in C (unter Linux) zu schreiben, das so lange eine Schleife durchläuft, bis der Benutzer eine Taste drückt, es sollte jedoch kein Tastendruck erforderlich sein, um jede Schleife fortzusetzen.

Gibt es eine einfache Möglichkeit, dies zu tun? Ich denke, ich könnte es möglicherweise mit select() machen, aber das scheint eine Menge Arbeit zu sein.

Alternativ gibt es eine Möglichkeit, a zu fangen ctrl-c Drücken Sie die Taste, um vor dem Schließen des Programms zu bereinigen, anstatt es nicht zu blockieren.

75
Zxaos

Wie bereits erwähnt, können Sie sigaction verwenden, um Strg-c abzufangen, oder select, um alle Standardeingaben abzufangen.

Beachten Sie jedoch, dass Sie bei der letzteren Methode auch den TTY-Modus so einstellen müssen, dass er im Zeichenmodus und nicht im Zeilenmodus ist. Letzteres ist die Standardeinstellung. Wenn Sie eine Textzeile eingeben, wird diese nicht an das Standardprogramm des laufenden Programms gesendet, bis Sie die Eingabetaste drücken.

Sie müssen die Funktion tcsetattr() verwenden, um den ICANON-Modus zu deaktivieren und wahrscheinlich auch ECHO zu deaktivieren. Aus dem Speicher muss das Terminal auch wieder in den ICANON-Modus versetzt werden, wenn das Programm beendet wird!

Der Vollständigkeit halber sei hier etwas Code angeführt, der einen Unix-TTY erstellt und die DOS-<conio.h>-Funktionen kbhit() und getch() emuliert:

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
        /* do some work */
    }
    (void)getch(); /* consume the character */
}
59
Alnitak

select() ist für Komfort etwas zu niedrig. Ich schlage vor, dass Sie die Bibliothek ncurses verwenden, um das Terminal in den Cbreak-Modus und den Verzögerungsmodus zu versetzen, und dann getch() aufrufen, die ERR zurückgibt, wenn kein Zeichen bereit ist:

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);

An diesem Punkt können Sie getch aufrufen, ohne zu blockieren.

15
Norman Ramsey

Auf UNIX-Systemen können Sie mit dem Aufruf sigaction einen Signalhandler für das Signal SIGINT registrieren, das die Tastenkombination Strg + C darstellt. Der Signal-Handler kann ein Flag setzen, das in der Schleife geprüft wird, so dass es entsprechend bricht.

11
Mehrdad Afshari

Sie möchten wahrscheinlich kbhit();

//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

dies funktioniert möglicherweise nicht in allen Umgebungen. Eine tragbare Methode wäre, einen Überwachungsthread zu erstellen und ein Flag für getch(); zu setzen.

6
Jon Clegg

Zu diesem Zweck kann die Curses-Bibliothek verwendet werden. Natürlich können auch select() und Signalhandler in gewissem Umfang verwendet werden. 

3
PolyThinker

Eine andere Möglichkeit, nicht blockierende Tastatureingaben zu erhalten, besteht darin, die Gerätedatei zu öffnen und zu lesen! 

Sie müssen die Gerätedatei kennen, nach der Sie suchen, z. B./dev/input/event *. Sie können cat/proc/bus/input/devices ausführen, um das gewünschte Gerät zu finden.

Dieser Code funktioniert für mich (als Administrator ausgeführt).

  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <linux/input.h>

  int main(int argc, char** argv)
  {
      int fd, bytes;
      struct input_event data;

      const char *pDevice = "/dev/input/event2";

      // Open Keyboard
      fd = open(pDevice, O_RDONLY | O_NONBLOCK);
      if(fd == -1)
      {
          printf("ERROR Opening %s\n", pDevice);
          return -1;
      }

      while(1)
      {
          // Read Keyboard Data
          bytes = read(fd, &data, sizeof(data));
          if(bytes > 0)
          {
              printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
          }
          else
          {
              // Nothing read
              sleep(1);
          }
      }

      return 0;
   }
3
JustinB

Wenn Sie glücklich sind, Control-C zu fangen, ist dies ein abgeschlossener Deal. Wenn Sie wirklich nicht blockierende E/A-Vorgänge wünschen, die Fluchbibliothek jedoch nicht möchten, können Sie Lock, Stock und Barrel in die Bibliothek AT & T sfio verschieben. Es ist eine schöne Bibliothek, die auf C stdio gemustert ist, aber flexibler, Thread-sicherer und besser ist. (sfio steht für sichere, schnelle E/A.)

2
Norman Ramsey

Es gibt keinen tragbaren Weg, dies zu tun, aber select () ist möglicherweise ein guter Weg. Siehe http://c-faq.com/osdep/readavail.html für weitere mögliche Lösungen.

1
Nate879

Hier ist eine Funktion, um dies für Sie zu tun. Sie benötigen termios.h, der mit POSIX-Systemen geliefert wird.

#include <termios.h>
void stdin_set(int cmd)
{
    struct termios t;
    tcgetattr(1,&t);
    switch (cmd) {
    case 1:
            t.c_lflag &= ~ICANON;
            break;
    default:
            t.c_lflag |= ICANON;
            break;
    }
    tcsetattr(1,0,&t);
}

Das Aufteilen: tcgetattr ruft die aktuellen Terminalinformationen ab und speichert sie in t. Wenn cmd 1 ist, wird das lokale Eingangsflag in t auf einen nicht blockierenden Eingang gesetzt. Ansonsten wird es zurückgesetzt. Dann ändert tcsetattr die Standardeingabe in t.

Wenn Sie die Standardeingabe am Ende Ihres Programms nicht zurücksetzen, treten Probleme in Ihrer Shell auf.

0
112