Библиотека сайта rus-linux.net
Цилюрик О.И. Linux-инструменты для Windows-программистов | ||
Назад | Библиотеки API POSIX | Вперед |
Терминал, режим ввода: канонический и некононический
Частным случаем блокирующего-неблокирующего вывода
является ввод с терминала — часто задаваемый вопрос: как
реализовать неблокирующий посимвольный ввод с терминала/консоли
(такой режим часто используется, например, визуальными редакторами)?
Какие вызовы API для этого использовать? Ответ состоит в том, что для
неблокирующего ввода не существует какого-то специального набора
вызовов POSIX, а используется соответствующий набор параметров
терминала, и, в частности, канонический или неканонический режим
ввода. Текущие установленные параметры терминала можно посмотреть
(наиболее интересующие нас параметры в контексте данного
рассмотрения: icanon, echo, min, time
):
$ stty -a < /dev/tty speed 38400 baud; rows 33; columns 93; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts -ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
Примечание: Вся терминальная система (и команда stty
)
реализована в те давние времена, когда в большинстве случаев
подключение терминала производилось через последовательные линии
RS-232, поэтому очень много параметров ориентированы на параметры
такой линии. Но, если возникает необходимость работать с RS-232 (для
связи с устройствами), то оказывается полезной и ещё одна команда,
вот как она возвращает, например, диагностику:
$ sudo setserial -bg /dev/ttyS* /dev/ttyS0 at 0x03f8 (irq = 4) is a 16550A /dev/ttyS1 at 0x02f8 (irq = 3) is a 16550A
Режим обмена (то, что мы видели по команде stty
)
с терминалом описывается (<bits/termios.h>
)
программной структурой:
#define NCCS 32 struct termios { tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ speed_t c_ispeed; /* input speed */ speed_t c_ospeed; /* output speed */ };
Основные функции (<termios.h>
)
работы с режимами терминала:
int tcgetattr( int fd, struct termios *termios );
int tcsetattr( int fd, int optional_actions, const struct termios *termios );
- где optional_actions
указывает как поступать с вводом и выводом, уже
поставленным в очередь. Это может быть (<bits/termios.h>
)
одно из следующих значений:
/* tcsetattr uses these */ #define TCSANOW 0 #define TCSADRAIN 1 #define TCSAFLUSH 2
TCSANOW
- делать изменения немедленно;
TCSADRAIN
- делать изменения после ожидания, пока весь поставленный в очередь
вывод не выведен (обычно используется при изменении параметров,
которые воздействуют на вывод);
TCSAFLUSH
- подобен TCSADRAIN
, но отбрасывает любой поставленный в очередь ввод.
Этой информации достаточно, чтобы рассмотреть
следующий пример (архив terminal.tgz
):
прямое управление курсором экрана терминала (нажатием клавиш 'd',
'u', 'l', 'r' — вверх, вниз, влево, вправо, соответственно, 'q'
— выход из программы):
move.c : #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <termios.h> #include <stdio.h> int main ( int argc, char **argv ) { struct termios savetty, tty; char ch; int x, y; printf( "Enter start position (x y): " ); scanf( "%d %d", &x, &y ); if( !isatty( 0 ) ) { fprintf( stderr, "stdin not terminal\n" ); exit( EXIT_FAILURE ); }; tcgetattr( 0, &tty ); // получили состояние терминала savetty = tty; tty.c_lflag &= ~( ICANON | ECHO | ISIG ); tty.c_cc[ VMIN ] = 1; tcsetattr( 0, TCSAFLUSH, &tty ); // изменили состояние терминала printf( "%c[2J", 27 ); // очистили экран fflush( stdout ); printf( "%c[%d;%dH", 27, y, x ); // установили курсор в позицию fflush( stdout ); for( ; ; ) { read( 0, &ch, 1 ); if( ch == 'q' ) break; switch( ch ) { case 'u': printf( "%c[1A", 27 ); break; case 'd': printf( "%c[1B", 27 ); break; case 'r': printf( "%c[1C", 27 ); break; case 'l': printf( "%c[1D", 27 ); break; }; fflush( stdout ); }; tcsetattr( 0, TCSAFLUSH, &savetty ); // восстановили состояние терминала printf( "\n" ); exit( EXIT_SUCCESS ); }
Примечание: Обязательная часть
подобных программ (не показанная в примере, чтобы его не усложнять)
это перехват сигналов завершения (SIGINT
, SIGTERM
) для
восстановления перед завершением программы канонического режима
ввода; в противном случае терминал будет «испорчен» для
дальнейшего использования в качестве терминала. Для восстановления
режима с успехом может быть использован вызов atexit()
,
как это сделано в примере (в том же архиве):
ncan.c : ... struct termios saved_attributes; void reset_input_mode( void ) { tcsetattr( STDIN_FILENO, TCSANOW, &saved_attributes); } void set_input_mode( void ) { struct termios tattr; ... tcgetattr( STDIN_FILENO, &saved_attributes ); atexit( reset_input_mode ); ... tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr ); } ...
Предыдущий раздел: | Оглавление | Следующий раздел: |
Асинхронный ввод-вывод | Источники информации |