Entrada de teclado C sin bloqueo

8 minutos de lectura

avatar de usuario
Zxaos

Estoy tratando de escribir un programa en C (en Linux) que se repite hasta que el usuario presiona una tecla, pero no debería requerir que se presione una tecla para continuar con cada ciclo.

¿Hay una manera simple de hacer esto? Me imagino que podría hacerlo con select() pero eso parece mucho trabajo.

Alternativamente, ¿hay alguna manera de atrapar un controlC presione la tecla para hacer la limpieza antes de que el programa se cierre en lugar de no bloquear io?

  • que tal abrir un hilo?

    – Nathan B.

    26 de mayo de 2020 a las 14:10

  • Recomiendo un subproceso separado que bloquea la entrada del teclado y pasa la entrada al otro subproceso principal cada vez que se presiona una tecla. Aquí hay un ejemplo que escribí en Python. Para C, asegúrese de usar colas de transferencia de mensajes seguras para subprocesos, o bloquee la adquisición de mutex, según sea necesario. La biblioteca de colas de Python ya es segura para subprocesos, pero tal seguridad para las colas no existe naturalmente en C ni en C++.

    – Gabriel grapas

    3 de febrero a las 21:28

  • Ver también: ¿Cómo evitar presionar Enter con getchar() para leer un solo carácter?

    – Gabriel grapas

    3 de febrero a las 22:54


avatar de usuario
Alnitak

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

Tenga en cuenta, sin embargo, que con el último método también necesita configurar el TTY para que esté en modo carácter a la vez en lugar de línea a la vez. Este último es el valor predeterminado: si escribe una línea de texto, no se envía a la entrada estándar del programa en ejecución hasta que presione Intro.

Necesitarías usar el tcsetattr() función para desactivar el modo ICANON, y probablemente también desactivar ECHO. ¡De memoria, también debe volver a configurar el terminal en el modo ICANON cuando finalice el programa!

Solo para completar, aquí hay un código que acabo de eliminar (nota: ¡no hay verificación de errores!) Que configura un TTY de Unix y emula el 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) > 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 */
}

  • ¿Se supone que getch() devuelve qué tecla se presionó, o simplemente se supone que consume el carácter?

    – Salepato

    19 de julio de 2012 a las 11:56

  • @Salepate se supone que debe devolver el personaje. Es el (void) bit que obtiene ese carácter y luego lo tira.

    – Alnitak

    19 de julio de 2012 a las 12:05

  • oh, ya veo, tal vez lo estoy haciendo mal, pero cuando uso getch() en un printf(“%c”); muestra caracteres extraños en la pantalla.

    – Salepato

    19 de julio de 2012 a las 12:07

  • @Max888 no es portátil, pero puede salir del programa usted mismo si el siguiente carácter recuperado es ^c (0x03)

    – Alnitak

    27 de julio de 2021 a las 22:05

  • Gracias por una solución elegante … Tengo un temporizador de interrupción en ejecución que causa kbhit() volver de vez en cuando -1 == EINTR …Esto significa kbhit() devoluciones true cuando debería (posiblemente) volver false … He arreglado esto cambiando el return declaración en kbhit() para return (select(1, &fds, NULL, NULL, &tv) > 0);

    – BlueChip

    21 oct 2021 a las 19:41

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 es mucho trabajo 😀

  • Esta respuesta está incompleta. Debe configurar el terminal en modo no canónico. También nfds debe establecerse en 1.

    – luser droog

    18 de febrero de 2016 a las 22:18

  • La primera lectura está bien, pero las siguientes lecturas muestran dos personajes. El primero supongo que es el enter del anterior y el segundo es la llave misma.

    – Mariano Vedovato

    9 de julio de 2021 a las 12:18

select() es un poco demasiado bajo para mayor comodidad. Te sugiero que uses el ncurses biblioteca para poner el terminal en modo cbreak y modo de retraso, luego llame getch()que volverá ERR si ningún personaje está listo:

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

En ese momento puedes llamar getch sin bloqueo.

  • También pensé en usar curses, pero el problema potencial con eso es que la llamada initscr() borra la pantalla, y también puede interferir con el uso normal de stdio para la salida de pantalla.

    – Alnitak

    16 de enero de 2009 a las 8:09

  • Sí, las maldiciones son bastante todo o nada.

    —Norman Ramsey

    17 de enero de 2009 a las 3:01

avatar de usuario
Justin B

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

avatar de usuario
112

Aquí hay una función para hacer esto por usted. Necesitas 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 t está configurado como entrada sin bloqueo. De lo contrario, se reinicia. Entonces tcsetattr cambia la entrada estándar a t.

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

  • A la declaración de cambio le falta un corchete 🙂

    – étale-cohomología

    10 de diciembre de 2016 a las 3:24

avatar de usuario
mmx

En los sistemas UNIX, puede utilizar sigaction llamada para registrar un manejador de señales para SIGINT señal que representa la secuencia de teclas Control+C. El controlador de la señal puede establecer una bandera que se verificará en el bucle para que se rompa de manera adecuada.

  • A la declaración de cambio le falta un corchete 🙂

    – étale-cohomología

    10 de diciembre de 2016 a las 3:24

avatar de usuario
Mateo Italia

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

  • Definitivamente no es portátil. kbhit() es una función de E/S de consola de DOS/Windows.

    – Alnitak

    15 de enero de 2009 a las 23:38

  • Sigue siendo una respuesta válida para muchos usos. El OP no especificó portabilidad ni ninguna plataforma en particular.

    – A Shelly

    15 de enero de 2009 a las 23:54

  • Alnitak: No sabía que implicaba una plataforma. ¿Cuál es el término equivalente de Windows?

    – Zxaos

    16 de enero de 2009 a las 5:15

  • @Alnitak, ¿por qué “sin bloqueo”, como concepto de programación, sería más familiar en el entorno Unix?

    – Mestre León

    23 de febrero de 2015 a las 5:45

  • @Alnitak: Quiero decir, estaba familiarizado con el término “llamada sin bloqueo” mucho antes de tocar cualquier * nix … así que, al igual que Zxaos, nunca me daría cuenta de que implicaría una plataforma.

    – Mestre León

    23 de febrero de 2015 a las 10:52

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad