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 control–C presione la tecla para hacer la limpieza antes de que el programa se cierre en lugar de no bloquear io?
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 significakbhit()
devolucionestrue
cuando debería (posiblemente) volverfalse
… He arreglado esto cambiando elreturn
declaración enkbhit()
parareturn (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
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;
}
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
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
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
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