Question

J'essaie d'écrire un programme en C (sous Linux) qui boucle jusqu'à ce que l'utilisateur appuie sur une touche, mais il ne devrait pas être nécessaire d'appuyer sur une touche pour continuer chaque boucle.

Existe-t-il un moyen simple de procéder? Je pense pouvoir le faire avec select() mais cela me semble beaucoup de travail.

Sinon, y a-t-il moyen d'attraper une ctrl - c pour effectuer un nettoyage avant la fermeture du programme au lieu d'un io non bloquant?

Était-ce utile?

La solution

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

Notez cependant 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 ne sera pas envoyé au stdin du programme en cours tant que vous n’aurez 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 à la fin du programme!

Juste pour être complet, voici le code que je viens de bricoler (nb: pas de vérification d'erreur!) qui configure un TTY Unix et émule les fonctions DOS < conio.h > 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 */
}

Autres conseils

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 renverra ERR si aucun personnage n'est prêt:

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

À ce stade, vous pouvez appeler getch sans bloquer.

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 drapeau qui sera vérifié dans la boucle, ce qui le fera se briser correctement.

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. Une manière portable serait de créer un thread de surveillance et de définir un indicateur sur getch ();

Une autre façon d’obtenir une entrée non bloquante au clavier est d’ouvrir le fichier de périphérique et de le lire!

Vous devez connaître le fichier de périphérique que vous recherchez, l'un des / dev / input / event *. Vous pouvez utiliser 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;
   }

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

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 le Bibliothèque AT & amp; T sfio . C'est une belle bibliothèque à motif C stdio mais plus flexible, thread-safe et qui fonctionne mieux. (sfio signifie E / S sûres et rapides.)

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 plus d'informations. solutions possibles.

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

Décrivez ceci: tcgetattr récupère les informations actuelles du terminal et les stocke dans t . Si cmd vaut 1, l'indicateur d'entrée local 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 aurez des problèmes avec votre shell.

Vous pouvez le faire en utilisant select comme suit:

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

Pas trop de travail: D

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top