C неблокирующий ввод с клавиатуры

82

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

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

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

Zxaos
источник
как насчет открытия ветки?
Nadav B

Ответы:

65

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

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

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

Просто для полноты, вот код, который я только что набрал (примечание: без проверки ошибок!), Который устанавливает Unix TTY и эмулирует <conio.h>функции DOS kbhit()и getch():

Альнитак
источник
1
Должен ли getch () возвращать, какая клавиша была нажата, или он просто должен потреблять символ?
Salepate
@Salepate должен возвращать символ. Это (void)бит, который получает этот персонаж, а затем отбрасывает его.
Alnitak
1
о, я понимаю, может быть, я делаю это неправильно, но когда я использую getch () в printf ("% c"); он отображает на экране странные символы.
Salepate
Незначительное редактирование, вам нужно #include <unistd.h> для работы read ()
jernkuan
Есть ли простой способ прочитать всю строку (например, с помощью getline) вместо одного символа?
azmeuk
15

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

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

Норман Рэмси
источник
Я тоже думал об использовании проклятий, но потенциальная проблема заключается в том, что вызов initscr () очищает экран, а также, возможно, мешает использовать обычный stdio для вывода на экран.
Alnitak
5
Да, проклятия - это все или ничего.
Норман Рэмси
11

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

ммх
источник
Я думаю, что, вероятно, сделаю это для простоты, но я собираюсь принять другой ответ, поскольку он ближе к тому, что требует заголовок.
Zxaos
6

Ты наверное хочешь kbhit();

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

Джон Клегг
источник
4
определенно не портативный. kbhit () - это функция ввода-вывода консоли DOS / Windows.
Alnitak
1
Это все еще верный ответ для многих применений. OP не указывал переносимость или какую-либо конкретную платформу.
AShelly
4
Альнитак: Я не знал, что это подразумевает платформу - что эквивалентно термину Windows?
Zxaos
3
@Alnitak, почему "неблокирование" как концепция программирования более знакомо в среде Unix?
MestreLion
4
@Alnitak: Я имею в виду, что я был знаком с термином «неблокирующий вызов» задолго до того, как касался любого * nix .. так что, как и Zxaos, я бы никогда не понял, что это подразумевает платформу.
MestreLion
4

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

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

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

JustinB
источник
фантастический пример, но я столкнулся с проблемой, из-за которой Xorg продолжал получать ключевые события, когда устройство событий переставало излучать, когда удерживалось более шести клавиш: \ странно, верно?
ThorSummoner
3

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

PolyThinker
источник
1
curses - это и имя библиотеки, и то, что вы будете бормотать, когда будете ее использовать;) Однако, поскольку я собирался упомянуть это +1 вам.
Уэйн Вернер
2

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

Норман Рэмси
источник
1

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

Nate879
источник
1
этот ответ неполный. без манипуляций с терминалом с помощью tcsetattr () [см. мой ответ] вы ничего не получите на fd 0, пока не нажмете Enter.
Alnitak
0

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

Не особо много работы: D

Стержень
источник
2
Это неполный ответ. Вам необходимо установить терминал в неканонический режим. Также nfdsдолжен быть установлен на 1.
luser droog
0

Вот функция, которая сделает это за вас. Вам нужен тот, termios.hкоторый поставляется с системами POSIX.

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

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

112
источник
В операторе switch отсутствует одна скобка :)
étale-cohomology