web-dev-qa-db-fra.com

C entrée clavier non bloquante

J'essaie d'écrire un programme en C (sous Linux) en boucle jusqu'à ce que l'utilisateur appuie sur une touche, mais cela ne devrait pas nécessiter une pression sur une touche pour continuer chaque boucle.

Y a-t-il un moyen simple de faire cela? Je pense que je pourrais éventuellement le faire avec select() mais cela me semble beaucoup de travail.

Sinon, y a-t-il un moyen d'attraper un ctrl-c appuyez sur la touche pour effectuer le nettoyage avant la fermeture du programme au lieu de ne pas bloquer io?

75
Zxaos

Comme déjà indiqué, vous pouvez utiliser sigaction pour capturer ctrl-c ou select pour capturer toute entrée standard.

Notez toutefois qu'avec cette dernière méthode, vous devez également définir le TTY pour qu'il soit en mode caractère à la fois plutôt qu'en mode ligne à la fois. Ce dernier est la valeur par défaut - si vous tapez une ligne de texte, il n'est pas envoyé au stdin du programme en cours tant que vous n'avez pas appuyé sur Entrée.

Vous devez utiliser la fonction tcsetattr() pour désactiver le mode ICANON et probablement aussi pour désactiver ECHO. De mémoire, vous devez également remettre le terminal en mode ICANON lorsque le programme se ferme!

Par souci d’exhaustivité, voici le code que je viens de bricoler (nb: pas de vérification d’erreur!) Qui configure un TTY Unix et émule les fonctions <conio.h> de kbhit() et 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 */
}
59
Alnitak

select() est un peu trop bas pour des raisons pratiques. Je vous suggère d'utiliser la bibliothèque ncurses pour mettre le terminal en mode cbreak et en mode délai, puis appelez getch(), qui retournera ERR si aucun caractère n'est prêt:

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

À ce stade, vous pouvez appeler getch sans bloquer.

15
Norman Ramsey

Sur les systèmes UNIX, vous pouvez utiliser l'appel sigaction pour enregistrer un gestionnaire de signaux pour le signal SIGINT qui représente la séquence de touches Ctrl + C. Le gestionnaire de signal peut définir un indicateur qui sera vérifié dans la boucle, le rendant ainsi brisé de manière appropriée.

11
Mehrdad Afshari

Vous voulez probablement kbhit();

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

using namespace std;

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

cela peut ne pas fonctionner sur tous les environnements. Un moyen portable serait de créer un thread de surveillance et de définir un indicateur sur getch();

6
Jon Clegg

La bibliothèque curses peut être utilisée à cette fin. Bien entendu, select() et les gestionnaires de signaux peuvent également être utilisés dans une certaine mesure. 

3
PolyThinker

Un autre moyen d’obtenir une entrée clavier non bloquante consiste à ouvrir le fichier de périphérique et à le lire! 

Vous devez connaître le fichier de périphérique que vous recherchez, un de/dev/input/event *. Vous pouvez exécuter cat/proc/bus/input/devices pour trouver le périphérique souhaité.

Ce code fonctionne pour moi (exécuté en tant qu'administrateur).

  #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

Si vous êtes content d'attraper Control-C, c'est chose faite. Si vous voulez vraiment des E/S non bloquantes mais que vous ne voulez pas de la bibliothèque curses, une autre solution consiste à déplacer verrou, stock et baril vers la bibliothèque AT & T sfio . C'est une belle bibliothèque à motifs C stdio mais plus souple, thread-safe et qui fonctionne mieux. (sfio signifie «Safe, fast I/O»).

2
Norman Ramsey

Il n’existe aucun moyen portable de le faire, mais select () pourrait être un bon moyen. Voir http://c-faq.com/osdep/readavail.html pour d'autres solutions possibles.

1
Nate879

Voici une fonction pour le faire pour vous. Vous avez besoin de termios.h fourni avec les systèmes 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);
}

En décomposant ceci: tcgetattr récupère les informations de terminal actuelles et les stocke dans t. Si cmd est à 1, l'indicateur d'entrée locale dans t est défini sur une entrée non bloquante. Sinon, il est réinitialisé. Ensuite, tcsetattr modifie l’entrée standard en t.

Si vous ne réinitialisez pas l'entrée standard à la fin de votre programme, vous rencontrerez des problèmes avec votre shell.

0
112