C неблокирующий ввод с клавиатуры - PullRequest
74 голосов
/ 16 января 2009

Я пытаюсь написать программу на C (в Linux), которая зацикливается до тех пор, пока пользователь не нажимает клавишу, но не требует нажатия клавиши для продолжения каждого цикла.

Есть ли простой способ сделать это? Я полагаю, что мог бы сделать это с select(), но это похоже на большую работу.

В качестве альтернативы, есть ли способ перехватить нажатие клавиши ctrl - c для очистки перед закрытием программы вместо неблокирующего ввода-вывода?

Ответы [ 10 ]

60 голосов
/ 16 января 2009

Как уже говорилось, вы можете использовать sigaction для перехвата Ctrl-C или select для перехвата любого стандартного ввода.

Обратите внимание, что с последним методом вам также нужно установить TTY так, чтобы он был в символьном режиме, а не в линейном режиме. Последний вариант используется по умолчанию - если вы введете строку текста, она не будет отправлена ​​на стандартный экран запущенной программы, пока вы не нажмете клавишу ввода.

Вам необходимо использовать функцию tcsetattr(), чтобы отключить режим ICANON и, возможно, также отключить ECHO. Из памяти вы также должны вернуть терминал в режим ICANON, когда программа выйдет!

Просто для полноты, вот код, который я только что написал (nb: без проверки ошибок!), Который устанавливает TIX Unix и эмулирует функции DOS <conio.h> kbhit() и 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 */
}
15 голосов
/ 16 января 2009

select() слишком низкий уровень для удобства. Я предлагаю вам использовать библиотеку ncurses, чтобы перевести терминал в режим cbreak и delay, затем вызовите getch(), который вернет ERR, если ни один символ не готов:

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

В этот момент вы можете звонить getch без блокировки.

11 голосов
/ 16 января 2009

В системах UNIX вы можете использовать вызов sigaction, чтобы зарегистрировать обработчик сигнала для сигнала SIGINT, который представляет последовательность клавиш Control + C. Обработчик сигнала может установить флаг, который будет проверяться в цикле, чтобы он соответствующим образом прерывался.

6 голосов
/ 16 января 2009

Вы, вероятно, хотите kbhit();

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

using namespace std;

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

это может работать не во всех средах. Переносимым способом было бы создать поток мониторинга и установить некоторый флаг на getch();

3 голосов
/ 23 апреля 2014

Еще один способ получить неблокирующий ввод с клавиатуры - открыть файл устройства и прочитать его!

Вы должны знать файл устройства, который вы ищете, один из / dev / input / event *. Вы можете запустить cat / proc / bus / input / devices, чтобы найти нужное устройство.

Этот код работает для меня (запуск от имени администратора).

  #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;
   }
3 голосов
/ 16 января 2009

Для этого можно использовать библиотеку curses. Конечно, select() и обработчики сигналов могут использоваться в определенной степени.

2 голосов
/ 17 января 2009

Если вы счастливы, просто поймав Control-C, это уже сделка. Если вам действительно нужен неблокирующий ввод / вывод, но вам не нужна библиотека curses, другой альтернативой является перемещение блокировки, запаса и ствола в библиотеку AT & T sfio . Это хорошая библиотека на основе C stdio, но более гибкая, поточно-ориентированная и работающая лучше. (sfio означает безопасный, быстрый ввод / вывод.)

1 голос
/ 16 января 2009

Переносного способа сделать это не существует, но select () может быть хорошим способом. См. http://c -faq.com / osdep / readavail.html для получения дополнительных возможных решений.

0 голосов
/ 17 июня 2016

Вот функция, которая сделает это за вас. Вам нужен termios.h, который поставляется с системами 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);
}

Разбивка: tcgetattr получает информацию о текущем терминале и сохраняет ее в t. Если cmd равно 1, флаг локального ввода в t устанавливается на неблокирующий вход. В противном случае он сбрасывается. Затем tcsetattr меняет стандартный ввод на t.

Если вы не сбросите стандартный ввод в конце вашей программы, у вас будут проблемы в вашей оболочке.

0 голосов
/ 04 февраля 2016

Вы можете сделать это, используя команду select следующим образом:

  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;
      }
     }
  }

Не слишком много работы: D

...