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?
c
linux
asynchronous
input
nonblocking
Zxaos
źródło
źródło
Odpowiedzi:
Jak już wspomniano, możesz użyć
sigaction
do zalewkowania ctrl-c lubselect
do 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 DOSkbhit()
igetch()
:#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 */ }
źródło
(void)
kawałek, który dostaje tę postać, a następnie ją wyrzuca.select()
jest trochę za niski dla wygody. Proponuję użyćncurses
biblioteki do wprowadzenia terminala w tryb cbreak i tryb opóźnienia, a następnie wywołaniegetch()
, które powróci,ERR
jeśli żadna postać nie jest gotowa:W tym momencie możesz dzwonić
getch
bez blokowania.źródło
W systemach UNIX można użyć
sigaction
wywołania do zarejestrowania programu obsługiSIGINT
sygnał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.źródło
Prawdopodobnie chcesz
kbhit();
//Example will loop until a key is pressed #include <conio.h> #include <iostream> using namespace std; int main() { while(1) { if(kbhit()) { break; } } }
może to nie działać we wszystkich środowiskach. Przenośnym sposobem byłoby utworzenie wątku monitorującego i włączenie pewnej flagi
getch();
źródło
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).
#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; }
źródło
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.źródło
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&T
sfio
. Jest to ładna biblioteka wzorowana na C,stdio
ale bardziej elastyczna, bezpieczna dla wątków i działa lepiej. (sfio oznacza bezpieczne, szybkie I / O).źródło
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 .
źródło
Możesz to zrobić za pomocą polecenia select w następujący sposób:
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; } } }
Nie za dużo pracy: D.
źródło
nfds
powinien być ustawiony na 1.Oto funkcja, która zrobi to za Ciebie. Potrzebujesz,
termios.h
który jest dostarczany z systemami 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); }
Breaking this down:
tcgetattr
pobiera aktualne informacje o terminalu i zapisuje je w plikut
. Jeślicmd
wynosi 1, flaga wejścia lokalnego wt
ustawiana jest na wejście nieblokujące. W przeciwnym razie jest resetowany. Następnietcsetattr
zmienia standardowe wejście nat
.Jeśli nie zresetujesz standardowego wejścia pod koniec programu, będziesz miał problemy w powłoce.
źródło