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