C nieblokujące wejście klawiatury

82

Próbuję napisać program w C (w systemie Linux), który zapętla się, dopóki użytkownik nie naciśnie klawisza, ale nie powinien wymagać naciśnięcia klawisza, aby kontynuować każdą pętlę.

Czy jest na to prosty sposób? Myślę, że prawdopodobnie mógłbym to zrobić, select()ale wydaje mi się, że to dużo pracy.

Alternatywnie, czy istnieje sposób, aby złapać naciśnięcie klawisza ctrl- w ccelu wyczyszczenia przed zamknięciem programu zamiast nieblokującego operacji io?

Zxaos
źródło
co z otwarciem wątku?
Nadav B

Odpowiedzi:

65

Jak już wspomniano, możesz użyć sigactiondo zalewkowania ctrl-c lub selectdo zalewkowania dowolnego standardowego wejścia.

Zwróć jednak uwagę, że w przypadku tej drugiej metody musisz również ustawić TTY tak, aby był w trybie znak po czasie, a nie wiersz po czasie. To drugie jest domyślne - jeśli wpiszesz wiersz tekstu, nie zostanie on wysłany na stdin uruchomionego programu, dopóki nie naciśniesz klawisza Enter.

Będziesz musiał użyć tej tcsetattr()funkcji, aby wyłączyć tryb ICANON i prawdopodobnie również wyłączyć ECHO. Z pamięci musisz także ustawić terminal z powrotem w tryb ICANON, gdy program zakończy pracę!

Dla kompletności, oto kod, który właśnie podałem (uwaga: bez sprawdzania błędów!), Który konfiguruje Unixowy TTY i emuluje <conio.h>funkcje DOS kbhit()i getch():

Alnitak
źródło
1
Czy getch () ma zwrócić, który klawisz został naciśnięty, czy po prostu ma zużywać znak?
Salepate
@Salepate ma zwrócić znak. To (void)kawałek, który dostaje tę postać, a następnie ją wyrzuca.
Alnitak
1
och, rozumiem, może robię to źle, ale kiedy używam getch () na printf ("% c"); wyświetla na ekranie dziwne znaki.
Salepate
Niewielka edycja, musisz #include <unistd.h>, aby read () działało
jernkuan
Czy istnieje prosty sposób na odczytanie całej linii (na przykład za pomocą „getline”) zamiast pojedynczego znaku?
azmeuk
15

select()jest trochę za niski dla wygody. Proponuję użyć ncursesbiblioteki do wprowadzenia terminala w tryb cbreak i tryb opóźnienia, a następnie wywołanie getch(), które powróci, ERRjeśli żadna postać nie jest gotowa:

W tym momencie możesz dzwonić getchbez blokowania.

Norman Ramsey
źródło
Myślałem też o użyciu curses, ale potencjalny problem z tym polega na tym, że wywołanie initscr () czyści ekran, a także może przeszkadzać w używaniu normalnego stdio do wyświetlania ekranu.
Alnitak
5
Tak, przekleństwa to prawie wszystko albo nic.
Norman Ramsey
11

W systemach UNIX można użyć sigactionwywołania do zarejestrowania programu obsługi SIGINTsygnału dla sygnału, który reprezentuje sekwencję klawiszy Control + C. Program obsługi sygnału może ustawić flagę, która będzie sprawdzana w pętli, powodując jej odpowiednie przerwanie.

mmx
źródło
Myślę, że prawdopodobnie zrobię to dla ułatwienia, ale zaakceptuję drugą odpowiedź, ponieważ jest bliższa temu, o co pyta tytuł.
Zxaos
6

Prawdopodobnie chcesz kbhit();

może to nie działać we wszystkich środowiskach. Przenośnym sposobem byłoby utworzenie wątku monitorującego i włączenie pewnej flagigetch();

