C sem bloqueio de entrada de teclado

votos
68

Estou tentando escrever um programa em C (no Linux) que circula até que o usuário pressiona uma tecla, mas não deve exigir uma tecla para continuar cada loop.

Existe uma maneira simples de fazer isso? Eu acho que eu poderia fazê-lo com select()mas que parece ser um monte de trabalho.

Alternativamente, existe um modo para apanhar uma ctrl- cpressão de tecla para fazer a limpeza antes de fechar o programa, em vez de não-bloqueio io?

Publicado 16/01/2009 em 00:28
fonte usuário
Em outras línguas...                            


10 respostas

votos
51

Como já foi dito, você pode usar sigactionpara interceptar ctrl-c, ou selectpara prender qualquer entrada padrão.

Note, porém, que, com o último método, você também precisa definir o TTY de modo que é na personagem-a-um-tempo, em vez de modo de linha-em-um-tempo. O último é o padrão - se você digitar uma linha de texto não são enviados para stdin do programa em execução até que você pressione enter.

Você precisa usar a tcsetattr()função para desativar o modo de ICANON, e provavelmente também desativar ECHO também. De memória, você também tem que definir o terminal de volta no modo ICANON quando o programa sai!

Apenas para completar, aqui está um código que acabei grávida (NB: não há verificação de erros!) Que cria um Unix TTY e emular as DOS <conio.h>funções 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 */
}
Respondeu 16/01/2009 em 00:37
fonte usuário

votos
15

select()é um pouco demasiado baixo nível de conveniência. Eu sugiro que você use a ncursesbiblioteca para colocar o terminal em modo cbreak e modo de atraso, em seguida, chamar getch(), que irá retornar ERRse nenhum personagem está pronto:

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

Nesse ponto, você pode chamar getchsem bloqueio.

Respondeu 16/01/2009 em 02:03
fonte usuário

votos
11

Em sistemas UNIX, você pode usar sigactionchamar para registrar um manipulador de sinal para SIGINTsinal que representa a sequência de teclas Control + C. O processador de sinal pode definir uma bandeira que irá ser verificado no circuito fazendo com que quebre apropriadamente.

Respondeu 16/01/2009 em 00:31
fonte usuário

votos
6

Você provavelmente quer kbhit();

//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

este pode não funcionar em todos os ambientes. A maneira portátil seria a criação de um fio de monitoramento e definir alguns bandeira nogetch();

Respondeu 16/01/2009 em 00:37
fonte usuário

votos
3

Outra maneira de obter a entrada de teclado non-blocking é abrir o arquivo de dispositivo e lê-lo!

Você tem que saber o arquivo de dispositivo que você está procurando, um dos / dev / input / evento *. Você pode executar cat / proc / bus / input / devices para encontrar o dispositivo que deseja.

Esse código funciona para mim (executar 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;
   }
Respondeu 23/04/2014 em 20:55
fonte usuário

votos
3

A biblioteca maldições podem ser usados para este fim. Claro, select()e os manipuladores de sinal pode ser usado também, até certo ponto.

Respondeu 16/01/2009 em 00:36
fonte usuário

votos
2

Se você está feliz apenas recuperar o Control-C, é um negócio feito. Se você realmente quer non-blocking I / O, mas você não quer que a biblioteca maldições, outra alternativa é mover fechadura, estoque e barril para a AT & T sfiobiblioteca . É bom biblioteca padronizada em C stdio, mas mais flexível, thread-safe, e tem melhor desempenho. (SFIO significa seguro, rápido I / O.)

Respondeu 17/01/2009 em 04:08
fonte usuário

votos
1

Não há nenhuma maneira portátil para fazer isso, mas seleccione () pode ser uma boa maneira. Veja http://c-faq.com/osdep/readavail.html para mais soluções possíveis.

Respondeu 16/01/2009 em 00:33
fonte usuário

votos
0

Aqui está uma função para fazer isso por você. Você precisa termios.hque vem com 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);
}

Quebrar este down: tcgetattrobtém as informações terminal atual e as armazena em t. Se cmdfor 1, a bandeira de entrada no local, té definido como a entrada de não-bloqueio. Caso contrário, é redefinido. Em seguida, tcsetattrmuda a entrada padrão para t.

Se você não redefinir a entrada padrão no final do seu programa você vai ter problemas em sua concha.

Respondeu 17/06/2016 em 02:12
fonte usuário

votos
0

Você pode fazer isso usando select como 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;
      }
     }
  }

Não muito trabalho: D

Respondeu 04/02/2016 em 08:10
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more