C неблокирующий ввод клавиатуры
Я пытаюсь написать программу на C (в Linux), которая выполняет циклы, пока пользователь не нажмет клавишу, но не должна требовать нажатия клавиши для продолжения каждого цикла.
есть ли простой способ сделать это? Я думаю, что я мог бы сделать это с select()
но это кажется много работы.
альтернативно, есть ли способ поймать ctrl-c нажатие клавиши для очистки до закрытия программы вместо неблокирующего ввода-вывода?
10 ответов
как уже говорилось, Вы можете использовать sigaction
для ловушки ctrl-c или select
для ловушки любого стандартного ввода.
обратите внимание, однако, что с последним методом вам также нужно установить TTY так, чтобы он был в символьном режиме, а не в режиме строки. Последнее значение по умолчанию-если вы вводите строку текста, она не отправляется в stdin запущенной программы, Пока вы не нажмете enter.
вам нужно использовать отключить режиме ICANON, и, вероятно, также отключить ECHO тоже. Из памяти, вы также должны установить терминал обратно в режим ICANON, когда программа выходит!
просто для полноты, вот код, который я только что постучал (nb: нет проверки ошибок!) который устанавливает Unix TTY и эмулирует 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 */
}
select()
немного слишком низкоуровневый для удобства. Я предлагаю вам использовать ncurses
библиотека, чтобы перевести терминал в режим cbreak и режим задержки, затем вызовите getch()
, что вернет ERR
если ни один символ не готов:
WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
в этот момент Вы можете позвонить getch
без блокировки.
в системах UNIX вы можете использовать sigaction
вызов для регистрации обработчика сигнала для SIGINT
сигнал, представляющий последовательность клавиш Control+C. Обработчик сигнала может установить флаг, который будет проверяться в цикле, что делает его соответствующим образом сломать.
вы, вероятно, хотите 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();
другой способ получить неблокирующий ввод с клавиатуры-открыть файл устройства и прочитать его!
вы должны знать файл устройства, который вы ищете, один из /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;
}
для этой цели можно использовать библиотеку curses. Конечно,select()
и обработчики сигналов также могут использоваться в определенной степени.
Если вы счастливы просто поймать Control-C,это сделано. Если вы действительно хотите неблокирующий ввод-вывод, но вы не хотите библиотеку проклятий, другой альтернативой является перемещение блокировки, запаса и ствола в AT & T sfio
библиотека. Это хорошая библиотека с рисунком на C stdio
но более гибкий, потокобезопасный и работает лучше. (sfio означает безопасный, быстрый ввод-вывод)
нет портативного способа сделать это, но select() может быть хорошим способом. См.http://c-faq.com/osdep/readavail.html для более возможных решений.
вот функция, чтобы сделать это для вас. Вам нужно 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
.
если вы не сбросите стандартный ввод в конце вашей программы, у вас будут проблемы в вашей оболочке.
вы можете сделать это, используя select as follow:
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