Чтобы написать программу для посимвольного чтения из терминала и вывода каждого символа в отдельной строке потребуется несложный код.
1: /* slecho.c */
2:
3: #include <ctype.h>
4: #include <slang/slang.h>
5: #include <stdio.h>
6:
7: int main(void) {
8: char ch = ' ';
9:
10: /*
11: Начать обработку SLANG tty со следующими параметрами:
12: -1 символ прерывания по умолчанию (обычно Ctrl-C)
13: 0 управление потоком не производится; все символы (кроме
14: символа прерывания) будут переданы в программу 1 разрешение
15: обработки выходных данных OPOST управляющих последовательностей
16: */
17: SLang_init_tty(-1, 0, 1);
18:
19: while (ch != 'q') {
20: ch = SLang_getkey();
21: printf("чтение: %c 0x%xn", isprint(ch) ? ch : ' ', ch);
22: }
23:
24: SLang_reset_tty();
25:
26: return 0;
27: }
Эта программа предполагает, что все заголовочные файлы S-Lang содержатся в каталоге /usr/include/slang. Если в вашей системе они находятся в другом каталоге, то тогда следует изменить соответствующим образом код (это касается всех примеров в этой главе). Для компилирования и компоновки этой программы в команду компоновки потребуется добавить -lslang, чтобы компоновщик мог найти функции S-Lang.
24.1.1. Инициализация обработки ввода в S-Lang
Прежде чем какая-либо функция обработки входных данных сможет работать, с помощью функции Slang_init_tty() нужно перевести терминал в состояние, которое ожидается S-Lang:
int SLang_init_tty(int abort_char, int flow_ctrl, int opost);
Первый параметр функции Slang_init_tty() определяет символ, который будет использован для прекращения работы. При передаче значения -1 будет сохранен текущий символ прерывания tty (обычно, <Ctrl+C>); в противном случае символу прерывания будет назначено переданное значение. Каждый раз при вводе на терминале символа прекращения работы ядро посылает сигнал SIGINT тому процессу, который обычно завершает работу приложения. В главе 12 мы рассказывали о том, как производится обработка сигналов, подобных SIGINT.
Следующий параметр отвечает за включение и отключение управления потоком. Управляя потоком на уровне терминала, пользователь может приостанавливать процесс вывода данных на терминал, не допуская прокрутки, а затем возобновлять его. Обычно для приостановления процесса вывода данных на экран используется <Ctrl+S> и <Ctrl+Q> — для возобновления этого процесса. Хотя эта особенность удобна для некоторых утилит, ориентированных на работу со строками, программы, работающие с библиотекой S-Lang, обычно ориентированы на работу с экраном, поэтому она может оказаться излишней. S-Lang позволяет приложениям отключать эту функциональность, в результате чего программа сможет назначить нажатия клавиш Stop (Стоп) и Start (Пуск) для других команд. Чтобы включить управление потоками, функции SLang_init_tty() необходимо передать ненулевое значение в качестве второго параметра.
Последний параметр разрешает заключительную обработку вывода на терминале. Любой механизм ядра, связанный с заключительной обработкой, будет включен, если последний параметр будет иметь ненулевое значение. Информацию об обработке выходных данных можно найти в главе 16.
24.1.2. Восстановление состояния терминала
После того как состояние терминала было изменено с помощью функции SLang_init_tty(), программа, прежде чем завершить свою работу, должна явным образом восстановить первоначальное состояние терминала. Если этого не сделать, то вряд ли можно будет работать с терминалом после завершения программы. Функция SLang_init_tty() не принимает и не возвращает никаких аргументов.
Если вы пишете программу, работу которой нужно будет приостановить (обычно посредством нажатия <Ctrl+Z>), то эту функцию также необходимо вызывать после получения сигнала SIGTSTP. Более подробно об обработке сигнала SIGTSTP можно прочитать в главе 15.
Не исключено, что в процессе разработки программ с помощью библиотеки S-Lang в них будут неоднократно происходить сбои, после которых терминал будет находиться в нестандартном состоянии. С этой проблемой можно справиться, если выполнить команду stty sane.
24.1.3. Чтение символов с терминала
После правильной инициализации терминала чтение одиночных нажатий клавиш не составит труда. Функция SLang_getkey() возвращает одиночный символ из терминала. Однако это не означает, что функция возвращает одиночное нажатие клавиши, ведь в системе Unix после многих нажатий клавиш может быть возвращено несколько символов. Например, на терминале VT100 (а также на многих других терминалах, включая консоль Linux) при нажатии клавиши <F1> на экран посылается четыре символа — ESC [ [ А (попробуйте запустить slecho, нажать клавишу <F1> и посмотрите, что получится). Чтобы установить соответствие между подобными многосимвольными последовательностями и нажатиями клавиш, можно использовать базу данных terminfo [37].
Функция SLang_get_key(), прежде чем вернуть результат, в течение неопределенного периода времени ожидает нажатие символа, который необходимо представить. В случае возникновения ошибки вместо действительного символа эта функция возвращает значение 0xFFFF[169].
24.1.4. Проверка ожидающего ввода
Во многих случаях вам нужно будет проверять доступные символы, не прибегая при этом к блокировке. Это удобно делать тогда, когда программе необходимо перейти к фоновой обработке, а пользователю в этот момент посылается запрос на ввод данных (особенно в видеоиграх). Функция SLang_input_pending() определена следующим образом:
int SLang_input_pending(int timeout);
Функция SLang_input_pending() возвращает true, если символы стали доступными в течение n десятых долей секунды. Она возвращает результат, как только символы становятся доступными, и false, если ни один из символов не окажется доступным в течение определенного периода времени. Если задать нулевой период времени, то функция SLang_input_pending() сообщит о доступных в данный момент символах.
Это поведение легко пронаблюдать. Для этого в программе slecho.с достаточно изменить проверку в цикле while:
while (ch != 'q' && SLang_input_pending(20))
Теперь программа будет ожидать ввода дополнительных данных не более двух секунд. По истечении двух секунд, если никакие данные не будут введены, работа программы будет завершена.
24.2. Обработка вывода
Функции библиотеки S-Lang, предназначенные для вывода данных на терминал, бывают двух разновидностей: функции управления терминалом (семейство SLtt) и функции высокого уровня для управления экраном (семейство SLsmg).
Функции, принадлежащие семейству SLtt, работают напрямую с терминалом; к ним принадлежат функции, осуществляющие отображение данных в строгом соответствии с возможностями, определенными в терминальной базе данных. Это семейство также включает набор подпрограмм для определения пар цветов переднего плана и фона, а также включения и выключения курсора. Разработчики приложений обычно используют только некоторые из этих функций, а остальные вызываются внутри самой библиотеки S-Lang.
Семейство SLsmg предлагает высокий уровень абстракции терминала. Хотя эти функции используют функции семейства SLtt для управления терминалом, они предлагают разработчикам приложений интерфейс с более широкими возможностями.
Эти функции отвечают за вывод строк, рисование линий и отправку запросов к экрану. Чтобы не допустить снижения производительности, эти подпрограммы осуществляют запись во внутренний буфер, а не напрямую на терминал. Когда приложение посылает библиотеке S-Lang запрос на обновление физического терминала, она сравнивает новое содержимое с исходным и соответствующим образом оптимизирует последовательность выходных данных.
24.2.1. Инициализация управления экраном
Прежде чем использовать функции библиотеки S-Lang для вывода данных на терминал, программа должна послать S-Lang запрос на поиск текущего терминала (как это определено в переменной окружения TERM) в терминальной базе данных. Это осуществляется следующим образом:
void SLtt_get_terminfo(void);
Одной из главных задач функции SLtt_get_terminfo() является установка физического размера экрана в соответствии с размером, указанным в базе данных терминала. Информация о количестве строк и колонок в терминале хранится, соответственно, в SLtt_Screen_Rows и SLtt_Screen_Cols. Хотя данные в терминальной базе данных обычно корректны, в настоящее время широкую популярность приобрели терминалы с изменяемыми размерами (например, xterms). После того как размер такого терминала будет изменен по отношению к размеру, принятому по умолчанию, терминальная база данных не будет содержать корректной информации о размерах терминала. Для компенсации этой неточности библиотека S-Lang позволяет программам восстанавливать исходные значения SLtt_Screen_Rows и SLtt_Screen_Cols после вызова функции SLtt_get_terminfo(). В системе Unix текущие размеры терминала всегда можно узнать с помощью команды TIOCGWINSZ управления вводом-выводом, которая подробно описана в главе 16.