Pregunta

Estoy tratando de escribir un programa en C (en Linux) que se repite hasta que el usuario presiona una tecla, pero que no requieren una tecla para continuar cada bucle.

Hay una manera simple de hacer esto?Me imagino que podría hacer con select() pero que parece como un montón de trabajo.

Alternativamente, hay una manera de atrapar a un ctrl-c keypress para hacer la limpieza antes de que el programa se cierra en lugar de no-bloqueo de io?

¿Fue útil?

Solución

Como ya se ha dicho, puede utilizar sigaction para capturar ctrl-c, o select para interceptar la entrada estándar.

Nota sin embargo de que con este último método también es necesario establecer el TTY, así que es de carácter-en-un-tiempo en lugar de la línea-en-un-modo de tiempo.El último es el valor predeterminado si se escribe en una línea de texto no se envía a la ejecución del programa de stdin hasta que pulse enter.

Tendría que usar la tcsetattr() función para apagar ICANON modo, y probablemente también desactiva el ECHO demasiado.De la memoria, usted también tiene que fijar el terminal de nuevo en ICANON modo cuando se sale del programa!

Sólo para la integridad, aquí está el código que acabo de knocked up (nb:ninguna comprobación de errores!) lo que configura un Unix TTY y emula las DOS <conio.h> funciones kbhit() y 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 */
}

Otros consejos

select() es un nivel demasiado bajo para mayor comodidad. Le sugiero que use la biblioteca ncurses para poner el terminal en modo cbreak y modo de retraso, luego llame a getch(), que devolverá ERR si no hay ningún carácter listo:

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

En ese momento puede llamar a getch sin bloquear.

En los sistemas UNIX, puede usar la llamada sigaction para registrar un manejador de señal para la señal SIGINT que representa la secuencia de teclas Control + C. El controlador de señal puede establecer un indicador que se comprobará en el bucle para que se rompa adecuadamente.

Probablemente quieras kbhit();

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

using namespace std;

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

esto puede no funcionar en todos los entornos. Una forma portátil sería crear un hilo de monitoreo y establecer alguna marca en getch();

¡Otra forma de obtener una entrada de teclado sin bloqueo es abrir el archivo del dispositivo y leerlo!

Debe conocer el archivo del dispositivo que está buscando, uno de / dev / input / event *. Puede ejecutar cat / proc / bus / input / devices para encontrar el dispositivo que desea.

Este código funciona para mí (ejecutar como administrador).

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

Las maldiciones de la biblioteca se puede utilizar para este propósito.Por supuesto, select() y manejadores de señal puede ser usado también en cierta medida.

Si estás contento con solo tomar Control-C, es un trato hecho. Si realmente desea E / S sin bloqueo pero no desea la biblioteca de maldiciones, otra alternativa es mover el bloqueo, el stock y el barril a AT & amp; T sfio biblioteca . Es una buena biblioteca diseñada en C stdio pero más flexible, segura para subprocesos y funciona mejor. (sfio significa E / S segura y rápida).

No hay una forma portátil de hacer esto, pero select () podría ser una buena manera. Consulte http://c-faq.com/osdep/readavail.html para obtener más información. posibles soluciones.

Aquí hay una función para hacer esto por usted. Necesita termios.h que viene con los sistemas 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);
}

Desglosando esto: tcgetattr obtiene la información actual del terminal y la almacena en t. Si cmd es 1, el indicador de entrada local en tcsetattr se establece en entrada sin bloqueo. De lo contrario, se restablece. Luego <=> cambia la entrada estándar a <=>.

Si no restablece la entrada estándar al final de su programa, tendrá problemas en su shell.

Puede hacerlo usando select de la siguiente manera:

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

No demasiado trabajo: D

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top