Дата и время публикации:
Теория и практика
1. Теория
В данном режиме терминал производит вывод на экран (stdout) вводимых пользователем символов c устройства ввода (stdin), как показано на рисунке 1.1
Рисунок 1.1
На рисунке 1.1 показан весь процесса ввода/вывода символов в терминале пользователя, в котором участвуют читающий и пишущий процессы на пользовательском уровне, драйвер tty на уровне ядра. Последний обеспечивает буферизацию данных в двух очередях: вывода (output queue) и ввода (input queue). Обе очереди имеют максимальный размер, поэтому по мере заполнения очереди вывода пишущий процесс будет ждать окончания передачи символов на устройство вывода, в то время как считающий процесс будет их терять при достижении максимального размера очереди ввода.
При включенном режиме ECHO обеспечивается возможность непосредственной передачи символов из очереди ввода в очередь вывода драйвера tty без участия пишущего процесса на пользовательском уровне.
Поэтому, в начале работы программы, следует сбрасывать флаг ECHO для того, чтобы предотвратить выдачу символов на экран самой операционной системой.
Потому чтобы
- выше описанный режим, реализуемой в ncurses, отличается от той реализации, что предлагает "ось зла", а именно в том, что любой видимый символ сохраняется в текущем окне в указанной через процедуру ввода с обеспечением перемещения и переноса курсора (cursor movement and wrapping) выбранной текущей или иной заданной позиции;
- прикладное приложение может само реализовать данный режим в контролируемой им части экрана;
- при работе с видами терминалов, работающих в асинхронный режиме, возможна ситуация, когда символ будет появляться всякий раз во время позиционирования курсора.
2. Практика
2.1 Управления режимом драйверa tty
Для включения или, как показано в листингe 2.1.2, отключения следует управляющей структуре tty-драйвера termios(3), передать соответствующий флаг.
Определение структуры данных приведено в листинге 2.1.1
Листинг 2.1.1
000 001 struct termios{ 002 tcflag_t c_iflag /* Флаги управления очередью ввода */ 003 tcflag_t c_oflag, /* Флаги управления очередью вывода */ 004 tcflag_t c_cflag, /* Флаги управления режимами на выводе */ 005 tcflag_t c_lflag, /* Флаги режимов локальной обработки на вводе */ 006 cc_t c_cc[NCCS] /* Специальные символы */ 007 }; 008
Режим ECHO относится к локально-обрабатываем на вводе. За его установку и снятие отвечает одноименный флаг ECHO, как показано в листинге 2.1.2
Листинг 2.1.2
000 struct termios tp; 001 002 if (tcgetattr(STDIN_FILENO, &tp) == -1) 003 perror("tcgetattr"); 004 005 tp.c_lflag &= ~ECHO; 006 007 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) 008 perror("tcsetattr"); 009
В листинге 2.1.3 показан пример реализации включения и выключения режима на контролируемой части экрана в ncurses.
Листинг 2.1.3
... 27 #include "ucurses.h" 28 #include "ukeys.h" ... 106 if ( KEYS_CMP(KEYMSG_ECHO) == KEYS_COMPARED ) 107 { 108 if( UCURSES_FLAGS_CTRL(ucursesp,WITH_ECHO,UCURSES_ENABLE) == OK ) 109 { 110 wprint_eol ( w, 0, "echo enable." ); 111 } 112 goto Case_to_print_CLI; 113 } 114 if ( KEYS_CMP(KEYMSG_NOECHO) == KEYS_COMPARED ) 115 { 116 if( UCURSES_FLAGS_CTRL(ucursesp,WITH_ECHO,UCURSES_DISABLE) == OK ) 117 { 118 wprint_eol ( w, 0, "echo disable." ); 119 } 120 goto Case_to_print_CLI; 121 } ...
Оператор-выражения UCURSES_FLAGS_CTRL() производит включение режима и принимает флаг WITH_ECHO и состояние UCURSES_ENABLE в строке 108 листинга 2.1.3), а для его отключения – тотже флаг, но состояние UCURSES_DISABLE в строке 40-44 того же листинга. Указанная процедура объявлена в заголовочном файле <ucurses.h> в строке листинга 2.1.3 в виде оператора-выражения. В листинге 2.1.4 приводится фрагмент кода данного заголовочного файла <ucurses.h>.
Листинг 2.1.4
... 61 #define UCURSES_ENABLE 1 62 #define UCURSES_DISABLE 0 ... 64 #define UCURSES_FLAGS_CTRL(wbase,flags,on)({ \ 65 int __ret = ERR, \ 66 __on = (on); \ 67 ucurses_wbase_t* __wbase = (wbase); \ 68 if( (__wbase) ) \ 69 { \ 70 int __flags = (flags); \ 71 if( ((__flags) & WITH_ECHO) ) \ 72 { \ 73 (__wbase)->ucurses_flags = ((__on) == UCURSES_ENABLE ? \ 74 UCURSES_ECHO_ENBL((__flags)) : \ 75 UCURSES_ECHO_DSBL((__flags)) ); \ 76 __ret = OK; \ 77 } \ 78 } \ 79 __ret; \ 80 }) ...
Как показано в листинге 2.1.4, при передаче флага WITH_ECHO, в строках 73-75 осуществляется вызов операторы-выражения UCURSES_ECHO_ENBL() и UCURSES_ECHO_DSBL(), которые вызываются в зависимости от выбранного состояния UCURSES_ENABLE или UCURSES_DISABLE, передаваемого в третьем аргументе on оператора-выражения UCURSES_FLAGS_CTRL().
Cоответственно, управленнием режимом непосредственно происходит в операторах-выражениях UCURSES_ECHO_ENBL() и UCURSES_ECHO_DSBL(), которые определены в заголовочном файле <ucurses-modes.h>, фрагмент исходного кода показан в листинге 2.1.5.
Листинг 2.1.5
... 30 #define WITH_ECHO 0x01 ... 34 #define UCURSES_FLAG_ENBL(f,flags)({ \ 35 int __f = (f); \ 36 int __flags = flags; \ 37 if ( !((__flags) & (__f)) ) \ 38 { \ 39 flags |= (__f); \ 40 } \ 41 __flags; }) 42 43 #define UCURSES_FLAG_DSBL(f,flags)({ \ 44 int __f = (f); \ 45 int __flags = flags; \ 46 if ( ((__flags) & (__f)) ) \ 47 { \ 48 flags &= ~(__f); \ 49 } \ 50 __flags; }) 51 52 #define UCURSES_ECHO_ENBL(flags)({ \ 53 int __ret = 0; \ 54 int __flags = UCURSES_FLAG_ENBL(WITH_ECHO,flags); \ 55 echo(); \ 56 __flags; }) 57 58 #define UCURSES_ECHO_DSBL(flags)({ \ 59 int __flags = UCURSES_FLAG_DSBL(WITH_ECHO,flags); \ 60 noecho(); \ 61 __flags; }) ...
В строке 55, оператор-выражение UCURSES_ECHO_ENBL() вызывает библиотечную процедуру echo() библиотеки ncurses, которая включает режим ECHO в свою очередь.
В строке 60, оператор-выражение UCURSES_ECHO_DSBL() вызывает библиотечную процедуру noecho() библиотеки ncurses, которая уже выключает режим ECHO .
На рисунке 2.1 приведены результаты выполнения вышерассмотренной цепочки, которую запускает оператор-выражение UCURSES_FLAGS_CTRL().
Рисунок 1.1
Как видно из результатов на рисунке 2.1 выполнения кода из листинга 2.3, в ncurses процедуры echo() и noecho() контролируют ввод каждого символа пользователем с клавиатуры и его отображение на экране терминала. При этом за чтение символа из потока вывода(stdout) отвечает getch(3) .
Следовательно, перенаправление символа с ввода (stdin) на вывод (stdout) предпочтительней делать на уровне пользователя, чтобы избегать дублирование и случайный выброс символа, что может вызвать нарушение целостности интерфейса у пользователя.
3. Библиография
3.1 echo(3) - Linux man page: curses input options
3.2 termios(3) - Linux man page: get and set terminal attributes, line control, get and set baud rate
3.3 ECHO вывод сообщений и переключения режима отображение команд на экране
3.4 The Single UNIX ® Specification, Version 2. Interface Overview
3.5 tty/no_echo.c from “The Linux Programming Interfase