CURSES/TERMINFO СОДЕРЖАНИЕ Предисловие 1. Введение 1.1. Что такое curses? 1.2. Что такое terminfo? 1.3. Взаимодействие curses и terminfo 1.4. Другие компоненты пакета управления терминалом 2. Использование подпрограмм пакета curses 2.1. Что нужно программе для работы с curses 2.1.1. Файл 2.1.2. Подпрограммы initscr(), refresh() и endwin() 2.2. Компиляция программы, которая использует curses 2.3. Выполнение программы, которая использует curses 2.4. Еще о строках, столбцах и подпрограмме initscr() 2.5. Еще об окнах и подпрограмме refresh() 2.6. Несложный ввод/вывод 2.6.1. Вывод 2.6.2. Ввод 2.7. Управление вводом/выводом 2.7.1. Атрибуты вывода 2.7.2. Звонки, свист, вспышки 2.7.3. Опции ввода 2.8. Работа с окнами 2.8.1. Ввод/вывод 2.8.2. Подпрограммы wnoutrefresh() и doupdate() 2.8.3. Новые окна 2.9. Прочие возможности пакета curses 2.9.1. Линии на экране и прочая графика 2.9.2. Использование программируемых меток 2.9.3. Работа с несколькими терминалами сразу 3. Использование подпрограмм пакета terminfo 3.1. Что нужно программе для работы с terminfo 3.2. Компиляция и выполнение программ, которые используют terminfo 3.3. Пример программы, работающей с terminfo 4. Использование базы данных terminfo 4.1. Создание описания терминала 4.1.1. Название терминала 4.1.2. Выяснение характеристик терминала 4.1.3. Указание характеристик терминала 4.1.3.1. Основные характеристики 4.1.3.2. Характеристики экрана 4.1.3.3. Характеристики клавиатуры 4.1.3.4. Параметризованные цепочки 4.1.4. Компиляция описания 4.1.5. Тестирование описания 4.2. Печать и сравнение описаний в базе данных terminfo 4.3. Преобразование termcap-описания в terminfo-описание 5. Примеры программ, работающих с curses 5.1. Программа editor 5.2. Программа highlight 5.3. Программа scatter 5.4. Программа show 5.5. Программа two 5.6. Программа window ПРЕДИСЛОВИЕ Многие коммерческие программные системы имеют в своем составе программы, управляющие вводом и выводом на терминал. Такая программа может перемещать курсор, показывать меню, делить эк- ран терминала на окна или форматировать экран для облегчения ввода и извлечения информации из базы данных. В этой главе объясняется, как разрабатывать программы, работаю- щие с терминалом в системе UNIX, используя пакет подпрограмм, называемый curses/terminfo. Этот пакет включает библиотеку подпрограмм на языке C, базу данных и набор вспомогательных средств системы UNIX. Назначение данной главы - не описание всех компонент пакета, а предоставление пользователю возможнос- ти немедленно приступить к написанию программ, работающих с терминалом. Здесь рассматриваются только наиболее часто исполь- зуемые подпрограммы, в отношении же остальных делается ссылка на curses(3X) и terminfo(4) в Справочнике программиста. Держите эту книгу под рукой на случай, если Вам нужно будет узнать под- робности о какой-либо из описанных (или не описанных) здесь подпрограмм. Для использования curses/terminfo нужно быть знакомым с языком программирования C, так как подпрограммы пакета написаны именно на нем. Кроме того, необходимо знать стандартный пакет средств ввода/вывода системы UNIX для языка C [см. stdio(3S)]. Вы смо- жете создавать разнообразные программы для работы с терминалом, пользуясь этими знаниями и пониманием принятого в системе UNIX принципа опоры на работу, уже сделанную другими. Данная глава делится на пять разделов: 1. Введение В этом разделе кратко описываются curses, terminfo и другие компоненты пакета подпрограмм для работы с тер- миналом. 2. Использование подпрограмм пакета curses В этом разделе описываются основные подпрограммы, сос- тавляющие библиотеку curses(3X). Рассказывается о подп- рограммах вывода на экран, чтения с экрана, построения окон. Описываются также подпрограммы, реализующие более сложные функции - линейную графику, использование тер- минальных программируемых меток, работу с несколькими терминалами одновременно. Приводится множество приме- ров, демонстрирующих эффективность использования этих подпрограмм. 3. Использование подпрограмм пакета terminfo В этом разделе описываются те подпрограммы пакета curses, которые непосредственно взаимодействуют с базой данных terminfo для реализации таких возможностей, как программируемые функциональные клавиши. 4. Использование базы данных terminfo В этом разделе описывается база данных terminfo, соот- ветствующие средства поддержки и их взаимодействие с библиотекой curses. 5. Примеры программ, работающих с curses В этом разделе приведены тексты шести программ, иллюст- рирующие использование подпрограмм пакета curses. 1. ВВЕДЕНИЕ 1.1. Что такое curses? Curses(3X) - это библиотека подпрограмм, которые используются для разработки программ, осуществляющих ввод/вывод на экран терминала в системе UNIX. Эти подпрограммы являются функциями C или макросами. Многие из них напоминают подпрограммы из стан- дартной библиотеки языка C. Например, имеется подпрограмма printw(), весьма похожая на printf(3S) и подпрограмма getch(), подобная getc(3S). В Вашем банке программа - автоматический кассир может использовать printw() для вывода меню и getch() для приема Ваших запросов на изъятие сумм (или, что даже лучше, на их вклад). Экранный текстовый редактор - такой, например, как редактор vi(1) системы UNIX, также может использовать эти и другие подпрограммы пакета curses. Название curses принято из-за того, что данная библиотека подп- рограмм оптимизирует движение курсора, то есть минимизирует это движение в процессе обновления экрана. Например, если (исполь- зуя подпрограммы пакета curses) Вы разработали текстовый редак- тор и редактируете фразу curses/terminfo - отличный пакет для работы с экраном так, чтобы она читалась: curses/terminfo - лучший пакет для работы с экраном то программа выведет только ' лучший' вместо 'отличный', ос- тальные символы останутся без изменений. Оптимизация управления курсором называется еще оптимизацией вывода, поскольку миними- зируется объем передаваемых данных, то есть вывод. При оптимизации управления курсором запись на экран производит- ся таким способом, который соответствует терминалу, с которым работает программа, использующая пакет curses. Таким образом, библиотека curses позволяет делать все необходимое на термина- лах различных типов. Подпрограммы пакета просматривают базу данных terminfo (подробно описывается ниже), чтобы найти подхо- дящее описание терминала. Чем будет полезна оптимизация управления курсором Вам и тем, кто будет пользоваться Вашими программами? Во-первых, она сэко- номит Ваше время, затрачиваемое на описание того, как именно В_ хотите изменять содержимое экрана. Во-вторых, она сохранит вре- мя пользователя за счет уменьшения времени, необходимого для переписывания экрана. В-третьих, она уменьшит загрузку линий связи системы UNIX в период обновления экрана. В-четвертых, Вам не придется задумываться об огромном количестве терминалов, на которых Ваша программа, быть может, будет работать. Далее приводится текст простой программы, работающей с curses. Она обращается к нескольким подпрограммам curses для того, что- бы передвинуть курсор на середину экрана и вывести цепочку сим- волов BullsEye. Все эти подпрограммы описываются в следующем разделе, который называется Использование подпрограмм пакета curses. Чтобы понять, что делают эти подпрограммы, Вам доста- точно взглянуть на их имена. #include main () { initscr (); move (LINES/2 - 1, COLS/2 - 4); addstr ("Bulls"); refresh (); addstr ("Eye"); refresh (); endwin (); } 1.2. Что такое terminfo? Под terminfo мы понимаем следующее: Группу подпрограмм из библиотеки curses, которые управ- ляют некоторыми терминальными функциями. Например, Вы можете использовать их для написания фильтров, или для программирования функциональной клавиатуры, если она на Вашем терминале программируемая. Подпрограммами terminfo могут пользоваться как те программисты, кото- рые пишут на C, так и те, которые работают в shell'е. Базу данных, содержащую описания многих типов термина- лов, с которыми могут работать программы, если они ис- пользуют curses. Эти описания содержат характеристики терминала и то, как терминал выполняет различные опера- ции,- например, сколько строк и столбцов отображается на экране, как интерпретируются управляющие символы и т.п. Описание каждого терминала компилируется в отдель- ный файл. Вам следует использовать язык, описываемый в terminfo(4), для создания таких файлов, и команду tic(1M) для их компиляции. Скомпилированные файлы обыч- но находятся в каталогах /usr/lib/terminfo/?. Эти ката- логи имеют имена, состоящие из одного символа - первой буквы названия терминала. Например, описание терминала AT&T Teletype 5425 обычно находится в файле /usr/lib/ terminfo/a/att5425. Далее приводится простая командная процедура, использующая базу данных terminfo. # Очистить экран и показать позицию 0,0 # tput clear tput cup 0 0 # или tput home echo "<- это позиция 0 0" # # Показать позицию 5,10 # tput cup 5 10 echo "<- это позиция 5 10" 1.3. Взаимодействие curses и terminfo Подпрограммы пакета curses извлекают из базы данных terminfo информацию о том типе терминала, на котором программа выполня- ется. В дальнейшем этот терминал мы будем называть текущим тер- миналом. Предположим, что программа, текст которой приведен в конце раз- дела Что такое curses?, выполняется на терминале AT&T Teletype 5425. Чтобы отработать должным образом, то есть вывести BullsEye в центр экрана, программа должна иметь информацию о количестве строк и столбцов на экране. Эти данные хранятся в описании терминала AT&T Teletype 5425 в базе данных terminfo. Чтобы получить их, программе, работающей с curses, достаточно знать только название терминала, на котором она выполняется. Можно передать ей это название, поместив его в переменную окру- жения $TERM при подключении к системе UNIX, или путем присвое- ния значения и помещения в окружение переменной $TERM в Вашем файле .profile [см. profile(4)]. Располагая значением $TERM, программа, использующая curses, может извлечь описание текущего терминала из базы данных terminfo. Пусть, например, в .profile включены следующие строки: TERM=5425 export TERM tput init В первой строке устанавливается название терминала, а во второй оно помещается в окружение. Третья строка примера требует от системы UNIX проинициализировать текущий терминал, то есть обеспечить соответствие состояния терминала его описанию в базе данных terminfo. (Порядок этих строк важен. Чтобы при вызове tput произошла правильная инициализация текущего терминала, $TERM должна быть уже установлена и помещена в окружение). Ес- ли, имея такие строки в файле .profile, Вы запустите программу, работающую с curses, она получит нужную ей информацию о терми- нале из файла, соответствующего значению $TERM, то есть из фай- ла /usr/lib/terminfo/a/att5425. 1.4. Другие компоненты пакета управления терминалом Как было сказано выше, пакет управления терминалом обычно назы- вают curses/terminfo. Однако, в нем есть и другие компоненты, из которых мы уже упомянули, в частности, tic(1M). Далее приво- дится полный список компонент, рассматриваемых в этом руко- водстве: captoinfo(1M) Средство для перевода описаний термина- лов, созданных под ранними версиями сис- темы UNIX, в формат terminfo. curses(3X) infocmp(1M) Средство для печати и сравнения скомпили- рованных описаний терминалов. tabs(1) Средство для установки нестандартных по- зиций табуляции. terminfo(4) tic(1M) Компилятор описаний терминалов для базы данных terminfo. tput(1) Средство для установки позиций табуляции на терминале и получения значений его ха- рактеристик. См. также profile(4), scr_dump(4), term(4) и term(5). Более подробную информацию об этих компонентах см. в Справочнике программиста и Справочнике пользователя. 2. ИСПОЛЬЗОВАНИЕ ПОДПРОГРАММ ПАКЕТА CURSES В этом разделе рассматриваются основные подпрограммы пакета curses, которые используются при создании интерактивных терми- нальных программ. В начале раздела описаны подпрограммы (и дру- гие компоненты), которые нужны для нормальной работы любой программе, работающей с пакетом (в дальнейшем мы для краткости будем иногда называть такие программы curses-программами). В оставшейся части раздела рассказывается о том, как компилиро- вать и выполнять такую программу. Наконец, описываются наиболее часто применяемые подпрограммы, которые делают следующее: Осуществляют ввод и вывод данных на экран терминала. Управляют вводом и выводом данных - например, подсвечи- вают выводимые данные или подавляют отображение вводи- мых символов на экране ("эхо"). Работают с несколькими образами экрана (окнами). Выполняют простые графические функции. Управляют программируемыми метками на экране терминала. Осуществляют ввод/вывод, работая одновременно с нес- колькими терминалами. По мере описания подпрограмм мы приводим простые программы, ил- люстрирующие их применение. Кроме того, мы ссылаемся на группу более значительных примеров, находящихся в разделе Примеры программ, работающих с curses. Эти последние программы более глубоки и зачастую используют подпрограммы, которые здесь не обсуждаются, поэтому держите под рукой справочник curses(3X). 2.1. Что нужно программе для работы с curses Если программа использует пакет curses, она должна включать файл и вызывать подпрограммы initscr(), refresh(), или им подобные, а также endwin(). 2.1.1. Файл В файле определяются несколько глобальных переменных и структур данных, а также те из подпрограмм пакета, которые в действительности являются макросами. Начнем с рассмотрения определяемых в файле переменных и струк- тур данных. В файле определены параметры всех подп- рограмм, входящих в пакет curses. Кроме того, определяются це- лочисленные переменные LINES и COLS, которым при выполнении программы назначаются значения соответственно вертикального и горизонтального размера экрана. Это делается при вызове описы- ваемой ниже подпрограммы initscr(). В файле определяются также константы OK и ERR. Большинство подпрограмм curses возвращают OK при нормальном завершении и ERR при возникновении ошибки. Примечание LINES и COLS являются внешними (глобальными) переменны- ми, представляющими размеры экрана. В пользовательском окружении можно установить значения двух подобных им переменных, $LINES и $COLUMNS. Программы curses исполь- зуют значения этих переменных окружения для определения размера экрана. В этой главе при ссылках на переменные окружения используется знак $, чтобы отличить их от пе- ременных, объявляемых в файле . Дополнитель- ную информацию об этих переменных см. ниже в разделах Подпрограммы initscr(), refresh() и endwin() и Еще о строках, столбцах и подпрограмме initscr(). Рассмотрим теперь макроопределения. Многие подпрограммы curses определены в как макросы, которые обращаются к дру- гим подпрограммам и макросам из curses. Например, refresh() яв- ляется макросом. Определение #define refresh() wrefresh(stdscr) показывает, что вызов refresh расширяется в обращение к также входящей в curses подпрограмме wrefresh(). Эта последняя, в свою очередь, вызывает две другие подпрограммы curses: wnoutrefresh() и doupdate(). Многие подпрограммы обеспечивают нужный результат, группируя два-три обращения к другим. Предостережение Макрорасширения curses могут создать проблемы при ис- пользовании некоторых конструкций языка C, например, ++ или --. Одно последнее замечание о : он автоматически включа- ет , файл интерфейса с драйвером tty, . Пов- торное включение в программу этих файлов безвредно, но и бесс- мысленно. 2.1.2. Подпрограммы initscr( ), refresh( ) и endwin( ) Подпрограммы initscr(), refresh() и endwin() приводят терминал в состояние "работа с curses", обновляют содержимое экрана и восстанавливают терминал в состоянии "вне curses" соответствен- но. Для уяснения действия каждой из этих подпрограмм воспользу- емся еще раз нашим простым примером. #include main () { initscr (); /* Инициализируем терминал и переменные и структуры данных из */ move (LINES/2 - 1, COLS/2 - 4); addstr ("Bulls"); refresh (); /* Выводим данные на экран терминала */ addstr ("Eye"); refresh /* Выводим еще на экран терминала */ endwin (); /* Восстанавливаем состояние терминала */ } Curses-программа начинается обычно с вызова initscr(); это дос- таточно сделать один раз. Подпрограмма initscr() определяет тип терминала, на котором выполняется программа, по значению пере- менной окружения $TERM, как это описано в разделе Взаимодейст- вие curses и terminfo. Затем подпрограмма инициализирует все переменные и структуры данных, описанные в файле . initscr() присваивает переменным LINES и COLS значения, соот- ветствующие терминалу, на котором выполняется программа. Напри- мер, если используется Teletype 5425, она присвоит значение 24 переменной LINES, а значение COLS станет равным 80. Если возни- кает ошибка, подпрограмма записывает сообщение об этом в stderr и завершает работу. Ввод/вывод при выполнении программы производится подпрограмма- ми, подобными использующимся в примере move() и addstr(). Нап- ример, move (LINES/2 - 1, COLS/2 - 4); требует перемещения курсора в точку, находящуюся несколько ле- вее центра экрана. Затем addstr ("Bulls"); требует вывести цепочку символов Bulls. Если программа выполня- ется на терминале Teletype 5425, эта цепочка будет выведена, начиная с позиции (11, 36). Внимание! Все подпрограммы curses, перемещающие курсор, отсчиты- вают его координаты относительно левого верхнего угла экрана. Координаты (LINES, COLS) этого угла принимаются за (0, 0), а не за (1, 1). Заметьте, что первой идет вертикальная координата, а затем горизонтальная, в от- личие от обычной записи 'x, y' для экранных (или графи- ческих) координат. Для перемещения курсора в среднюю строку экрана в примере появляется -1, чтобы учесть ко- ординаты (0, 0) для угловой позиции. При вызове подпрограмм типа move() и addstr() содержимое физи- ческого экрана терминала не меняется. Экран обновляется только при вызове refresh(). До такого вызова изменяется только внут- реннее представление экрана, которое называется окном. Эту весьма важную концепцию мы обсудим далее в разделе Еще об окнах и подпрограмме refresh(). В заключение отметим, что программа, работающая с curses, за- канчивается вызовом endwin(). Эта подпрограмма восстанавливает прежнее состояние терминала и помещает курсор в левый нижний угол экрана. 2.2. Компиляция программы, которая использует curses Компилируйте такую программу, как обычную программу на языке C, командой cc(1) (см. Справочник программиста), которая вызывает Kомпилятор языка C. Подпрограммы обычно хранятся в библиотеке /usr/lib/ libcurses.a. Чтобы редактор внешних связей просматривал эту библиотеку, в команде cc необходимо указать опцию -l. Общий формат командной строки для компиляции программы, работа- ющей с curses, таков: cc файл.c -lcurses -o файл Файл.c - это имя файла с исходным текстом программы; файл - имя выполнимого объектного файла. 2.3. Выполнение программы, которая использует curses В процессе своей работы подпрограммы curses опираются на инфор- мацию из окружения пользователя. В частности, пользователю та- кой программы обычно нужно включить в свой файл .profile следу- ющие три строки: TERM=тип_текущего_терминала export TERM tput init Смысл этих строк объясняется в разделе Взаимодействие curses и terminfo. Пользователи curses-программы могут, кроме того, ус- тановить в файле .profile значения переменных окружения $LINES, $COLUMNS и $TERMINFO, однако, в отличие от случая с $TERM, де- лать это не обязательно. Если Ваша curses-программа не работает должным образом, Вы, возможно, попытаетесь ее отладить с помощью sdb(1), как это описывается в Справочнике программиста. Пользуясь sdb, нужно учитывать ряд обстоятельств. Во-первых, curses-программа инте- рактивна и контролирует положение курсора. Однако, интерактив- ный отладчик, подобный sdb, может произвести на экране измене- ния, о которых не будет знать отлаживаемая программа. Во-вторых, программа, работающая с curses, выводит в окно до тех пор, пока не будет вызвана refresh() или подобная ей подп- рограмма. В связи с такой задержкой вывода на экран, отладка его правильности может быть затруднена. В-третьих, во входящих в curses макросах нельзя установить точ- ки прерывания. Вместо макросов нужно использовать подпрограммы, к которым эти макросы обращаются - например, к wrefresh() вмес- то refresh(), Более подробную информацию о макросах см. выше в разделе Файл . 2.4. Еще о строках, столбцах и подпрограмме initscr( ) Определив размеры экрана терминала, initscr() присваивает зна- чения переменным LINES и COLS. Эти значения берутся из перемен- ных terminfo, называемых lines и columns. Последние, в свою очередь, извлекаются из базы данных terminfo, если не установ- лены значения переменных окружения $LINES и $COLUMNS. 2.5. Еще об окнах и подпрограмме refresh( ) Как указывалось ранее, подпрограммы curses не обновляют экран, пока не будет вызвана refresh(). Когда refresh() вызывается, все данные, накопленные для вывода, пересылаются на экран теку- щего терминала. Эффект использования окна во многом подобен эффекту использова- ния буфера при работе с редактором системы UNIX. Например, ког- да Вы редактируете файл при помощи vi(1), все изменения содер- жимого файла отражаются в буфере, а сам файл изменяется только после выдачи команд w или zz. Аналогично, когда Вы вызываете программу, работающую с экраном через пакет curses, изменяется содержимое окна, а сам экран терминала перерисовывается только при вызове refresh(). В содержится описание принятого по умолчанию окна stdscr (стандартное окно), размеры которого совпадают с разме- рами экрана терминала. В stdscr имеет тип WINDOW*, указатель на структуру языка C, которую пользователь может представлять себе в виде двумерного массива символов, соот- ветствующего экрану терминала. Программа всегда отслеживает как состояние stdscr, так и состояние физического экрана. refresh(), когда вызывается, сравнивает их и посылает на терми- нал последовательность символов, приводящий его экран в соот- ветствующий содержимому stdscr вид. При этом выбирается один из многих способов сделать это, с учетом характеристик терминала и возможного сходства того, что есть на экране и того, что содер- жится в окне. Выходной поток оптимизируется таким образом, что- бы он содержал как можно меньше символов. На рисунке ниже пока- зано, что происходит при работе программы, выводящей BullsEye в центре экрана (см. раздел Что такое curses?). Обратите внима- ние, что, какой бы мусор ни был на экране, он там и останется, пока не будет вызвана refresh(). При этом вызове экран очищает- ся и заполняется текущим содержимым stdscr. stdscr физический экран initscr () ~------------ ~------------ │_ │ │ │ │ │ │ (мусор) │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ move (LINES/2-1, COLS/2-4) ~------------ ~------------ │ │ │ │ │_ │ │ (мусор) │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ addstr ("Bulls") ~------------ ~------------ │ │ │ │ │ Bulls_ │ │ (мусор) │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ refresh () ~------------ ~------------ │ │ │ │ │ Bulls_ │ │ Bulls_ │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ addstr ("Eye") ~------------ ~------------ │ │ │ │ │ BullsEye_ │ │ Bulls_ │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ refresh () ~------------ ~------------ │ │ │ │ │ BullsEye_ │ │ BullsEye_ │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ endwin () ~------------ ~------------ │ │ │ │ │ BullsEye_ │ │ BullsEye │ │ │ │ │ │ │ │ │ │ │ │_ │ ------------ ------------ Вместо stdscr Вы можете создавать и использовать другие окна. Окна полезны для одновременного ведения нескольких образов эк- рана. Например, во многих приложениях для ввода и вывода данных используются два окна: одно для ввода/вывода собственно данных, а другое - для вывода сообщений об ошибках, чтобы эти сообщения не портили содержимое основного окна. Можно разделить экран на большое количество окон, обновляя по желанию то или иное из их. Если окна перекрываются, на экран выводится содержимое того окна, которое обновлялось позже. Мож- но также создать одно окно внутри другого (первое мы будем иногда называть подокном). Допустим, разрабатываемое Вами при- ложение использует в качестве интерфейса с пользователем экран- ные формы, например, для изображения на экране расписки. Тогда можно использовать меньшие, находящиеся внутри основного, окна для управления доступом к отдельным полям такой формы. Некоторые подпрограммы пакета curses предназначены для работы с окнами особого типа, которые мы будем называть спецокнами. Спе- цокно - это такое окно, размер которого не ограничивается раз- мером экрана и которое не связано с каким-либо определенным местом на экране. Их можно применять, если Вам нужны очень большие окна, или же такие, которые нужно отображать на экран частями. Спецокна могут, например, понадобиться при работе с электронными таблицами. Ниже показаны взаимосвязи между несколькими окнами, подокнами и спецокнами и экраном терминала. ~--------------------------------------------- │ экран терминала │ │ ~---------- ~---------- │ │ │ окно │ │ окно │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ~------------ -------------- │ │ │ │ │ │ │ │ │ │ ~----- │ │ │ │ │ спецокно │ │ │ │под- │ │ │ │ │ │ │ │ │ │ окно│ │ │ │ │ │ │ │ │ │ │ │ ~- -------- │ │ │ ~---------- │ │ │ ----- │ │ │ спец- │ │ │ │ │ подспец- │ │ │ │ │ │ │ окно │ │ │ │ │ окно │ │ │ ---------- │ -------- - │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ---------- │ │ │ │ │ │ │ │ │ │ │ │ │ │ ~---------- ---------- - │ │ │ │ │ окно │ │ │ │ │ │ │ │ │ │ │ ------------ -------------- │ ---------- ---------- - │ │ │ │ │ │ │ │ │ --------------- ---------- ------------------ │ │ │ │ │ │ │ │ ---------- В разделе Работа с окнами описываются подпрограммы, необходимые для создания и использования окон. Если Вы хотите сейчас уви- деть программу, работающую с окнами средствами curses, см. программу window в разделе Примеры программ, работающих с curses. 2.6. Несложный ввод/вывод 2.6.1. Вывод Curses содержит подпрограммы для записи в stdscr, похожие на подпрограммы из stdio(3S) для записи в файл. Они позволяют: Выводить одиночный символ - addch(). Выводить цепочку символов - addstr(). Форматировать строку из нескольких входных аргументов - printw(). Перемещать курсор, возможно, с одновременным выводом символа (символов) - move(), mvaddch(), mvaddstr(), mvprintw(). Очистить весь экран или его часть - clear(), erase(), crltoeol(), clrtobot(). Далее следуют описания и примеры применения этих подпрограмм. Предостережение Библиотека curses располагает своим собственным набором подпрограмм ввода/вывода. Если Вы работаете с curses, Вам не следует использовать для ввода/вывода других подпрограмм или обращений к системе, например, read(2) или write(2); в противном случае при выполнении подп- рограммы могут возникнуть нежелательные эффекты. addch( ) СИНТАКСИС #include int addch (ch) chtype ch; ОПИСАНИЕ addch() записывает один символ в stdscr. Этот символ имеет тип chtype, который определяется в . chtype содержит данные и атрибуты (об атри- бутах см. ниже в разделе Атрибуты вывода). Работая с переменными этого типа, объявляйте их принад- лежащими именно к типу chtype, а не к тому основному типу (скажем, short), через который объявляется chtype в . Это обеспечит совместимость с будущими версиями. addch() производит некоторую перекодировку. Так, она преобразует: символ перевода строки в последовательность, обес- печивающую очистку до конца строки и переход к но- вой строке; символ табуляции в соответствующее ко- личество пробелов; другие управляющие символы в их запись в нотации ^X. addch() обычно возвращает OK, за исключением случая вы- вода символа в правом нижнем углу нероллируемого окна, когда возвращается ERR. addch() является макросом. ПРИМЕР #include main () { initscr (); addch ('a'); refresh (); endwin (); } Эта программа выводит следующее: ~------------------------------------------------------------- │a │ │ │ │ ... │ │ │ │ │ │$ _ │ ------------------------------------------------------------- См. также программу show в разделе Примеры программ, работающих с curses. addstr( ) СИНТАКСИС #include int addstr (str) char *str; ОПИСАНИЕ addstr() выводит цепочку символов в stdscr. addstr() вызывает addch() для вывода каждого символа. addstr() производит ту же перекодировку, что и addch(). addstr() возвращает OK при успешном завершении и ERR при ошибке. addstr() является макросом. ПРИМЕР См. примеры программ во Введении. printw( ) СИНТАКСИС #include int printw(fmt [,arg...]) char *fmt; ОПИСАНИЕ printw() осуществляет форматированный вывод в stdscr. Подобно printf(3S), printw() получает формат и перемен- ное количество аргументов. Подобно addstr(), printw() обращается к addch() для вы- вода каждого символа. printw() возвращает OK при успешном завершении и ERR при ошибке. ПРИМЕР #include main () { char *title = "Не указано"; int no = 0; . . . initscr (); . . . printw ("%s отсутствует на складе.", title); printw ("Попросите кассира заказать %d для Вас.\n", no); refresh (); endwin (); } Эта программа выводит следующее: ~------------------------------------------------------------- │Не указано отсутствует на складе. │ │Попросите кассира заказать 0 для Вас. │ │ │ │ ... │ │ │ │$ _ │ ------------------------------------------------------------- move( ) СИНТАКСИС #include int move (y, x); int y, x; ОПИСАНИЕ move() устанавливает курсор в позицию "строка y, колон- ка x" окна stdscr. Обратите внимание, что первым аргументом move() являет- ся координата y, а вторым - x. Координаты левого верх- него угла stdscr равны (0, 0), а правого нижнего - (LINES-1, COLS-1). Подробнее см. в разделе Подпрограммы initscr(), refresh() и endwin(). Перемещение курсора можно производить одновременно с выводом данных, а именно: mvaddch (y, x, ch) перемещает курсор и выводит символ; mvaddstr (y, x, str) перемещает курсор и выводит цепочку символов; mvprintw (y, x, fmt[,arg...]) перемещает курсор и выводит форматированную строку. move() возвращает OK при нормальном завершении и ERR при ошибке. К ошибке приводит попытка сдвинуть курсор в позицию, не находящуюся между (0, 0) и (LINES-1, COLS- 1). move() является макросом. ПРИМЕР #include main() { initscr (); addstr ("Курсор должен быть здесь -->"); addstr (" если move () работает."); printw ("\n\n\nНажмите для завершения теста."); move (0, 28); refresh (); getch (); /* Вводит , см. ниже */ endwin (); } Эта программа выводит следующее: ~------------------------------------------------------------- │Курсор должен быть здесь -->_если move () работает. │ │ │ │ │ │Нажмите для завершения теста. │ │ ... │ │ │ │ │ │ │ ------------------------------------------------------------- После нажатия возврата каретки экран будет выглядеть так: ~------------------------------------------------------------- │Курсор должен быть здесь -->_ │ │ │ │ │ │Нажмите для завершения теста. │ │ ... │ │ │ │ │ │ │ ------------------------------------------------------------- Другой пример использования move() можно найти в программе scatter в разделе Примеры программ, работающих с curses. clear( ), erase( ) СИНТАКСИС #include int clear ( ) int erase ( ) ОПИСАНИЕ Обе подпрограммы заполняют все окно stdscr пробелами. clear() допускает наличие на экране мусора, о котором она не знает; эта подпрограмма вызывает сначала erase(), а затем clearok(), что приводит к полной очистке физического экрана при следующем вызове refresh() для stdscr. Подробнее о clearok() см. curses(3X). initscr() автоматически вызывает clear(). clear() всегда возвращает OK, erase() не возвращает ни- чего полезного. И clear(), и erase() являются макросами. clrtoeol( ), clrtobot( ) СИНТАКСИС #include int clrtoeol ( ) int clrtobot ( ) ОПИСАНИЕ clrtoeol() заменяет остаток строки пробелами. clrtobot() заменяет остаток экрана пробелами. Обе подпрограммы выполняют свои действия с позиции, в которой находится курсор (включительно). Ни одна из подпрограмм не возвращает ничего полезного. ПРИМЕР Приведем текст программы, использующей clrtobot(). #include main () { initscr (); addstr ("Нажмите для удаления отсюда "); addstr ("до конца строки и далее."); addstr ("\nУдалите это тоже.\nИ это."); move (0, 32); refresh (); getch (); clrtobot (); refresh (); endwin (); } Эта программа выводит следующее: ~------------------------------------------------------------- │Нажмите для удаления отсюда_до конца строки и далее. │ │Удалите это тоже. │ │И это. │ │ │ │ ... │ │ │ │ │ ------------------------------------------------------------- Обратите внимание на два вызова refresh() - первый выводит этот текст на экран, второй очищает экран с отмеченного курсором места до конца. После нажатия возврата каретки экран будет выглядеть так: ~------------------------------------------------------------- │Нажмите для удаления отсюда │ │ │ │ ... │ │ │ │ │ │$ _ │ ------------------------------------------------------------- Примеры использования clrtoeol() см. в программах show и two в разделе Примеры программ, работающих с curses. 2.6.2. Ввод Подпрограммы curses, предназначенные для чтения с текущего тер- минала, подобны тем, которые содержатся в stdio(3S) и читают из файла. Они позволяют: Читать символы по одному: getch(). Читать строку, заканчивающуюся символом перевода стро- ки: getstr(). Сканировать вводимые символы, извлекая значения и прис- ваивая их переменным из списка аргументов: scanw(). Первичной подпрограммой является getch(), которая обрабатывает и возвращает значение одного введенного символа. Она подобна подпрограмме getchar(3S) из библиотеки языка C, за исключением того, что она может производить некоторые зависящие от системы или терминала действия, которые не выполняет getchar(). Напри- мер, getch() можно использовать совместно с подпрограммой keypad() из библиотеки curses. Это позволяет программе распоз- навать и обрабатывать, как один символ, последовательности, на- чинающиеся с ESC, которые передаются, например, при нажатии клавиш управления курсором или функциональных клавиш. Дополни- тельную информацию о keypad() см. в описаниях getch() и keypad() в curses(3X). Ниже описываются основные подпрограммы для ввода с терминала и даются примеры их использования. getch( ) СИНТАКСИС #include int getch ( ) ОПИСАНИЕ getch() читает один символ с текущего терминала. getch() возвращает значение этого символа или ERR при возникновении ситуации "конец файла", получении сигна- ла, или при чтении без ожидания, если символ еще не введен. getch() является макросом. См. далее, а также в curses(3X) описания echo(), noecho(), cbreak(), nocbreak(), raw(), noraw(), halfdelay(), nodelay() и keypad(). ПРИМЕР #include main () { int ch; initscr (); cbreak (); /* Пояснения см. в разделе "Опции ввода" */ addstr ("Введите любой символ: "); refresh (); ch=getch (); printw ("\n\n\nВы ввели '%c'.\n", ch); refresh (); endwin (); } Посмотрим, что выводит эта программа. Первый refresh() выводит цепочку символов, указанную в addstr(), из stdscr на экран тер- минала: ~------------------------------------------------------------- │Введите любой символ: _ │ │ │ │ │ │ ... │ │ │ │ │ ------------------------------------------------------------- Пусть на клавиатуре нажали w. getch() принимает символ и его значение присваивается ch. Наконец, второй раз вызывается refresh() и экран становится таким: ~------------------------------------------------------------- │Введите любой символ: │ │ │ │ │ │Вы ввели 'w'. │ │ │ │ ... │ │ │ │$ _ │ ------------------------------------------------------------- Другой пример использования getch() см. в программе show в раз- деле Примеры программ, работающих с curses. getstr( ) СИНТАКСИС #include int getstr (str) char *str; ОПИСАНИЕ getstr() читает символы и сохраняет их в буфере до тех пор, пока не будет нажат возврат каретки, перевод стро- ки или клавиша ввода. getstr() не проверяет буфер на переполнение. Прочитанные символы пересылаются в цепочку str. getstr() является макросом и вызывает getch() для чте- ния каждого символа. getstr() возвращает ERR только тогда, когда getch() возвращает ERR. В остальных слуаях возвращается OK. См. далее, а также в curses(3X) описания echo(), noecho(), cbreak(), nocbreak(), raw(), noraw(), halfdelay(), nodelay() и keypad(). ПРИМЕР #include main () { char str [256]; initscr (); cbreak (); /* Пояснения см. в разделе "Опции ввода" */ addstr ("Введите строку символов,"); addstr (" оканчивающуюся :\n\n"); refresh (); getstr (str); printw ("\n\n\nВы ввели \n'%s'\n", str); refresh (); endwin (); } Допустим, Вы ввели строку "Мне нравится изучать систему UNIX". После нажатия возврата каретки экран будет выглядеть так: ~------------------------------------------------------------- │Введите строку символов, оканчивающуюся : │ │ │ │Мне нравится изучать систему UNIX │ │ │ │ │ │Вы ввели │ │'Мне нравится изучать систему UNIX' │ │ ... │ │ │ │$ _ │ ------------------------------------------------------------- scanw( ) СИНТАКСИС #include int scanw (fmt [,arg...]) char *fmt; ОПИСАНИЕ scanw() вызывает getstr() и сканирует введенную строку. Подобно scanf(3S), scanw() использует формат для преоб- разования введенной строки и присваивает значения пере- менному количеству аргументов. scanw() возвращает те же значения, что и scanf(). Дополнительную информацию см. в описании scanf(3S). ПРИМЕР include main () { char string [100]; float number; initscr (); cbreak (); /* Пояснения см. в разделе "Опции ввода" */ echo (); addstr ("Введите число и текст,"); addstr (" разделенные запятой:"); refresh (); scanw ("%f,%s", &number, string); clear (); printw ("Вы ввели текст \"%s\" и число %f.", string, number); refresh (); endwin (); } Обратите внимание на два вызова refresh(). После первого вызова на экране появляется строка, переданная addstr(), после второго - строка, которую возвращает scanw(). Кроме того, обратите вни- мание на вызов clear(). Допустим, Вы ввели строку 2,twin. После выполнения программы экран терминала будет выглядеть так: ~------------------------------------------------------------- │Вы ввели текст "twin" и число 2. │ │ │ │ │ │ ... │ │ │ │$ _ │ ------------------------------------------------------------- 2.7. Управление вводом/выводом 2.7.1. Атрибуты вывода Рассказывая об addch(), мы упомянули, что эта подпрограмма вы- водит в stdscr один знак типа chtype. chtype состоит из двух частей: информации о самом символе и информации о наборе атри- бутов, связанных с этим символом. Эти атрибуты позволяют отоб- ражать символ с повышенной яркостью, подчеркнутым, инвертиро- ванным и т.д. С stdscr всегда связан набор атрибутов, которые автоматически присваиваются каждому выводимому символу. Вы можете изменить текущие значения атрибутов, используя attrset() или другие подпрограммы пакета curses, которые описаны ниже. Приведем здесь список атрибутов и их описания: A_BLINK - мерцание. A_BOLD - повышенная яркость или жирный шрифт. A_DIM - пониженная яркость. A_REVERSE - инвертированное отображение. A_STANDOUT - как можно заметнее, насколько это возможно на данном терминале. A_UNDERLINE - подчеркивание. A_ALTCHARSET - альтернативная кодировка (см. раздел Ли- нии на экране и прочая графика). Эти атрибуты можно передавать в качестве аргумента подпрограмме attrset() или ей подобным. Им можно передавать комбинации атри- бутов, об'единенные операцией диз'юнкции (]). Примечание Не каждый терминал может отображать любой из перечис- ленных атрибутов. Если терминал не может реализовать запрошенный атрибут, curses пытается заменить его похо- жим, а если и это невозможно, то атрибут игнорируется. Рассмотрим использование одного из этих атрибутов. Следующий фрагмент программы обеспечивает отображение слова с повышенной яркостью: . . . printw ("Яркое "); attrset (A_BOLD); printw ("слово"); attrset (0); printw (" бросается в глаза.\n"); . . . refresh (); Атрибуты можно включать по одному, как в примере: attrset (A_BOLD), или в комбинации. Например, чтобы вывести яркий мер- цающий текст, Вы можете использовать attrset (A_BOLD | A_BLINK). Те или иные атрибуты включаются и выключаются подп- рограммами attron() и attroff() без какого-либо влияния на ос- тальные атрибуты отображения. attrset (0) отключает все атрибу- ты. Заметьте, что в набор атрибутов входит A_STANDOUT, который мож- но применять для привлечения внимания пользователя. Для физи- ческой реализации этого атрибута используется наиболее визуаль- но выразительный способ отображения, возможный на данном терми- нале. Обычно это повышенная яркость или инверсия. Если нужно просто выделить часть текста, все равно, подсветкой, инверсией или как-либо еще, лучше использовать A_STANDOUT. Для его вклю- чения и выключения удобны функции standout() и standend() соот- ветственно. Фактически, standend() отключает все атрибуты. Кроме перечисленных атрибутов, имеется еще две битовые маски, а именно A_CHARTEXT и A_ATTRIBUTES. Их можно использовать для извлечения только символа или только атрибутов из значения, возвращаемого входящей в curses функцией inch(), путем их конъ- юнкции (операция & языка C) с этим значением. См. описание inch() в curses(3X). Приведем описание attrset() и других подпрограмм curses, кото- рые используются для управления атрибутами вывода. attron( ), attroff( ), attrset( ) СИНТАКСИС #include int attron (attrs) chtype attrs; int attrset (attrs) chtype attrs; int attroff (attrs) chtype attrs; ОПИСАНИЕ attron() включает запрошенные атрибуты attrs, сохраняя те, которые уже включены. attrs принадлежит к типу chtype, , определяемому во включаемом файле . attrset() включает запрошенные атрибуты attrs вместо тех, которые включены в момент обращения к ней. attroff() выключает запрошенные атрибуты attrs, если они включены. Атрибуты могут объединятся при помощи побитной операции ИЛИ (|). Все подпрограммы возвращают OK. ПРИМЕР См. программу highlight в разделе Примеры программ, ра- ботающих с curses. standout( ), standend( ) СИНТАКСИС #include int standout ( ) int standend ( ) ОПИСАНИЕ standout() включает атрибут A_STANDOUT и эквивалентен вызову attron (A_STANDOUT). standend() выключает все атрибуты и эквивалентен вызову attrset (0). Обе подпрограммы всегда возвращают OK. ПРИМЕР См. программу highlight в разделе Примеры программ, ра- ботающих с curses. 2.7.2. Звонки, свист, вспышки Иногда бывает нужно привлечь внимание пользователя. Для этого в пакет curses включены две подпрограммы, которые могут заставить терминал звенеть, а его экран - вспыхивать. flash() заставляет экран терминала вспыхнуть, если это возмож- но, если же нет - включает звуковой сигнал. Вспышка является альтернативой звуковому сигналу и особенно полезна, когда звук мешает кому-либо, сидящему неподалеку от терминала. Подпрограм- ма beep() вызывается, когда нужно подать звуковой сигнал (если это почему-либо невозможно на данном терминале, beep() пытается произвести вспышку). beep( ), flash( ) СИНТАКСИС #include int flash ( ) int beep ( ) ОПИСАНИЕ flash() пытается заставить вспыхнуть экран терминала. Если это невозможно, flash() пытается включить на тер- минале звуковой сигнал. beep() пытается включить на терминале звуковой сиг- нал. Если это невозможно, beep() пытается заставить вспыхнуть экран терминала. Ни одна из подпрограмм не возвращает какого-либо осмыс- ленного значения. 2.7.3. Опции ввода Система UNIX производит множество действий с символами, вводи- мыми пользователем. Среди прочего, система выполняет следующее: Отображает вводимые символы на экране терминала (такое отображение мы будем называть эхом). Обрабатывает символы забоя (ERASE, обычно #) и уничто- жения (KILL, обычно @). Интерпретирует CTRL+D как конец файла (EOF). Обрабатывает символы прерывания (INTR) и выхода (QUIT). Сбрасывает бит четности символа. Преобразует возврат каретки в перевод строки. Curses полностью берет на себя управление экраном терминала, поэтому его подпрограммы отключают эхо, производимое системой UNIX, и делает его самостоятельно. Временами бывает необходимо программно отключать стандартную для системы UNIX обработку символов. Для этого предназначены некоторые подпрограммы curses, например, noecho() и cbreak(). Используя такие подпрог- раммы, можно управлять интерпретацией вводимых символов. В при- веденной ниже таблице перечислены основные подпрограммы, пред- назначенные для управления режимом ввода. Если программа использует curses и вводит с терминала, она должна установить некоторые опции ввода. Это связано с тем, что при старте программы терминал может оказаться в любом из режи- мов cbreak(), raw(), nocbreak(), или noraw(). Как показано в таблице, при запуске программы гарантируется только наличие ре- жима echo(). Как правило, в интерактивной терминальной программе используют- ся комбинация режимов noecho() и cbreak(). Пусть, например, нужно отображать вводимые символы не там, где находится курсор, а в нижней строке экрана. Для этого предназначена подпрограмма noecho(). Однако, хотя noecho() и подавляет отображение вводи- мых символов, символы забоя и уничтожения обрабатываются обыч- ным образом. Для отключения этого режима используется подпрог- рамма cbreak(). ~----------------- ------------------------------------------ │ Опции │ Символы │ │ ввода │ Интерпретируются │ Не интерпретируются │ ----------------- -------------------- --------------------- │ Нормальное │ прерывание, выход, │ │ │ состояние │ сброс бита четности│ │ │ "вне curses" │ в │ │ │ │ забой, уничтожение │ │ │ │ конец файла │ │ ----------------- -------------------- --------------------- │ Нормальное │ эхо (заменяет эхо │ все остальное │ │ состояние │ системы UNIX) │ не определено │ │ "старт curses- │ │ │ │ программы" │ │ │ ----------------- -------------------- --------------------- │ cbreak () и │ прерывание, выход │ забой, уничтожение │ │ echo () │ сброс бита четности│ конец файла │ │ │ эхо │ │ ----------------- -------------------- --------------------- │ cbreak () и │ прерывание, выход │ забой, уничтожение │ │ noecho () │ сброс бита четности│ конец файла, эхо │ ----------------- -------------------- --------------------- │ nocbreak () и │ разрыв, выход │ эхо │ │ noecho () │ сброс бита четности│ │ │ │ забой, уничтожение │ │ │ │ конец файла │ │ ----------------- -------------------- --------------------- │ nocbreak () и │ См. предостережение ниже │ │ echo () │ │ │ ----------------- -------------------- --------------------- │ nl () │ в │ │ │ │ │ │ ----------------- -------------------- --------------------- │ nonl () │ │ в │ ----------------- -------------------- --------------------- │ raw () [вместо │ │ разрыв, выход │ │ cbreak ()] │ │ сброс бита четности │ ----------------- -------------------- --------------------- Предостережение Не применяйте одновременно nocbreak() и noecho(). Если установлены оба режима и Вы вызываете getch(), програм- ма при чтении каждого символа включает, а затем отклю- чает cbreak(). В зависимости от состояния драйвера тер- минала при вводе символа, это может привести к выводу нежелательных данных на экран. Помимо подпрограмм, перечисленных в таблице, для управления вводом используются следующие подпрограммы curses: noraw(), halfdelay(), nodelay(). Они обсуждаются в curses(3X). Далее подробнее описываются подпрограммы noecho(), cbreak() и связанные с ними echo() и nocbreak(). echo( ), noecho( ) СИНТАКСИС #include int echo ( ) int noecho ( ) ОПИСАНИЕ echo() устанавливает режим "эхо" - отображение символов на экране по мере их ввода. При старте программы этот режим установлен. noecho() отключает режим "эхо". Ни та, ни другая подпрограмма не возвращают полезных значений. Curses-программы могут работать неправильно, если эхо установлено одновременно с nocbreak(). См. таблицу выше и последующее предостережение. После отключения эха вводимые символы можно отображать посредством addch(). ПРИМЕР См. программы editor и show в разделе Примеры программ, работающих с curses. cbreak( ), nocbreak( ) СИНТАКСИС #include int cbreak ( ) int nocbreak ( ) ОПИСАНИЕ cbreak() включает режим "прерывание при вводе каждого символа". Программа получает каждый символ сразу после его ввода, но символы забоя, уничтожения и конца файла не обрабатываются. nocbreak() возвращает к обычному режиму "построчный ввод". Обычно этот режим установлен при запуске прог- раммы. Ни одна из подпрограмм не возвращает полезных значений. Curses-программы могут работать неправильно, если cbreak() включается и отключается в пределах одной программы, либо если используется комбинация nocbreak() и echo(). См. таблицу выше и последующее предостережение. ПРИМЕР См. программы editor и show в разделе Примеры программ, работающих с curses. 2.8. Работа с окнами Ранее, в разделе Еще об окнах и подпрограмме refresh(), объяс- нялось, что такое окна и спецокна и для чего они могут Вам по- надобиться. В этом разделе описываются подпрограммы curses, предназначенные для создания окон и спецокон и управления ими. 2.8.1. Ввод/вывод Для ввода и вывода данных в окна и спецокна используются подп- рограммы, похожие на те, которые работают с stdscr. Единствен- ная разница состоит в том, что необходимо указать окно, к кото- рому относится операция. Как правило, имена этих подпрограмм получаются путем добавления буквы w в начало названия соот- ветствующей подпрограммы для stdscr, а также имени окна в ка- честве первого параметра. Например, если нужно вывести символ c в окно mywin, addch ('c') превращается в waddch (mywin,'c'). Далее приводится список работающих с окнами подпрограмм, соот- ветствующих тем, которые были описаны в разделе Несложный ввод/ вывод. waddch (win, ch) mvwaddch (win, y, x, ch) waddstr (win, str) mvwaddstr (win, y, x, str) wprintw (win, fmt[,arg...]) mvwprintw (win, y, x, fmt[,arg...]) wmove (win, y, x) wclear (win) werase (win) wclrtoeol (win) wclrtobot (win) wrefresh (win) Как видно из описаний, эти подпрограммы отличаются от тех, ко- торые работают с stdscr, только своими именами и добавлением аргумента win. Заметьте, что у подпрограмм, чьи имена начинают- ся на mvw, аргумент win предшествует координатам y, x, хотя, судя по их именам, следовало бы ожидать обратного. См. в curses(3X) дополнительные данные об этих подпрограммах, вариан- тах подпрограмм getch(), getstr() и другую информацию, касающу- юся работы с окнами. Все подпрограммы с буквой w в имени могут использоваться и со спецокнами, за исключением wrefresh() и wnoutrefresh() (см. ни- же). Вместо них со спецокнами нужно использовать соответственно prefresh() и pnoutrefresh(). 2.8.2. Подпрограммы wnoutrefresh( ) и doupdate( ) Как было сказано ранее, подпрограмма refresh() пересылает дан- ные с stdscr на экран терминала. refresh() является макросом, который расширяется в wrefresh (stdscr), о чем также упомина- лось в разделах Что нужно программе для работы с curses и Еще об окнах и подпрограмме refresh(). Подпрограмма wrefresh() предназначена для пересылки данных из окна (будь то stdscr или созданное пользователем) на экран тер- минала; она вызывает подпрограммы wnoutrefresh() и doupdate(). Аналогичным образом, prefresh() пересылает данные из спецокна на экран, обращаясь к pnoutrefresh() и doupdate(). Пользуясь wnoutrefresh() или pnoutrefresh() (здесь мы для прос- тоты рассмотрим только первую из них) и doupdate(), Вы можете обновлять экран с большей эффективностью, чем обращаясь к wrefresh(). wrefresh() сначала вызывает wnoutrefresh(), которая копирует указанное окно в структуру данных, называемую вирту- альным экраном. Виртуальный экран содержит то, что программа собирается вывести на терминал. Вызвав wnoutrefresh(), wrefresh() затем обращается к doupdate(), которая сравнивает виртуальный экран с физическим и производит обновление послед- него. Если Вы хотите отобразить содержимое сразу нескольких окон, вызовы wrefresh() приведут к нескольким обращениям к wnoutrefresh() и doupdate(), то есть к нескольким актам вывода данных на экран. Однако, вызвав wnoutrefresh() для каждого ок- на, а затем один раз doupdate(), можно уменьшить общее коли- чество передаваемых символов и необходимого для передачи време- ни процессора. Нижеследующая программа использует doupdate() лишь однажды. #include main () { WINDOW *w1, *w2; initscr (); w1 = newwin (2, 6, 0, 3); w2 = newwin (1, 4, 5, 4); waddstr (w1, "Bulls"); wnoutrefresh (w1); waddstr (w2, "Eye"); wnoutrefresh (w2); doupdate (); endwin (); } В начале этой программы объявляются новые окна. Операторы w1 = newwin (2, 6, 0, 3); w2 = newwin (1, 4, 5, 4); объявляют два окна, которые называются w1 и w2, передавая неко- торые описания подпрограмме newwin(), которая подробнее описана ниже. Ниже показано действие wnoutrefresh() и doupdate() на эти два окна и на виртуальный и физический экраны. Экран stdscr @ (0,0) виртуальный физический initscr () ~------------ ~------------ ~----------- │_ │ │_ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ ----------- Экран stdscr @ (0,0) виртуальный физический w1 = newwin ~------------ ~------------ ~----------- (2,6,0,3) │_ │ │_ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор) │ │ │ │ │ │ │ │ │ │ │ │ │ ------------ ------------ ----------- w1 @ (0,3) ~-------- │_ │ │ │ │ │ -------- Экран stdscr @ (0,0) виртуальный физический w2 = newwin ~------------ ~------------ ~----------- (1,4,5,4) │_ │ │_ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор) │ │ │ │ │ │ │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │_ │ │_ │ │ │ │ │ -------- ------ Экран stdscr @ (0,0) виртуальный физический waddstr (w1, ~------------ ~------------ ~----------- "Bulls") │ │ │ │ │ │ │_ │ │_ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор) │ │ │ │ │ │ │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │ Bulls_ │ │_ │ │ │ │ │ -------- ------ Экран stdscr @ (0,0) виртуальный физический wnoutrefresh ~------------ ~------------ ~----------- (w1) │ │ │ │ │ │ │_ │ │ Bulls_ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор) │ │ │ │ │ │ │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │ Bulls_ │ │_ │ │ │ │ │ -------- ------ Экран stdscr @ (0,0) виртуальный физический waddstr(w2, ~------------ ~------------ ~----------- "Eye") │ │ │ │ │ │ │- │ │ Bulls_ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор) │ │ │ │ │ │ │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │ Bulls_ │ │ Eye_ │ │ │ │ │ -------- ------ Экран stdscr @ (0,0) виртуальный физический wnoutrefresh ~------------ ~------------ ~----------- (w2) │_ │ │ Bulls │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ (мусор) │ │ │ │ Eye_ │ │ │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │ Bulls_ │ │ Eye_ │ │ │ │ │ -------- ------ Экран stdscr @ (0,0) виртуальный физический doupdate () ~------------ ~------------ ~----------- │_ │ │ Bulls │ │ Bulls │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Eye_ │ │ Eye_ │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │ Bulls_ │ │ Eye_ │ │ │ │ │ -------- ------ Экран stdscr @ (0,0) виртуальный физический endwin() ~------------ ~------------ ~----------- │_ │ │ Bulls │ │ Bulls │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Eye_ │ │_ Eye │ ------------ ------------ ----------- w1 @ (0,3) w2 @ (5,4) ~-------- ~------ │ Bulls_ │ │ Eye_ │ │ │ │ │ -------- ------ 2.8.3. Новые окна Далее следуют описания подпрограмм newwin() и subwin(), которые применяются для создания новых окон. В curses(3X) описаны подп- рограммы newpad() и subpad() для создания новых спецокон. newwin( ) СИНТАКСИС #include WINDOW *newwin (nlines, ncols, begin_y, begin_x) int nlines, ncols, begin_y, begin_x; ОПИСАНИЕ newwin() возвращает указатель на новое окно с новой об- ластью данных. Аргументы nlines и ncols задают размер нового окна. Аргументы begin_y и begin_x задают координаты точки на экране [относительно (0, 0)], на которую отображается левый верхний угол окна. ПРИМЕР См. пример программы, работающей с двумя окнами и рису- нок выше. См. также программу window в разделе Примеры программ, работающих с curses. subwin( ) СИНТАКСИС #include WINDOW *subwin (orig, nlines, ncols, begin_y, begin_x) WINDOW *orig; int nlines, ncols, begin_y, begin_x; ОПИСАНИЕ subwin() возвращает указатель на новое окно, которое является частью ранее определенного окна, orig. Аргументы nlines и ncols задают размер нового окна. Аргументы begin_y и begin_x задают координаты левого верхнего угла окна на экране терминала. Как подокна, так и сами окна могут перекрываться. Предостережение На момент выпуска данного руководства не работало опре- деление подокон у подокон. ПРИМЕР #include main() { WINDOW *sub; initscr (); box (stdscr, 'w', 'w'); /* О box() см. curses(3X) */ mvaddstr (stdscr, 7, 10, "------- это 10,10"); mvaddch (stdscr, 8, 10, '|'); mvaddch (stdscr, 9, 10, 'v'); sub = subwin (stdscr, 10, 20, 10, 10); box (sub, 's', 's'); wnoutrefresh (stdscr); wrefresh (sub); endwin (); } Эта программа заполняет границу stdscr (то есть края экрана терминала) символами w, а границу подокна sub - символами s. Другой пример см. в программе window раздела Примеры программ, работающих с curses. 2.9. Прочие возможности пакета curses Умея пользоваться основными подпрограммами curses для ввода/вы- вода на экран и управления окнами, можно создавать терминальные программы, удовлетворяющие потребности большинства пользовате- лей. Вместе с тем, в curses имеются подпрограммы, которые поз- воляют делать больше, чем просто вводить и выводить символы и работать с окнами. На следующих нескольких страницах кратко описываются некоторые из этих подпрограмм, которые помогут Вам выводить простые графические изображения, использовать програм- мируемые метки терминала и обращаться из одной программы к нес- кольким терминалам. Прежде, чем использовать эти возможности, следует освоить подп- рограммы ввода/вывода и управления окнами, которые были расс- мотрены выше в этой главе, а также в руководстве curses(3X). Предостережение Подпрограммы, описываемые далее в разделах Линии на эк- ране и прочая графика и Использование программируемых меток, имеются только в системе UNIX V версия 3.1. Программа, работающая с ними, возможно, не сможет вы- полняться на более ранних версиях системы UNIX. Для ра- боты с этими подпрограммами необходимо иметь систему UNIX V версия 3.1 и соответствующую версию библиотеки curses. 2.9.1. Линии на экране и прочая графика Многие терминалы поддерживают альтернативную кодировку для простых графических изображений (графические символы, или гли- фы). Этим набором символов можно пользоваться и в программах, работающих с curses. В curses используются те же названия гли- фов, что и в графическом наборе символов VT100. Для работы с альтернативной кодировкой в curses-программе, не- обходимо передать подпрограмме waddch() (или подобной) последо- вательность символов, чьи имена начинаются с ACS_. Например, ACS_ULCORNER - это переменная для символа, изображающего левый верхний угол. Если терминал поддерживает этот символ, то в ACS_ULCORNER будет результат операции ИЛИ (|) соответствующего значения и битной маски A_ALTCHARSET. Если такого графического символа нет, используется символ из стандартного набора ASCII, аппроксимирующий требуемое изображение. Например, для аппрокси- мации ACS_HLINE, горизонтальной черты, используется - (знак ми- нус). Если близкая аппроксимация невозможна, используется +, знак плюс. Все стандартные имена ACS_ и аппроксимирующие их символы перечислены в curses(3X). Мы приведем часть текста программы, которая использует графи- ческие символы. В примере вызывается подпрограмма box(), входя- щая в пакет curses, для того, чтобы окружить меню на экране рамкой. По умолчанию box() использует графические символы, но по выбору пользователя может рисовать знаками | и - [см. curses(3X)]. Если меню, изображенное в рамке, имеет продолжение вверх или вниз за пределы рамки, то для указания этого выводят- ся стрелки соответственно вверх или вниз (ACS_UARROW и ACS_DARROW). box (menuwin, ACS_VLINE, ACS_HLINE); ... /* Вывод стрелок вверх/вниз */ wmove (menuwin, maxy, maxx - 5); /* вывод стрелки вверх или горизонтальной черты */ if (moreabove) waddch(menuwin, ASC_UARROW); else waddch (menuwin, ACS_HLINE); /* вывод стрелки вниз или горизонтальной черты */ if (morebelow) waddch (menuwin, ASC_DARROW); else waddch (menuwin, ACS_HLINE); Приведем еще один пример. Поскольку символ для стрелки вниз (например, строчная v) не очень выделяется на экране, если на нем много строчных букв, этот символ можно заменить на пропис- ную V. if ( ! (ACS_DARROW & A_ALTCHARSET)) ACS_DARROW = 'V'; Более подробную информацию см. в Справочнике программиста, curses(3X). 2.9.2. Использование программируемых меток Большинство терминалов поддерживает программируемые метки в нижней части своего экрана. На клавиатуре этим меткам обычно соответствуют функциональные клавиши. Меток обычно восемь, каж- дая длиной в восемь знаков и высотой в одну-две строки. Библиотека curses содержит подпрограммы, поддерживающие единую модель из восьми программируемых меток. Если терминал их не имеет, нижняя строка его экрана превращается в область програм- мируемых меток. Для использования в curses-программах таких ме- ток, нет необходимости иметь на клавиатуре соответствующие им функциональные клавиши. Далее кратко описываются большинство подпрограмм curses, приме- няющихся при работе с программируемыми метками: slk_init(), slk_set(), slk_refresh(), slk_noutrefresh(), slk_clear() и slk_restore(). Если Вы пользуетесь программируемыми метками в cursesпрограмме, Вам необходимо перед initscr() вызвать подпрограмму slk_init(). При этом устанавливается внутренний флаг, указывающий initscr(), что программируемые метки используются. Если initscr() обнаружит, что программируемые метки отсутствуют на терминале, либо их меньше восьми, либо их длина менее восьми символов, она удаляет нижнюю строку stdscr, чтобы использовать ее для размещения программируемых меток. При этом размер stdscr и значение переменной LINES уменьшается на единицу. Разумно на- писанная программа, использующая переменные LINES и COLS, будет выполняться правильно, как если бы этой строки вообще не было бы на экране. slk_init() имеет один аргумент, определяющий способ размещения меток на экране в случае использования для этого строки из stdscr. Выбор возможен между размещением группами 3-2-3, как это принято на терминалах AT&T, либо 4-4, как на терминалах Хьюлетт-Паккард. Подпрограммы curses определяют положение и размер меток в соответствии с указанным способом, причем макси- мально возможная длина метки - восемь символов. Подпрограмма slk_set() имеет три аргумента: номер метки (1-8), текст, помещаемый в метку (не более восьми символов), выравни- вание этого текста в поле метки (0 - выравнивание влево, 1 - центрирование, 2 - выравнивание вправо). Подпрограмма slk_noutrefresh() подобна wnoutrefresh() тем, что она обновляет содержимое внутреннего образа экрана, но не выво- дит данные на реальный экран. Поскольку после ее вызова обычно вызывают wrefresh(), именно slk_noutrefresh(), как правило, ис- пользуется для вывода меток на экран терминала. Подобно тому, как wrefresh() эквивалентна последовательным вы- зовам wnoutrefresh() и doupdate(), подпрограмма slk_refresh() эквивалентна последовательным вызовам slk_noutrefresh() и doupdate(). Чтобы программируемые метки не мешали работе в среде shell, пе- ред обращением к endwin() можно вызвать slk_clear(), которая удаляет программируемые метки с экрана и вызывает doupdate(). Программируемые метки можно восстановить на экране функцией slk_restore(). Подробнее о подпрограммах для работы с програм- мируемыми метками см. curses(3X). 2.9.3. Работа с несколькими терминалами сразу Curses-программа может выводить данные сразу на несколько тер- миналов. Это может оказаться полезным при разработке программ, которые представляют собой один процесс и обращаются к одной базе данных, например, игр для нескольких участников. Писать программы, работающие с несколькими терминалами, - дело нелегкое, и подпрограммы curses не решают всех возникающих при этом проблем. Например, сами программы, а не подпрограммы биб- лиотеки, должны определять имена файлов для терминалов и типы этих терминалов. Стандартный способ с использованием переменной окружения $TERM не срабатывает, поскольку каждый процесс может знать только о своем собственном окружении. Другая проблема возникает, когда несколько программ пытаются читать данные с одного и того же терминала. Следует избегать ситуации, когда программы перехватывают данные друг у друга. В то же время, программа, которая пытается занять дополнительный терминал, не должна просто прерывать программу, которая уже ра- ботает на нем. (Обычно это также неприемлемо из соображений бе- зопасности, но бывают случаи, когда это может оказаться полез- ным, например, если терминал не используется, либо для разра- ботки программ связи между терминалами). Типичным решением проблемы является запуск каждым пользователем программы, уве- домляющей главную программу о желании с ней работать и передаю- щей главной программе идентификатор процесса уведомляющей прог- раммы, имя линии и тип используемого терминала. Затем уведомля- ющая программа переходит в неактивное состояние до окончания работы главной. Перед своим завершением, главная программа ак- тивизирует все уведомляющие программы, после чего все они за- вершаются. Концепция работы curses со многими терминалами заключается в поддержании текущего терминала. Все обращения к подпрограммам относятся к этому текущему терминалу. Главная программа должна описать каждый терминал и сохранить ссылку на него в своих пе- ременных. Когда программе нужно работать с некоторым термина- лом, ей следует установить его в качестве текущего и вызывать обычные подпрограммы curses. Ссылки на терминал в curses-программе имеют тип *SCREEN. Новый терминал инициализируется путем обращения к newterm (type, outfd, infd). newterm возвращает указатель на новый терминал, type - это цепочка символов, содержащая тип используемого тер- минала. outfd является указателем на файл [*FILE, stdio(3S)], который используется для вывода на терминал, а infd указывает на файл для ввода с терминала. Вызов newterm() заменяет обычное обращение к initscr(), которое расширяется в newterm (getenv ("TERM"), stdout, stdin). Для изменения текущего терминала необходимо вызвать set_term (sp), где sp указывает на терминал, который должен стать теку- щим. set_term() возвращает указатель на терминал, который был текущим на момент ее вызова. Важно понять, что каждый терминал будет иметь свои собственные режимы работы и окна. Каждый терминал должен быть проинициали- зирован соответствующим вызовом newterm(). Для каждого термина- ла отдельно должны быть установлены режимы работы, например, cbreak() или noecho(). Равным образом для каждого терминала от- дельно должны вызываться endwin() и refresh(). Ниже изображен типичный сценарий рассылки сообщения на несколько терминалов. for (i=0; i #include . . . setupterm ((char*) 0, 1, (int*) 0); . . . putp (clear_screen); . . . reset_shell_mode (); exit (0); Файлы и нужны, поскольку они содержат опре- деления текстовых объектов, чисел и флагов, которые используют- ся подпрограммами terminfo. setupterm() занимается инициализа- цией. Передача ей значений (char*) 0, 1 и (int*) 0 обеспечивает установку разумных режимов. Если setupterm() не может распоз- нать тип используемого терминала, она выводит сообщение об ошибке и завершается. reset_shell_mode() делает примерно то же, что и endwin(), и должна вызываться перед завершением terminfo- программы. При вызове setupterm() определяются значения глобальных пере- менных, например clear_screen. Их значения могут быть выведены на экран входящими в terminfo подпрограммами putp() или tputs(), что обеспечивает пользователю дополнительные возмож- ности по контролю терминала. Такую строку не следует выводить подпрограммой printf(3S) из библиотеки языка C, поскольку в ней содержится информация об использовании символов-заполнителей. Программа, пытающаяся вывести ее таким образом, может завер- шиться аварийно, если терминал требует использования заполните- лей, или если он использует протокол xon/xoff. На уровне terminfo подпрограммы более высокого уровня, напри- мер, addch() и getch(), недоступны. Вам придется самостоятельно решать проблему вывода на экран. Список характеристик и их опи- саний см. в terminfo(4), список подпрограмм terminfo см. в curses(3X). 3.2. Компиляция и выполнение программ, которые используют terminfo Общий формат командной строки для компиляции, как и особенности запуска программ, использующих terminfo, те же, что и для любой другой curses-программы. Подробности см. в разделах Компиляция программы, которая использует curses и Выполнение программы, которая использует curses. 3.3. Пример программы, работающей с terminfo Программа termhl - это простой пример использования подпрограмм terminfo. Она является вариантом программы highlight (см. раз- дел Примеры программ, работающих с curses), в котором не ис- пользуются возможности curses более высокого уровня. Программу termhl можно использовать в качестве фильтра, вставляющего пос- ледовательности символов, необходимые для включения режимов подсветки и подчеркивания и для отключения всех атрибутов. /* Версия программы highlight уровня terminfo */ #include #include int ulmode = 0; /* Режим подчеркивания */ main (argc, argv) int argc; char **argv; { FILE *fd; int c, c2; int outch(); if (argc > 2) { fprintf (stderr, "Откуда: termhl [file]\n"); exit(1) } if (argc == 2) { fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit(2); } } else { fd = stdin; } setupterm ((char*) 0, 1, (int*) 0); for (;;) { c = getc (fd); if (c == EOF) break (); if (c == '\') { c2 = getc (fd); switch (c2) { case 'B': tputs (enter_bold_mode, 1, outch); continue; case 'U': tputs (enter_underline_mode, 1, outch); ulmode = 1; continue; case 'N': tputs (exit_attribute_mode, 1, outch); ulmode = 0; continue; } putch (c); putch (c2); } else putch (c); } fclose (fd); fflush (stdout); resetterm (); exit (0); } /* Функция putch подобна putchar, */ /* но проверяет необходимость подчеркивания */ putch (c) int c; { outch (c); if (ulmode && underline_char) { outch ('\b'); tputs (underlihe_char, 1, outch); } } /* outchar передается tputs в качестве параметра */ /* как функция, которую нужно вызывать вместо putchar */ outch (c) int c; { putchar (c); } Познакомимся поближе с подпрограммами terminfo на примере ис- пользования функции tputs (cap, affcnt, outc) в этой программе. tputs() добавляет символы-заполнители. Некоторые терминалы име- ют возможность задерживать вывод. Их описания в базе данных terminfo, быть может, содержат цепочки, подобные $<20>, что оз- начает задержку на 20 миллисекунд (см. ниже раздел Указание ха- рактеристик терминала). tputs генерирует достаточное для за- держки на это время количество символов-заполнителей. tputs() имеет три параметра. Первый является цепочкой символов, определяющей характеристику (атрибут вывода), которую необходи- мо реализовать. Второй - количество затрагиваемых этим строк. (Некоторые запросы требуют добавления символов-заполнителей в количестве, зависящем от этого параметра. Например, запрос на вставку строки, insert_line, может привести к копированию всех строк ниже текущей, для чего нужно время, пропорциональное ко- личеству копируемых строк. Если ни одна строка не затрагивает- ся, в качестве affcnt используется 1. Единица вместо нуля бе- рется для надежности, так как affcnt умножается на некоторый интервал времени, а умножение чего-либо на 0 дает 0). Третий параметр - это подпрограмма, вызываемая для вывода каждого сим- вола. Для понимания желательности использования подпрограмм уровня curses вместо подпрограмм уровня terminfo (там, где это возмож- но), полезно обратить внимание на проверку значения underli- ne_char в примере. Некоторые терминалы имеют управляющий сим- вол, указывающий начало подчеркивания, и еще один символ, ука- зывающий конец подчеркивания. Другим необходимо указание кода подчеркивания при выводе каждого символа. Программа termhl хра- нит значение этого режима, и, если необходимо, выводит underli- ne_char для подчеркивания очередного символа. Из-за наличия та- кого рода мелких деталей, с которыми программы уровня terminfo должны разбираться самостоятельно, и предпочтительнее работать на уровне curses. Программа termhl была написана для иллюстрации применения подп- рограмм terminfo, поэтому она получилась сложнее, чем могла бы быть. Вместо непосредственного вывода enter_bold_mode, enter_underline_mode и exit_attribute_mode можно было бы ис- пользовать подпрограмму vidattr [см. curses(3X)]. Фактически, в последнем случае программа была бы более надежна, поскольку су- ществует несколько способов изменения атрибутов вывода. 4. ИСПОЛЬЗОВАНИЕ БАЗЫ ДАННЫХ TERMINFO База данных terminfo содержит описания многих терминалов, с ко- торыми могут работать как подпрограммы curses, так и некоторые средства системы UNIX, например vi(1). Каждое описание термина- ла представляет собой скомпилированный файл, содержащий имена, под которыми этот терминал известен, и группу разделенных запя- тыми полей, описывающих возможные действия и характеристики терминала. В этом разделе описываются база данных terminfo, средства для работы с ней и ее связь с библиотекой curses. 4.1. Создание описания терминала База данных terminfo содержит описания многих наиболее часто применяющихся терминалов. Может, однако, оказаться, что Вы по- желаете написать curses-программу, предназначенную для выполне- ния на терминале, который еще не описан. В этом случае Вам при- дется создать его описание. В общем случае для генерации описания терминала необходимо: Указать известные Вам названия терминала. Выяснить и перечислить характеристики терминала. Компилировать описание терминала. Тестировать это описание. При необходимости вернуться ко второму шагу, уточнить характеристики и повторить последующие шаги. Иногда генерировать и тестировать описание терминала легче по частям. Такие последовательные тесты помогут выявить Ваши сла- бые стороны в работе по описанию терминалов. Кроме того, можно облегчить генерацию, корректируя уже существующее описание по- хожего терминала (не следует забывать девиз системы UNIX: опи- раться на работу других). Далее мы опишем каждый шаг построения описания некоторого ус- ловного терминала myterm. 4.1.1. Название терминала Название терминала - это информация, с которой начинается его описание в terminfo. В действительности, название может вклю- чать несколько имен, разделенных вертикальной чертой (|). Пер- вое имя должно представлять собой наиболее часто используемое сокращение. Последним должно указываться имя, полностью иденти- фицирующее терминал. Обычно, это название, которым пользуется фирма-производитель. Все имена между первым и последним должны быть известными синонимами имени терминала. Все имена, кроме последнего, записываются строчными буквами и не должны содер- жать пробелов. Естественно, последнее имя как можно более точно воспроизводит название фирмы-производителя. Приведем название из описания терминала AT&T Teletype 5420: 5420|att5420|AT&T Teletype 5420, Обратите внимание, что в начале идет наиболее часто используе- мое сокращение, а в конце - официальное название терминала. За- метьте также, что название оканчивается запятой. Приведем название для нашего условного терминала myterm: myterm:mytm:mine:fancy:terminal:My FANCY Terminal, Имена терминалов должны удовлетворять обычным соглашениям об именах. Эти соглашения касаются, в частности, начального имени, например, 5425 или myterm. Это имя не должно содержать странных символов (например, минусов), которые могут не быть распознаны как часть синонима имени терминала. Возможные режимы оборудова- ния и предпочтения пользователя указываются путем добавления имени и "индикатора режима" в конце имени. Например, для нашего фиктивного терминала режим с большим числом символов в строке (что обозначается через -w) будет записан в виде myterm-w. Дру- гие индикаторы подробно обсуждаются в term(5). 4.1.2. Выяснение характеристик терминала После создания цепочки имен, для правильного описания нового терминала следует выяснить значения его характеристик. Для это- го Вам надлежит сделать следующее: Изучить руководство по работе с Вашим терминалом. В нем должна содержаться информация о возможностях терминала и соответствующих им последовательностях управляющих символов. Проверить, что передается при нажатии клавиш Вашего терминала, если этой информации нет в руководстве. Это можно сделать одним из следующих способов. Введите stty -echo; cat -vu нажмите тестируемые клавиши, например, стрелку вправо. CTRL+D stty echo или cat >dev/null нажмите клавиши тестируемой управляющей последовательности, например, \E[H. CTRL+D Первая строка для каждого из способов тестирования го- товит терминал к выполнению теста. CTRL+D помогает вер- нуть терминал в нормальный режим. См. terminfo(4), где перечислены все названия характе- ристик терминалов, которые используются для его описа- ния. Подробности излагаются в следующем разделе, Указа- ние характеристик терминала. 4.1.3. Указание характеристик терминала После того, как Вы уяснили характеристики терминала, их необхо- димо указать в его описании. Описание имеет вид последователь- ности разделенных запятыми полей, каждое из которых содержит принятое в terminfo сокращение названия характеристики и, в не- которых случаях, ее значение для данного терминала. Например, для указания способности терминала подавать звуковой сигнал ис- пользуется сокращение bel. На большинстве терминалов инструкци- ей, передаваемой для подачи звукового сигнала, является CTRL+G. Таким образом, характеристика возможности подачи звукового сиг- нала изображается в описании терминала в виде bel=^G,. Описание характеристики может занимать несколько строк. В отли- чие от первой строки, строки продолжения должны начинаться с символа табуляции или пробела. В описание можно включать ком- ментарии, которые выделяются символом # в начале строки. Полный список характеристик, которые можно указывать в описании терминала см. в terminfo(4). В этом списке приводятся название характеристики, сокращенное название, которое используется в базе данных, двухбуквенный код, соответствующий этой характе- ристике в устаревшей базе данных termcap, а также краткое опи- сание характеристики. Сокращение, которое необходимо указать в описании терминала см. в колонке "Сокращение". примечание Чтобы curses-программа могла работать с терминалом, его описание в базе данных terminfo должно включать, как минимум, характеристики, касающиеся перемещения курсора во всех четырех направлениях и очистки экрана. Значение характеристики может задаваться клавишей (например, CTRL+G), числом, или цепочкой символов, определяющей последова- тельность операций, которые нужно выполнить, чтобы добиться не- обходимого эффекта. Для указания типа значения после названия характеристики в описании терминала могут использоваться неко- торые специальные символы, а именно: # Указывает, что далее следует числовое значение ха- рактеристики. Например, количество столбцов может определяться в виде cols#80. = Указывает, что значение характеристики является цепочкой символов. Эта цепочка в действительности может быть последовательностью команд, требующих от терминала выполнения определенных действий. В таких цепочках могут использоваться некоторые спе- циальные символы, а именно: ^ Обозначает управляющий символ. Например, звуковой сигнал подается передачей CTRL+G, что записывается в виде ^G. \e или \E В сопровождении еще каких-либо символов обозначают escape-последовательности. Напри- мер, \EC передается на терминал в виде ESC C. \n Обозначает перевод строки. \l Обозначает переход к новой строке. \r Обозначает возврат каретки \t Обозначает горизонтальную табуляцию. \b Обозначает возврат на символ назад. \f Обозначает переход к новой странице. \s Обозначает пробел. \nnn Обозначает символ с восьмеричным кодом nnn, где nnn может иметь от одной до трех цифр. $< > Эти символы используются для обозначения за- держки в миллисекундах. Длительность задерж- ки заключается в угловые скобки (<>). Она может быть целым или десятичным числом с од- ной цифрой после точки. После числа может следовать звездочка (*). Звездочка указыва- ет, что длительность задержки пропорциональ- на количеству вовлеченных в операцию строк. Например, задержка в 20 миллисекунд на стро- ку указывается в виде $<20*>. Более подроб- ную информацию о задержке и символах-запол- нителях см. в terminfo(4). Иногда бывает необходимо отключить некоторую характеристику в описании терминала. Это можно сделать, поместив точку (.) перед сокращенным названием характеристики, тогда она воспринимается как комментарий. Например, чтобы закомментировать характеристи- ку звукового сигнала, необходимо записать ее так: .bel=^G, Теперь, когда мы узнали, как описываются характеристики терми- нала, опишем их для myterm. Мы рассмотрим характеристики экра- на, клавиатуры, основные и параметрические характеристики. 4.1.3.1. Основные характеристики Следующие характеристики являются общими для большинства терми- налов: звуковой сигнал, колонки и строки на экране, возможность надпечатывания символов. Пусть наш гипотетический терминал име- ет эти и некоторые другие перечисленные ниже возможности. За- метьте, что в скобках после каждой характеристики следует ее сокращение для terminfo: Автоматический переход в начало следующей строки, когда курсор достигает правой границы (am). Способность подавать звуковой сигнал. Командой подачи сигнала является CTRL+G (bel). Ширина экрана - 80 колонок. (cols). Используется протокол xon/xoff (xon). Объединив последовательность имен (см. раздел Название термина- ла) и описание упомянутых характеристик, мы получим следующее описание для базы данных terminfo: myterm:mytm:mine:fancy:terminal:My FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, 4.1.3.2. Характеристики экрана Эти характеристики управляют содержимым экрана. Пусть терминал myterm имеет перечисленные ниже характеристики. Опять-таки, в скобках после описания характеристики дается ее сокращение. Возврату каретки соответствует CTRL+M (cr). Перемещению курсора на одну строку вверх соответствует CTRL+K (cuu1). Перемещению курсора на одну строку вниз соответствует CTRL+J (cud1). Перемещению курсора на одну строку влево соответствует CTRL+H (cub1). Перемещению курсора на одну строку вправо соответствует CTRL+L (cuf1). Переходу к инвертированному отображению соответствует ESC D (smso). Отмене инвертированного отображения соответствует ESC Z (rmso). Последовательностью, очищающей до конца строки, являет- ся ESC K, и этот процесс должен сопровождаться задерж- кой в 3 мс. (el). Производится роллирование экрана терминала при получе- нии перевода строки в конце экрана (ind). Описание терминала myterm, включающее эти характеристики, будет выглядеть так: myterm|mytm|mine|fancy|terminal|My FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, cr=^M, cuu1=^K, cud1=^G, cub1=^H, cuf1=^L, smso=\ED, rmso=\EZ, el=\EK$<3>, ind=\n, 4.1.3.3. Характеристики клавиатуры Эти характеристики представляют собой последовательности симво- лов, генерируемые при нажатии клавиш на клавиатуре терминала. Большинство терминалов имеет ряд специальных клавиш, по меньшей мере, стрелки и клавиши забоя. Пусть наш гипотетический терми- нал имеет несколько таких клавиш, которые генерируют следующие последовательности: Забой - CTRL+H (kbs). Стрелка вверх - ESC [A (kcuu1). Стрелка вниз - ESC [B (kcud1). Стрелка вправо - ESC [C (kcuf1). Стрелка влево - ESC [D (kcub1). Клавиша HOME - ESC [H (khome). Добавим эту информацию к нашему описанию: myterm|mytm|mine|fancy|terminal|My FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, cr=^M, cuu1=^K, cud1=^G, cub1=^H, cuf1=^L, smso=\ED, rmso=\EZ, el=\EK$<3>, ind=\n, kbs=^H, kcuu1=\E[A, kcud1=\E[B, kcuf1=\E[C, kcub1=\E[D, khome=\E[H, 4.1.3.4. Параметризованные цепочки Характеристики, значения которых задаются параметризованными цепочками, могут иметь параметры. Например, таковы характерис- тики, описывающие позиционирование курсора на экране, или вклю- чение комбинации режимов отображения. Для позиционирования кур- сора используется характеристика cup, при этом передаются два параметра: номер строки и номер столбца. В случае таких харак- теристик, как cup или sgr (установка атрибутов), параметры пе- редаются через входящую в terminfo подпрограмму tparm(). Аргументы параметрических характеристик обрабатываются согласно включенным в описания этих характеристик специальным последова- тельностям символов, которые начинаются с %. Эти последователь- ности похожи на те, которые используются в printf(3S). Кроме того, данные можно записать в стек и производить над ними опе- рации, которые записываются в виде, близком к обратной польской записи. Как мы упомянули, cup имеет два аргумента: номер строки и номер столбца. sgr имеет девять аргументов, по одному на каж- дый из атрибутов отображения. Список аргументов sgr и порядок их следования, а также примеры см. в terminfo(4). Пусть на нашем терминале курсор позиционируется последователь- ностью, состоящей из символов ESC [, за которыми следуют номера строки и столбца, разделенные точкой с запятой, а в конце идет H. Координаты задаются относительно 1, а не нуля. Так, чтобы передвинуть курсор в позицию (5, 18) относительно (0, 0), необ- ходимо передать последовательность \E[6;19H. Целочисленные аргументы записываются в стек последовательностью %p, за которой следует номер аргумента, например, %p2 для запи- си в стек второго аргумента. %i - это краткая запись для увели- чения первых двух аргументов. Для вывода числа из вершины стека в десятичном виде используется %d, как и в printf. Последова- тельность cup нашего терминала строится следующим образом: ~------ ------------------------------------------------- │ cup= │ Пояснения │ │ \E[ │ вывести ESC [ │ │ %i │ увеличить оба аргумента │ │ %p1 │ записать первый аргумент (номер строки) в стек │ │ %d │ вывести номер строки в десятичном виде │ │ ; │ вывести точку с запятой │ │ %p2 │ записать второй аргумент (номер столбца в стек) │ │ %d │ вывести номер столбца в десятичном виде │ │ H │ вывести завершающий цепочку символ H │ ------ ------------------------------------------------- или cup=\E[%i%p1%d;%p2%dH, Добавив это к нашему описанию, получим: myterm|mytm|mine|fancy|terminal|My FANCY Terminal, am, bel=^G, cols#80, lines#30, xon, cr=^M, cuu1=^K, cud1=^G, cub1=^H, cuf1=^L, smso=\ED, rmso=\EZ, el=\EK$<3>, ind=\n, kbs=^H, kcuu1=\E[A, kcud1=\E[B, kcuf1=\E[C, kcub1=\E[D, khome=\E[H, cup=\E[%i%p1%d;%p2%dH, Подробнее о параметризованных цепочках см. terminfo(4). 4.1.4. Компиляция описания Описания терминалов в базе данных terminfo переводятся из ис- ходного в компилированный формат компилятором tic(1M). Исходный файл с описанием обычно имеет расширение .ti. Напри- мер, описание myterm будет называться myterm.ti. Скомпилирован- ное описание myterm обычно помещается в /usr/lib/terminfo/m/ myterm, так как название терминала начинается на m. myterm бу- дет также иметь синонимы, например, /f/fancy. Если перед компи- ляцией установлено и помещено в окружение значение переменной $TERMINFO, скомпилированное описание будет помещено в каталог $TERMINFO. В этом случае все программы будут искать описание терминала сначала в каталоге $TERMINFO, а затем уже в подразу- меваемом каталоге /usr/lib/terminfo. Общий формат командной строки для запуска компилятора tic таков: tic [-v[число]] [-c] файл Опция -v требует вывода трассировочной информации о ходе компи- ляции файла. Опция -c задает поиск ошибок в описании, она может использоваться одновременно с -v. Если нужно скомпилировать сразу несколько файлов, следует прежде воспользоваться командой cat(1) для их объединения. Приведенная ниже командная строка задает компиляцию описания terminfo для нашего гипотетического терминала: tic -v myterm.ti # в ходе компиляции выдается трассировочная информация Подробную информацию о компиляторе см. в Справочнике админист- ратора, tic(1M). 4.1.5. Тестирование описания Рассмотрим три способа тестирования описания терминала. Во-пер- вых, можно установить значение переменной окружения $TERMINFO равным имени каталога, содержащего это описание. Если программы выполняются на новом терминале так же, как они делали это на других терминалах, новое описание пригодно для использования. Во-вторых, можно проверить правильность заполнения экрана при вставке строк, для чего закомментировать характеристику xon в описании терминала и редактировать при помощи vi(1) большой файл (не менее ста строк) при скорости передачи 9600 бод, если это возможно. Удалите около 15 строк в середине экрана и быстро нажмите несколько раз u (отменить операцию). Если содержимое экрана портится, то, вероятно, нужно увеличить время задержки. Примерно такой же тест можно использовать для проверки вставки символа. В-третьих, можно воспользоваться командой tput(1). Эта команда выводит значение характеристики - числовое или текстовое, в за- висимости от ее типа. Если характеристика булева, tput ничего не выводит, но возвращает код завершения (0, если истина, 1, если ложь). Общий формат команды tput таков: tput [-T тип] характеристика Опция -Tтип задает тип терминала, о котором вы запрашиваете ин- формацию. Обычно эту опцию указывать не требуется, так как по умолчанию в качестве имени терминала берется значение перемен- ной окружения $TERM. Поле характеристика указывает, значение какой характеристики надо извлечь из базы данных terminfo. Следующая командная строка показывает, как выводится на экран последовательность для очистки экрана: tput clear # экран терминала очищается Следу ющая командная строка показыват, как отобразить на терми- нале количество колонок, име ющихся на го экране: tput cols # здесь появится количество колонок Более подробную информацию о команде tput и выводимых ей сооб- щениях см. в Справочнике пользователя, tput(1). 4.2. Печать и сравнение описаний в базе данных terminfo Иногда бывает необходимо сравнить описания двух терминалов или просмотреть описание, не обращаясь к каталогу, содержащему ис- ходные тексты описаний terminfo. В обоих случаях можно восполь- зоваться командой infocmp(1M). Сравнить два описания одного и того же терминала можно, например, так: mkdir /tmp/old /tmp/new TERMINFO=/tmp/old tic old5420.ti TERMINFO=/tmp/new tic new5420.ti infocmp -A /tmp/old -B /tmp/new -d 5420 5420 Эта последовательность команд сравнивает старое и новое описа- ния терминала 5420. Чтобы получить исходный текст описания terminfo для 5420, вве- дите infocmp -I 5420 4.3. Преобразование termcap-описания в terminfo-описание ПРЕДОСТЕРЕЖЕНИЕ База данных terminfo разработана в качестве замены базы данных termcap. Такой переход нельзя выполнить мгновен- но, поскольку с использованием termcap (и для работы с реход от termcap к terminfo требует некоторого опыта работы с обеими базами данных. С описаниями в этих ба- зах данных необходимо обращаться весьма осторожно, пос- кольку от них зависит правильное функционирование Ваше- го терминала. Команда captoinfo(1M) преобразует описание в формате termcap(4) в описание в формате terminfo(4). captoinfo получает файл в формате termcap и выводит эквивалентное описание в формате terminfo на стандартный вывод. Например, командная строка captoinfo /etc/termcap преобразует файл /etc/termcap в исходный текст в формате terminfo, с сохранением комментариев и другой информации, не относящейся собственно к описанию, если она присутствует. Ко- мандная строка captoinfo ищет описание текущего терминала в базе данных termcap (они за- даются значениями переменных окружения $TERM и $TERMCAP соот- ветственно) и преобразует его в формат terminfo. Если нужны описания терминала как в формате termcap, так и в формате terminfo, можно хранить только описание terminfo и ис- пользовать infocmp -C для получения описания в формате termcap. Если Вы компилировали программы командой cc(1) с опциями -ltermcap или -ltermlib, они будут работоспособны и в дальней- шем. Однако, вместо этих опций следует использовать -lcurses. 5. ПРИМЕРЫ ПРОГРАММ, РАБОТАЮЩИХ С CURSES Далее приводятся примеры, демонстрирующие использование подп- рограмм curses. 5.1. Программа editor Здесь подпрограммы curses используются для создания редактора текстов. Для простоты программа editor хранит буфер в stdscr; ясно, что в настоящем редакторе следовало бы иметь отдельную структуру для буфера. В этой программе есть и другие упрощения: не предусматривается редактирование файлов, содержащих больше строк, чем помещается на экране, либо со строками, длина кото- рых больше длины строки на экране; считается, что файл не со- держит управляющих символов. Об этой программе стоит сказать следующее. Во-первых, она ис- пользует подпрограммы move(), mvaddstr(), flash(), wnoutrefresh(), clrtoeol(). Эти подпрограммы рассматривались в разделе Использование подпрограмм пакета curses. Во-вторых, в ней применяются некоторые подпрограммы curses, о которых мы ранее не упоминали. Например, функция, осуществляю- щая запись файла, обращается к подпрограмме mvinch(), которая возвращает символ, находящийся в указанной позиции окна. Струк- тура данных, используемая для записи файла, не содержит инфор- мации о длине строк и их количестве в файле, поэтому при записи уничтожаются хвостовые пробелы. Программа использует также подпрограммы insch(), delch(), insertln(), deleteln(), которые вставляют или удаляют символ или строку. Информацию об этих подпрограммах см. в curses(3X). В-третьих, этот редактор воспринимает в качестве команд как специальные клавиши, так и символы ASCII. С одной стороны, для новичков более удобен в освоении редактор, использующий специ- альные клавиши. Им легче использовать стрелки для перемещения курсора, чем запомнить, что букве h соответствует движение вле- во, j - вниз, k - вверх, а l - вправо. С другой стороны, опыт- ные пользователи обычно предпочитают использовать символы ASCII, так как в этом случае не нужно переносить пальцы в ту часть клавиатуры, где находятся специальные клавиши. Примечание Ваша curses-программа сможет работать с более широким набором терминалов, если в ней с каждой специальной клавишей будет связан символ ASCII, поскольку не на каждом терминале клавиатура включает, например, стрел- ки. В-четвертых, команда CTRL+L выполняет функцию, которую должны иметь большинство curses-программ. Случается так, что другие программы выводят что-либо на экран (например, при передаче со- общений), либо экран портится в результате сбоев оборудования. В этом случае пользователь может ввести CTRL+L, в результате чего экран очищается и заполняется вновь посредством wrefresh (curscr) Наконец, еще одной важной особенностью является то, что вводи- мые команды заканчиваются нажатием CTRL+D, а не ESC. Было бы соблазнительно использовать ESC, так как эта клавиша является одной из немногих, присутствующих на любой клавиатуре (наряду с RETURN и BREAK). Однако, это может привести к возникновению двусмысленности. Большинство терминалов использует последова- тельности символов, начинающиеся с ESC для управления термина- лом и имеют клавиши, при нажатии на которые передаются такие последовательности. Если программа получает с клавиатуры ESC, она не может различить, нажал ли пользователь ESC или другую функциональную клавишу. Editor и другие использующие curses программы решают эту проб- лему, устанавливая таймер. Если за некоторый интервал времени приходит еще символ и он может быть началом последовательности, которую генерирует функциональная клавиша, программа пытается читать последующие символы до тех пор, пока не будет считана вся последовательность для функциональной клавиши, либо символ, который не может быть частью такой последовательности, либо по- ка не истечет определенное время. Эта стратегия хороша, но не абсолютно надежна. Пользователь может нажать ESC и сразу за ней еще клавишу, и тогда curses-программа решит, что была нажата функциональная клавиша. Кроме того, ESC не сразу передается об- рабатывающей программе, в результате чего последняя медленнее реагирует на нажатие этой клавиши. Многие существующие ныне программы используют ESC в качестве одной из основных своих команд, и это положение нельзя изме- нить, не вызвав недовольства большого количества пользователей. Чтобы использовать функциональную клавиатуру, эти программы должны решать описанную выше проблему двусмысленности - в луч- шем случае путем использования таймера. Мораль всего этого яс- на: избегайте использования клавиши ESC в Ваших программах. /* Программа editor - экранный редактор. Пользовательский интерфейс подобен подмножеству vi. Для простоты буфер хранится в stdscr */ #include #define CTRL(c) ((c) & 037) main (argc, argv) int argc; char **argv; ( extern void perror(), exit(); int i, n, l; int c; int line = 0; FILE *fd; if (argc != 2) { fprintf (stderr, "usage: %s file\n", argv [0]); exit (1); } fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit (2); } initscr (); cbreak (); nonl (); noecho (); idlok (stdscr, TRUE); keypad (stdscr, TRUE); /* Читаем файл */ while ((c = getc(fd)) != EOF) { if (c == '\n') line++; if (line > LINES - 2) break; addch(c); } fclose (fd); move (0, 0); refresh (); edit (); /* Записываем файл */ fd = fopen (argv [1], "w"); for (l = 0; l < LINES - 1; l++) { n = len(l); for (i = 0; i < n; i++) putc (mvinch (l, i) & A_CHARTEXT, fd); putc('\n', fd); } fclose(fd); endwin (); exit (0); } len (lineno) int lineno; { int linelen = COLS - 1; while (linelen >= 0 && mvinch (lineno, linelen) == ' ') linelen--; return linelen + 1; } /* Глобальное значение текущего положения курсора */ int row, col; edit () { int c; for (;;) { move (row, col); refresh (); c = getch (); /* Команды редактора */ switch (c) { /* hjkl и стрелки: перемещают курсор в указанном направлении */ case 'h': case KEY_LEFT: if (col > 0) col--; else flash (); break; case 'j': case KEY_DOWN: if (row < LINES - 1) row++; else flash (); break; case 'k': case KEY_UP: if (row > 0) row--; else flash (); break; case 'l': case KEY_RIGHT: if (col < COLS - 1) col++; else flash (); break; /* i: переход в режим ввода */ case KEY_IC: case 'I': input (); break; /* x: удалить текущий символ */ case KEY_DC: case 'x': delch (); break; /* o: вставить строку и перейти в режим ввода */ case KEY_IL: case 'o': move (++row, col = 0); insertln (); input (); break; /* d: удалить текущую строку */ case KEY_DL: case 'd': deleteln (); break; /* CTRL+L: перерисовать экран */ case KEY_CLEAR: case CTRL('L'): wrefresh (curscr); break; /* w: записать и закончить работу */ case 'w': return; /* q: закончить работу без записи файла */ case 'q': endwin (); exit (2); default: flash (); break; } } } /* Режим ввода: принимает и вставляет символы Выход: CTRL+D или EIC */ input () { int c; standout (); mvaddstr (LINES - 1, COLS - 20, "Режим ввода"); standend (); move (row, col); refresh (); for (;;) { c = getch (); if (c == CTRL('D') || c == KEY_EIC) break; insch (c); move (row, ++col); refresh (); } move (LINES - 1, COLS - 20); clrtoeol (); move (row, col); refresh (); } 5.2. Программа highlight Эта программа иллюстрирует использование подпрограммы attrset(). Программа highlight читает текстовый файл и исполь- зует находящиеся в нем управляющие цепочки для переключения ат- рибутов отображения. \U означает подчеркивание, \B - повышенную яркость, \N восстанавливает подразумеваемые значения атрибутов. Обратите внимание на первый вызов подпрограммы scrollok(), о которой мы еще не упоминали [см. curses(3X)]. Эта подпрограмма позволяет роллировать экран терминала, если его размер меньше размера файла. Когда программа пытается вывести что-либо ниже нижнего края экрана, scrollok() автоматически роллирует на одну строку вверх и вызывает refresh(). /* highlight: программа, использующая последовательности \U, \B, \N для выделения текста на экране, позволяя подчеркивать их или отображать с повышенной яркостью */ #include main (argc, argv) int argc; char **argv; { FILE *fd; int c, c2; void exit (), perror (); if (argc != 2) { fprintf (stderr, "usage: %s file\n", argv [0]); exit (1); } fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit (2); } initscr (); scrollok (stdscr, TRUE); nonl (); while ((c = getc (fd)) != EOF) { if (c == '\\') { c2 = getc (fd); switch (c2) { case 'B': attrset (A_BOLD); continue; case 'U': attrset (A_UNDERLINE); continue; case 'N': attrset (0); continue; } addch (c); addch (c2); } else addch (c) } fclose (fd); refresh (); endwin (); exit (0); } 5.3. Программа scatter Эта программа берет первые (LINES - 1) строк стандартного ввода и отображает символы на экране терминала в случайном порядке. Чтобы эта программа работала правильно, входной файл не должен содержать символов табуляции и неотображаемых символов. /* Программа scatter */ #include #include extern time_t time (); #define MAXLINES 120 #define MAXCOLS 160 char s [MAXLINES] [MAXCOLS]; /* Массив экрана */ int T [MAXLINES] [MAXCOLS]; /* Результирующий массив, чтобы сохранять количество и расположение введенных символов */ main () { register int row = 0, col = 0; register int c; int char_count = 0; time_t t; void exit (), srand (); initscr (); for (row = 0; row < MAXLINES; row++) for (col = 0; col < MAXCOLS; col++) s [row] [col] = ' '; col = row = 0; /* Считываем */ while ((c=getchar ()) != EOF && row < LINES) { if (c != '\n') { /* Помещаем символ в массив экрана */ s [row] [col] = c; if (c != ' ') char_count++; } else { col = 0; row++; } } time (&t); /* Инициализация датчика случайных чисел */ srand ((unsigned) t); while (char_count) { row = rand () % LINES; col = (rand () >> 2) % COLS; if (T [row] [col] !=1 && s [row] [col] != ' ') { move (row, col); addch (s [row] [col]); T [row] [col] = 1; char_count--; refresh (); } } endwin (); exit (0); } 5.4. Программа show Программа show просматривает файл, показывая его содержимое эк- ран за экраном в ответ на нажатия пробела. Программа вызывает cbreak(), чтобы не нужно было нажимать возврат каретки после пробела, и noecho(), чтобы пробел не выводился на экран. Не об- суждавшаяся ранее подпрограмма nonl() вызывается для дополни- тельной оптимизации. Также не обсуждавшаяся ранее подпрограмма idlok() вызывается, чтобы дать возможность вставлять и удалять строки [дополнительную информацию см. в curses(3X)]. Заметьте, что вызываются еще подпрограммы clrtoeol(), clrtobot(). #include #include main (argc, argv) int argc; char **argv; { FILE *fd; char linebuf[BUFSIZ]; int line; void done(), perror(), exit(); if (argc != 2) { fprintf (stderr, "Вызов: %s файл\n", argv [0]); exit (1); } fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit (2); } signal (SIGINT, done); initscr (); noecho (); cbreak (); nonl (); idlok (stdscr, TRUE); while (1) { move (0, 0); for (line = 0; line < LINES; line++) { if (!fgets (linebuf, sizeof linebuf, fd)) { clrtobot (); done (); } move (line, 0); printw ("%s", linebuf); } refresh (); if (getch == 'q') done(); } } void done () { move (LINES - 1, 0); clrtoeol (); refresh (); endwin (); exit (0); } 5.5. Программа two Эта программа просматривает входной файл, выводя первую его страницу на экран терминала, с которого она была вызвана, а вторую - на терминал, имя которого указывается в командной строке. Затем она ждет нажатия пробела на одном из терминалов, после чего выводит на него следующую страницу. Программа two - это простейший пример двухтерминальной curses- программы. Она не поддерживает уведомления и требует указания имени и типа второго терминала в командной строке. После этого на втором терминале нужно ввести команду sleep 100000, чтобы перевести его в неактивное состояние на время работы программы. Пользователь первого терминала должен иметь полномочия для вво- да и вывода на втором терминале. #include #include SCREEN *me, *you; SCREEN *set_term (); FILE *fd, *fdyou; char linebuf [512]; main (argc, argv) int argc; char **argv; { void done (), exit (); unsigned sleep (); char *getenv (); int c; if (argc != 4) { fprintf (stderr, "usage: %s oterm otype ifile\n", argv [0]); exit (1); } fd = fopen (argv [3], "r"); fdyou = fopen (argv [1], "w+"); signal (SIGINT, done); /* Красиво умереть */ me = newterm (getenv ("TERM"), stdout, stdin); /* Инициализация своего терминала */ you = newterm (argv [2], fdyou, fdyou); /* Инициализация второго терминала */ set_term (me); /* Устанавливаем режимы своего термина- ла */ noecho (); /* Отменяем эхо */ cbreak (); /* Включаем cbreak */ nonl (); /* Разрешаем переход к новой строке */ nodelay (stdscr, TRUE) /* Не зависаем на вводе */ /* Выдаем первый экран на свой терминал */ dump_page (me); /* Выдаем второй экран на другой терминал */ dump_page (you); for (;;) { /* Для каждого экрана */ set_term (me); c = getch (); /* Ждем, пока пользователь прочитает все это */ if (c == 'q') done (); if (c == ' ') dump_page (me); set_term (you); c = getch (); /* Ждем, пока пользователь прочитает все это */ if (c == 'q') done (); if (c == ' ') dump_page (you); sleep(1); } } dump_page (term) SCREEN *term; { int line; set_term (term); move (0, 0); for (line = 0; line < LINES - 1; line++) { if (fgetc (linebuf, sizeof linebuf, fd) == NULL) { clrtobot (); done (); } mvaddstr (line, 0, linebuf); } standout ( mvprintw (LINES - 1, 0, "--Еще--"); standend (); refresh (); /* Выводим */ } /* Очищаем и заканчиваем */ void done () { /* Очищаем первый терминал */ set_term (me); move (LINES - 1, 0); /* В левый нижний угол */ clrtoeol (); /* Очищаем нижнюю строку */ refresh (); /* Все обновляем */ endwin (); /* Для выхода из curses */ /* Очищаем второй терминал */ set_term (you); move (LINES - 1, 0); /* В левый нижний угол */ clrtoeol (); /* Очищаем нижнюю строку */ refresh (); /* Все обновляем */ endwin (); /* Для выхода из curses */ exit (0); } 5.6. Программа window Эта программа представляет собой пример работы со многими окна- ми. Основная информация содержится в stdscr. Если на физический экран нужно временно вывести что-либо еще, создается новое ок- но, покрывающее часть экрана. Обращение к wrefresh() для этого окна приводит к тому, что его содержимое записывается на экран терминала поверх содержимого stdscr. Последующий вызов refresh() для stdscr приводит к восстановлению экрана. Обратите внимание на вызов подпрограммы touchwin() [мы не упоминали о ней ранее, см. curses(3X)], который производится перед выводом содержимого окна поверх того окна, которое уже находится на эк- ране. Эта подпрограмма препятствует оптимизации работы с экра- ном. Если у Вас возникают проблемы с выводом одного окна поверх другого, для нормальной работы следует предварительно вызывать touchwin() для нового окна. #include WINDOW *cmdwin; main () { int i, c; char buf [120]; void exit (); initscr (); nonl (); noecho (); cbreak (); cmdwin = newwin (3, COLS, 0, 0); /* Верхние 3 строки */ for (i = 0; i < LINES; i++) mvprintw (i, 0, "Это строка %d окна stdscr", i); for (;;) { refresh (); c = getch (); switch (c) { case 'c': /* Ввод команды с клавиатуры */ werase (cmdwin); wprintw (cmdwin, "Введите команду:"); wmove (cmdwin, 2, 0); for (i = 0; i < COLS; i++) waddch (cmdwin, '-'); wmove (cmdwin, 1, 0); touchwin (cmdwin); wrefresh (cmdwin); wgetstr (cmdwin, buf); touchwin (stdscr); /* Теперь команда находится в буфере. В этом месте должны располагаться операторы для ее обработки */ case 'q': endwin (); exit (0); } }