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?

È stato utile?

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top