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 presionar una tecla para continuar cada ciclo.
¿Existe una forma sencilla de hacer esto? Me imagino que posiblemente podría hacerlo con select()
pero eso parece mucho trabajo.
Por otra parte, hay una manera de coger un ctrl- cpulsación de tecla para hacer la limpieza antes de que el programa se cierra en lugar de io sin bloqueo?
c
linux
asynchronous
input
nonblocking
Zxaos
fuente
fuente
Respuestas:
Como ya se dijo, puede usar
sigaction
para capturar ctrl-c, oselect
para capturar cualquier entrada estándar.Sin embargo, tenga en cuenta que con el último método también debe configurar el TTY para que esté en el modo carácter a la vez en lugar de línea a la vez. Este último es el predeterminado: si escribe una línea de texto, no se envía al stdin del programa en ejecución hasta que presione Intro.
tcsetattr()
Debería usar la función para desactivar el modo ICANON y probablemente también desactivar ECHO. Desde la memoria, también debe volver a configurar el terminal en modo ICANON cuando el programa sale.Solo para completar, aquí hay un código que acabo de crear (nb: ¡sin verificación de errores!) Que configura un TTY Unix y emula las
<conio.h>
funciones de DOSkbhit()
ygetch()
:#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 */ }
fuente
(void)
que consigue ese carácter y luego lo tira.select()
es un nivel demasiado bajo para su conveniencia. Le sugiero que use lancurses
biblioteca para poner el terminal en modo cbreak y modo de retraso, luego llamegetch()
, que regresaráERR
si no hay ningún personaje listo:En ese momento puede llamar
getch
sin bloquear.fuente
En los sistemas UNIX, puede utilizar la
sigaction
llamada para registrar un gestor deSIGINT
señales para la señal que representa la secuencia de teclas Control + C. El manejador de señales puede establecer una bandera que se verificará en el bucle para que se rompa adecuadamente.fuente
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 bandera en
getch();
fuente
Otra forma de obtener entrada de teclado sin bloqueo es abrir el archivo del dispositivo y leerlo.
Debe conocer el archivo de 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; }
fuente
La biblioteca de curses se puede utilizar para este propósito. Por supuesto,
select()
y los manejadores de señales también se pueden usar hasta cierto punto.fuente
Si estás contento con solo ver Control-C, es un trato hecho. Si realmente desea E / S sin bloqueo pero no desea la biblioteca de curses, otra alternativa es mover el candado, el stock y el barril a la biblioteca de AT&T
sfio
. Es una bonita biblioteca con un patrón en Cstdio
pero más flexible, segura para subprocesos y funciona mejor. (sfio significa E / S rápida y segura).fuente
No hay una forma portátil de hacer esto, pero select () podría ser una buena forma. Consulte http://c-faq.com/osdep/readavail.html para obtener más soluciones posibles.
fuente
Puede hacerlo usando seleccionar como sigue:
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 mucho trabajo: D
fuente
nfds
debe establecerse en 1.Aquí hay una función para hacer esto por usted. Necesita lo
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 de la terminal y la almacenat
. Sicmd
es 1, el indicador de entrada localt
se establece en entrada sin bloqueo. De lo contrario, se reinicia. Luegotcsetattr
cambia la entrada estándar at
.Si no restablece la entrada estándar al final de su programa, tendrá problemas en su shell.
fuente