| 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.