C nieblokujące wejście klawiatury

Próbuję napisać program w języku C (na Linuksie), który zapętla się, dopóki użytkownik nie naciśnie klawisza, ale nie powinien wymagać naciśnięcia klawisza, aby kontynuować każdą pętlę.

Czy jest na to prosty sposób? Myślę, że mógłbym to zrobić z select(), ale to wygląda na dużo pracy.

Alternatywnie, czy istnieje sposób, aby złapać ctrl-C nacisnąć klawisz do czyszczenia przed zamknięciem programu zamiast nieblokującego io?

Author: alk, 2009-01-16

10 answers

Jak już wspomniano, możesz użyć sigaction do pułapki ctrl-C lub select do pułapki dowolnego standardowego wejścia.

Zauważ jednak, że w tej ostatniej metodzie musisz również ustawić TTY tak, aby był w trybie znak-w-czasie, a nie w trybie linia-w-czasie. Ta ostatnia jest domyślna - jeśli wpiszesz wiersz tekstu, nie zostanie on wysłany na stdin uruchomionego programu, dopóki nie naciśniesz klawisza enter.

Musisz użyć funkcji tcsetattr(), aby wyłączyć tryb ICANON i prawdopodobnie również wyłączyć ECHO. Od pamięci, musisz również ustawić terminal z powrotem w trybie ICANON po zakończeniu programu!

Dla kompletności, oto jakiś kod, który właśnie zapukałem (nb: bez sprawdzania błędów!), który ustawia uniksowy TTY i emuluje funkcje DOS <conio.h> kbhit() i 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);
}

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 */
}
 53
Author: Alnitak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-30 08:29:10

select() jest trochę za niski poziom dla wygody. Sugeruję użycie biblioteki ncurses, aby umieścić terminal w trybie cbreak i delay, a następnie wywołać getch(), które zwróci ERR, Jeśli żaden znak nie jest gotowy:

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

W tym momencie możesz zadzwonić getch bez blokowania.

 15
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-16 01:03:13

W systemach uniksowych można użyć wywołania sigaction, aby zarejestrować obsługę sygnału dla sygnału SIGINT, który reprezentuje sekwencję klawiszy Control+C. Obsługa sygnału może ustawić flagę, która będzie sprawdzana w pętli, co spowoduje odpowiednie przerwanie.

 11
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-15 23:31:33

Prawdopodobnie chcesz kbhit();

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

using namespace std;

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

To może nie działać we wszystkich środowiskach. Przenośnym sposobem byłoby utworzenie wątku monitorującego i ustawienie flagi na getch();

 6
Author: Jon Clegg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-15 14:30:09

Innym sposobem uzyskania nieblokującego wejścia z klawiatury jest otwarcie pliku urządzenia i odczytanie go!

Musisz znać plik urządzenia, którego szukasz, jeden z /dev/input/event*. Możesz uruchomić cat / proc/bus/input / devices, aby znaleźć żądane urządzenie.

Ten kod działa dla mnie (Uruchom jako administrator).

  #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
Author: JustinB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-24 21:36:07

Biblioteka curses może być używana do tego celu. Oczywiście, select() i Signal handlers mogą być również używane do pewnego stopnia.

 3
Author: PolyThinker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-12 05:35:59

Jeśli jesteś szczęśliwy, że złapałeś Control-C, to załatwione. Jeśli naprawdę chcesz nieblokujące wejścia / wyjścia, ale nie chcesz biblioteki curses, inną alternatywą jest przeniesienie lock, stock i barrel do AT&T sfio . Ładna biblioteka wzorowana na C stdio, ale bardziej elastyczna, bezpieczna dla wątków i działa lepiej. (SFIO oznacza bezpieczne, szybkie I / O.)

 2
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-27 21:05:45

Nie ma na to przenośnego sposobu, ale select() może być dobrym sposobem. Zobacz http://c-faq.com/osdep/readavail.html aby uzyskać więcej możliwych rozwiązań.

 1
Author: Nate879,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-15 23:33:07

Oto funkcja, aby to zrobić dla Ciebie. Potrzebujesz termios.h, który jest dostarczany z systemami POSIX.

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

Dzieląc to: tcgetattr pobiera bieżące informacje o terminalu i przechowuje je w t. Jeśli cmd wynosi 1, znacznik wejścia lokalnego w {[3] } jest ustawiony na wejście nieblokujące. W przeciwnym razie jest resetowany. Następnie tcsetattr zmienia standardowe wejście na t.

Jeśli nie zresetujesz standardowego wejścia na końcu programu, będziesz miał problemy w powłoce.

 0
Author: 112,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-05 01:00:31

Możesz to zrobić za pomocą select w następujący sposób:

  int nfds = 0;
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(0, &readfds); /* set the stdin in the set of file descriptors to be selected */
  while(1)
  {
     /* Do what you want */
     int count = select(nfds, &readfds, NULL, NULL, NULL);
     if (count > 0) {
      if (FD_ISSET(0, &readfds)) {
          /* If a character was pressed then we get it and exit */
          getchar();
          break;
      }
     }
  }

Nie za dużo pracy: D

 -1
Author: Rod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-04 08:10:43