Autor: | Wojciech Muła |
---|---|
Dodany: | 2003(?) |
Osoby przyzwyczajone do DOS-owych funkcji getch() lub kbhit() odczuwają ich brak w Linuksie. Odpowiedniki tych funkcji są dostarczane w bibliotekach ncurses i slang, ale nie zawsze można pozwolić sobie na luksus ich używania, wtedy samodzielna obsługa raw-mode jest jedynym rozwiązaniem.
Interfejs do terminali zawarty jest w pliku nagłówkowym termios.h. Tryb pracy terminala jest opisany przez strukturę termios. Pobraniu tychże parametrów terminala służy funkcja tcgetattr(), zapisaniu tcsetattr(). Po szczegóły odsyłam do man termios, oraz info libc — rozdział "Low-Level Terminal Interface".
Terminale mogą pracować w dwóch trybach:
Spośród wielu parametrów terminala interesujące są wyłącznie dwie flagi ICANON oraz ECHO przechowywane w polu c_lflag wspomnianej struktury. Flaga ICANON włącza tryb kanoniczny, natomiast ECHO włącza wyświetlanie wprowadzanych znaków na ekran; poniżej przykład przełączenia w tryb raw.
terminal.c:
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <termios.h> int tty_fileno; /* deskryptor terminala */ typedef struct { int num; /* ilość bajtów (0..8) */ char array[8]; /* odczytane bajty */ } keycode; keycode get_key() { static keycode kc; /* odczytane zostanie co najwyżej 8 bajtów */ if ((kc.num=read(tty_fileno, kc.array, 8)) == -1) kc.num = 0; errno = 0; return kc; } keycode wait_for_key() { fd_set read_set; FD_ZERO(&read_set); FD_SET (tty_fileno, &read_set); /* czekaj na klawisz */ select(tty_fileno+1, &read_set, NULL, NULL, NULL); return get_key(); } extern int errno; void check_errno() { if (errno) { perror(""); exit(EXIT_FAILURE); } } int main() { struct termios term; char eof; /* kod znaku EOF */ keycode k; int i; tty_fileno = open("/dev/tty", O_RDONLY | O_NONBLOCK); check_errno(); /* pobranie parametrów terminala */ tcgetattr(tty_fileno, &term); check_errno(); eof = term.c_cc[VEOF]; /* znak EOF, prawdopodobnie będzie to \004 */ /* wyłączenie trybu kanonicznego oraz echa */ term.c_lflag &= ~(ICANON | ECHO); tcsetattr(tty_fileno, TCSAFLUSH, &term); check_errno(); /* wyświetlane są kody klawiszy, koniec po naciśnięciu CTRL-D */ while (1) { k = wait_for_key(); /* pobierz kod klawisza */ if (k.num == 1 && k.array[0] == eof) { puts("CTRL-D!"); break; } printf("%d: ", k.num); for (i=0; i<k.num; i++) printf("\\%03o ", (unsigned char)k.array[i]); putchar('\n'); } puts("naciśnij dowolny klawisz..."); wait_for_key(); /* przywrócenie zmienionych parametrów */ term.c_lflag |= (ICANON | ECHO); tcsetattr(tty_fileno, TCSAFLUSH, &term); check_errno(); return 0; }
Plik terminala /dev/tty otwierany jest w trybie nieblokującym, dzięki czemu można było łatwo zaimplementować sekwencję:
if (kbhit()) return getch(); else return NO_KEY;
przy pomocy pojedynczego wywołania funkcji read; w przeciwnym razie należałoby użyć funkcji select(). Jeśli Czytelnik programował w DOS-ie pamięta zapewne sprawdzanie czy naciśnięty został 2-bajtowy klawisz rozszerzony, czy zwyczajny 1-bajtowy, w Linuksie jest jeszcze gorzej, bowiem klawiszom przypisywane są kody nawet 5-bajtowe. Dlatego też, by zaoszczędzić powtarzających się sekwencji kodu, funkcje get_key() oraz wait_for_key() zwracają od razu wszystkie bajty wygenerowane przez klawisz w strukturze keycode. Niestety sama klasyfikacja poszczególnych kodów musi zostać wykonana samodzielnie, gdyż w zależności od terminala te same klawisze mogą wysyłać różne kody. Proponuję zapoznać się z man termcap.