Jon Clegg
źródło
4
zdecydowanie nie przenośny. kbhit () to funkcja IO konsoli DOS / Windows.
Alnitak
1
Nadal jest to ważna odpowiedź na wiele zastosowań. W OP nie określono przenośności ani żadnej konkretnej platformy.
AShelly
4
Alnitak: Nie wiedziałem, że to sugeruje platformę - jaki jest odpowiednik terminu Windows?
Zxaos
3
@Alnitak Dlaczego pojęcie „nieblokowania” jako koncepcja programowania byłoby bardziej znane w środowisku Unix?
MestreLion
4
@Alnitak: Chodzi mi o to, że termin „połączenie nieblokujące” znałem na długo przed dotknięciem któregokolwiek * nix… więc tak jak Zxaos nigdy nie zdawałbym sobie sprawy, że oznaczałoby to platformę.
MestreLion
4

Innym sposobem uzyskania nieblokującego wprowadzania danych z klawiatury jest otwarcie pliku urządzenia i przeczytanie go!

Musisz znać plik urządzenia, którego szukasz, jeden z / dev / input / event *. Możesz uruchomić cat / proc / bus / input / devices, aby znaleźć żądane urządzenie.

Ten kod działa dla mnie (uruchom jako administrator).

JustinB
źródło
fantastyczny przykład, ale napotkałem problem polegający na tym, że Xorg ciągle otrzymywał kluczowe zdarzenia, gdy urządzenie zdarzenia przestało emitować, gdy trzymano więcej niż sześć kluczy: \ dziwne, prawda?
ThorSummoner
3

W tym celu można wykorzystać bibliotekę curses. Oczywiście, select()do pewnego stopnia można również użyć programów obsługi sygnałów.

PolyThinker
źródło
1
curses to zarówno nazwa biblioteki, jak i to, co będziesz mruczał, kiedy jej używasz;) Jednak skoro miałem o tym wspomnieć, +1.
Wayne Werner
2

Jeśli cieszysz się po prostu łapaniem Control-C, sprawa jest skończona. Jeśli naprawdę chcesz nieblokujące wejścia / wyjścia, ale nie chcesz biblioteki curses, inną alternatywą jest przeniesienie blokady, zapasu i lufy do biblioteki AT&Tsfio . Jest to ładna biblioteka wzorowana na C, stdioale bardziej elastyczna, bezpieczna dla wątków i działa lepiej. (sfio oznacza bezpieczne, szybkie I / O).

Norman Ramsey
źródło
1

Nie ma przenośnego sposobu, aby to zrobić, ale select () może być dobrym sposobem. Więcej możliwych rozwiązań można znaleźć pod adresem http://c-faq.com/osdep/readavail.html .

Nate879
źródło
1
ta odpowiedź jest niekompletna. bez manipulacji terminalem za pomocą tcsetattr () [zobacz moją odpowiedź] nic nie dostaniesz na fd 0, dopóki nie naciśniesz enter.
Alnitak
0

Możesz to zrobić za pomocą polecenia select w następujący sposób:

Nie za dużo pracy: D.

Pręt
źródło
2
Ta odpowiedź jest niepełna. Musisz ustawić terminal w trybie niekanonicznym. Również nfdspowinien być ustawiony na 1.
luser Droog
0

Oto funkcja, która zrobi to za Ciebie. Potrzebujesz, termios.hktóry jest dostarczany z systemami POSIX.

Breaking this down: tcgetattrpobiera aktualne informacje o terminalu i zapisuje je w pliku t. Jeśli cmdwynosi 1, flaga wejścia lokalnego w tustawiana jest na wejście nieblokujące. W przeciwnym razie jest resetowany. Następnie tcsetattrzmienia standardowe wejście na t.

Jeśli nie zresetujesz standardowego wejścia pod koniec programu, będziesz miał problemy w powłoce.

112
źródło
W instrukcji przełącznika brakuje jednego nawiasu :)
étale-cohomology