C input da tastiera non bloccante
-
19-08-2019 - |
Domanda
Sto cercando di scrivere un programma in C (su Linux) che viene eseguito fino a quando l'utente preme un tasto, ma non dovrebbe richiedere un tasto per continuare ogni ciclo.
C'è un modo semplice per farlo? Immagino che potrei farlo con select()
ma sembra un sacco di lavoro.
In alternativa, c'è un modo per catturare un tasto ctrl - c per fare pulizia prima che il programma si chiuda invece di io non bloccante?
Soluzione
Come già detto, puoi utilizzare sigaction
per intercettare ctrl-c o select
per intercettare qualsiasi input standard.
Nota comunque che con quest'ultimo metodo devi anche impostare il TTY in modo che sia in modalità carattere alla volta piuttosto che in linea alla volta. Quest'ultimo è il valore predefinito: se si digita una riga di testo, questo non viene inviato allo stdin del programma in esecuzione fino a quando non si preme Invio.
Dovresti utilizzare la funzione tcsetattr()
per disattivare la modalità ICANON e probabilmente disabilitare anche ECHO. Dalla memoria, devi anche riportare il terminale in modalità ICANON quando il programma esce!
Solo per completezza, ecco un po 'di codice che ho appena rovinato (nb: nessun controllo degli errori!) che imposta un Unix TTY ed emula le funzioni DOS <conio.h>
kbhit()
e 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 */
}
Altri suggerimenti
select()
è un po 'troppo basso per comodità. Ti suggerisco di utilizzare la ncurses
libreria per mettere il terminale in modalità cbreak e ritardo, quindi chiamare getch()
, che restituirà ERR
se nessun personaggio è pronto:
WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
A quel punto puoi chiamare getch
senza bloccare.
Sui sistemi UNIX, è possibile utilizzare la chiamata sigaction
per registrare un gestore di segnale per il segnale SIGINT
che rappresenta la sequenza di tasti Control + C. L'handler del segnale può impostare un flag che verrà controllato nel loop facendolo rompere in modo appropriato.
Probabilmente vuoi kbhit();
//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>
using namespace std;
int main()
{
while(1)
{
if(kbhit())
{
break;
}
}
}
questo potrebbe non funzionare su tutti gli ambienti. Un modo portatile sarebbe quello di creare un thread di monitoraggio e impostare un flag su getch();
Un altro modo per ottenere l'input da tastiera non bloccante è aprire il file del dispositivo e leggerlo!
Devi conoscere il file del dispositivo che stai cercando, uno di / dev / input / event *. Puoi eseguire cat / proc / bus / input / devices per trovare il dispositivo che desideri.
Questo codice funziona per me (eseguito come amministratore).
#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 libreria curses può essere usata per questo scopo. Naturalmente, select()
e i gestori di segnali possono essere utilizzati anche in una certa misura.
Se sei contento di prendere Control-C, è un affare fatto. Se vuoi davvero l'I / O non bloccante ma non vuoi la libreria curses, un'altra alternativa è spostare lock, stock e barrel in AT & amp; T sfio
libreria . È una libreria piacevole modellata su C stdio
ma più flessibile, thread-safe e offre prestazioni migliori. (sfio sta per I / O sicuro e veloce.)
Non esiste un modo portatile per farlo, ma select () potrebbe essere un buon modo. Vedi http://c-faq.com/osdep/readavail.html per ulteriori informazioni possibili soluzioni.
Ecco una funzione per farlo per te. È necessario termios.h
fornito con i sistemi 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);
}
Suddivisione: tcgetattr
ottiene le informazioni correnti sul terminale e le memorizza in t
. Se cmd
è 1, il flag di input locale in tcsetattr
è impostato su input non bloccante. Altrimenti viene ripristinato. Quindi <=> cambia l'input standard in <=>.
Se non ripristini l'input standard alla fine del tuo programma, avrai problemi nella tua shell.
Puoi farlo usando select come segue:
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;
}
}
}
Non troppo lavoro: D