C nicht blockierende Tastatureingabe

Lesezeit: 6 Minuten

Benutzeravatar von Zxaos
Zxaos

Ich versuche, ein Programm in C (unter Linux) zu schreiben, das eine Schleife durchläuft, bis der Benutzer eine Taste drückt, aber keinen Tastendruck erfordern sollte, um jede Schleife fortzusetzen.

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

Alternativ gibt es eine Möglichkeit, a zu fangen Strgc Tastendruck, um eine Bereinigung durchzuführen, bevor das Programm geschlossen wird, anstatt io nicht zu blockieren?

  • wie wär’s mit Threaderöffnung?

    – Nathan B

    26. Mai 2020 um 14:10 Uhr

  • Ich empfehle einen separaten Thread, der die Tastatureingabe blockiert und die Eingabe bei jedem Tastendruck an den anderen Haupt-Thread weiterleitet. Hier ist ein Beispiel, das ich in Python geschrieben habe. Stellen Sie für C sicher, dass Sie Thread-sichere Message-Passing-Warteschlangen verwenden oder den Abruf von Mutexes nach Bedarf blockieren. Die Python-Warteschlangenbibliothek ist bereits Thread-sicher, aber eine solche Sicherheit für Warteschlangen gibt es natürlich weder in C noch in C++.

    – Gabriel Staples

    3. Februar um 21:28 Uhr

  • Siehe auch: Wie vermeide ich es, die Eingabetaste mit getchar() zu drücken, um nur ein einzelnes Zeichen zu lesen?

    – Gabriel Staples

    3. Februar um 22:54 Uhr


Benutzeravatar von Alnitak
Alnitak

Wie bereits erwähnt, können Sie verwenden sigaction um Strg-C zu fangen, oder select um jede Standardeingabe abzufangen.

Beachten Sie jedoch, dass Sie bei der letzteren Methode auch das TTY so einstellen müssen, dass es sich im Zeichen-zu-Zeit-Modus befindet und nicht im Zeilen-zu-Zeit-Modus. Letzteres ist die Standardeinstellung – wenn Sie eine Textzeile eingeben, wird sie nicht an die Standardeingabe des laufenden Programms gesendet, bis Sie die Eingabetaste drücken.

Sie müssten die verwenden tcsetattr() Funktion zum Deaktivieren des ICANON-Modus und wahrscheinlich auch zum Deaktivieren von ECHO. Aus dem Speicher heraus müssen Sie das Terminal auch wieder in den ICANON-Modus versetzen, wenn das Programm beendet wird!

Nur der Vollständigkeit halber, hier ist ein Code, den ich gerade erstellt habe (Achtung: keine Fehlerprüfung!), der ein Unix-TTY einrichtet und das DOS emuliert <conio.h> Funktionen kbhit() und getch():

#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) > 0;
}

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 */
}

  • Soll getch() zurückgeben, welche Taste gedrückt wurde, oder soll es einfach das Zeichen verbrauchen?

    – Salepate

    19. Juli 2012 um 11:56 Uhr

  • @Salepate soll das Zeichen zurückgeben. Es ist das (void) Bit, das diesen Charakter bekommt und dann wegwirft.

    – Alnitak

    19. Juli 2012 um 12:05 Uhr

  • Oh, ich verstehe, vielleicht mache ich es falsch, aber wenn ich getch() auf einem printf(“%c”); Es zeigt seltsame Zeichen auf dem Bildschirm.

    – Salepate

    19. Juli 2012 um 12:07 Uhr

  • @ Max888 nicht portabel, aber Sie könnten das Programm selbst beenden, wenn das nächste abgerufene Zeichen ^ c (0x03) ist.

    – Alnitak

    27. Juli 2021 um 22:05 Uhr

  • Danke für eine elegante Lösung … Ich habe einen Interrupt-Timer, der dies verursacht kbhit() ab und zu zurückkehren -1 == EINTR …Das heisst kbhit() kehrt zurück true wann es (wohl) zurückkehren sollte false …Ich habe dies behoben, indem ich die geändert habe return Aussage ein kbhit() zu return (select(1, &fds, NULL, NULL, &tv) > 0);

    – Blauchip

    21. Oktober 2021 um 19:41 Uhr

select() ist ein bisschen zu niedrig für die Bequemlichkeit. Ich schlage vor, Sie verwenden die ncurses Bibliothek, um das Terminal in den Cbreak-Modus und den Verzögerungsmodus zu versetzen, und rufen Sie dann auf getch()die zurückkehren wird ERR wenn kein Charakter bereit ist:

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

Da kann man anrufen getch ohne zu blockieren.

  • Ich habe auch über die Verwendung von Flüchen nachgedacht, aber das potenzielle Problem dabei ist, dass der Aufruf von initscr () den Bildschirm löscht und möglicherweise auch die Verwendung von normalem stdio für die Bildschirmausgabe behindert.

    – Alnitak

    16. Januar 2009 um 8:09 Uhr

  • Ja, Flüche sind ziemlich alles oder nichts.

    – Norman Ramsey

    17. Januar 2009 um 3:01 Uhr

Auf UNIX-Systemen können Sie verwenden sigaction Aufruf zum Registrieren eines Signal-Handlers für SIGINT Signal, das die Tastenfolge Strg+C darstellt. Der Signal-Handler kann ein Flag setzen, das in der Schleife überprüft wird, wodurch es entsprechend unterbrochen wird.

  • Ich denke, ich werde dies wahrscheinlich der Einfachheit halber tun, aber ich werde die andere Antwort akzeptieren, da sie näher an dem liegt, was der Titel verlangt.

    – Zxaos

    15. Januar 2009 um 23:37 Uhr

Benutzeravatar von Jon Clegg
Jon Clegg

Du willst 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. Ein tragbarer Weg wäre, einen Überwachungsthread zu erstellen und ein Flag zu setzen getch();

Benutzeravatar von JustinB
Justin B

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

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

Dieser Code funktioniert bei mir (als Administrator ausführen).

  #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;
   }

  • fantastisches Beispiel, aber ich bin auf ein Problem gestoßen, bei dem Xorg weiterhin Tastenereignisse empfangen hat, wenn das Ereignisgerät aufgehört hat, zu senden, wenn mehr als sechs Tasten gehalten wurden: \ seltsam, oder?

    – ThorSummoner

    26. Dezember 2019 um 7:57 Uhr

frogattos Benutzeravatar
Frosch

Dazu kann die Curses-Bibliothek verwendet werden. Na sicher, select() und Signalhandler können bis zu einem gewissen Grad ebenfalls verwendet werden.

  • fantastisches Beispiel, aber ich bin auf ein Problem gestoßen, bei dem Xorg weiterhin Tastenereignisse empfangen hat, wenn das Ereignisgerät aufgehört hat, zu senden, wenn mehr als sechs Tasten gehalten wurden: \ seltsam, oder?

    – ThorSummoner

    26. Dezember 2019 um 7:57 Uhr

Benutzeravatar von indiv
Einzeln

Wenn Sie glücklich sind, nur Control-C zu fangen, ist es eine beschlossene Sache. Wenn Sie wirklich nicht blockierende E/A, aber nicht die Curses-Bibliothek wollen, besteht eine andere Alternative darin, Lock, Stock und Barrel in die zu verschieben AT&T sfio Bibliothek. Es ist eine schöne Bibliothek, die auf C gemustert ist stdio aber flexibler, Thread-sicherer und performanter. (sfio steht für sichere, schnelle I/O.)

1420360cookie-checkC nicht blockierende Tastatureingabe

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy