Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 19 (всего у книги 67 страниц)
Последняя функция, обслуживаемая структурой termios
, – манипулирование скоростью линии передачи. Для этой скорости не определен отдельный элемент структуры; она управляется вызовами функций. Скорости ввода и вывода обрабатываются отдельно.
Далее приведены четыре прототипа вызовов:
#include
speed_t cfgetispeed(const struct termios *);
speed_t cfgetospeed(const struct termios *);
int cfsetispeed(struct termios *, speed_t speed);
int cfsetospeed(struct termios *, speed_t speed);
Обратите внимание на то, что они воздействуют на структуру termios
, а не непосредственно на порт. Это означает, что для установки новой скорости вы должны считать текущие установки с помощью функции tcgetattr
, задать скорость, применив приведенные вызовы, и затем записать структуру termios
обратно с помощью функции tcsetattr
. Скорость линии передачи изменится только после вызова tcsetattr
.
В вызовах перечисленных функций допускается задание разных значений скорости speed
, но к основным относятся следующие константы:
□ B0
– отключение терминала;
□ B1200
– 1200 бод;
□ B2400
– 2400 бод;
□ B9600
– 9600 бод;
□ B19200
– 19 200 бод;
□ B38400
– 38 400 бод.
Не существует скоростей выше 38 400 бод, задаваемых стандартом, и стандартного метода обслуживания последовательных портов на более высоких скоростях.
Дополнительные функцииПримечание
В некоторых системах, включая Linux, для выбора более высоких скоростей определены константы
В57600
,B115200
иВ230400
. Если вы пользуетесь более старой версией ОС Linux и эти константы недоступны, можно применить командуsetserial
для получения нестандартных скоростей 57 600 и 115 200. В этом случае указанные скорости будут использоваться при выборе константы B38400. Оба эти метода непереносимы, поэтому применяйте их с осторожностью.
Есть небольшое число дополнительных функций для управления терминалами. Они работают непосредственно с дескрипторами файлов без необходимости считывания и записывания структур типа termios
.
#include
int tcdrain(int fd);
int tcflow(int fd, int flowtype);
int tcflush(int fd, int in_out_selector);
Функции предназначены для следующих целей:
□ tcdrain
– заставляет вызвавшую программу ждать до тех пор, пока не будет отправлен весь поставленный в очередь вывод;
□ tcflow
– применяется для приостановки или возобновления вывода;
□ tcflush
– может применяться для отказа от входных или выходных данных либо и тех, и других.
Теперь, когда мы уделили довольно много внимания структуре termios
, давайте рассмотрим несколько практических примеров. Возможно, самый простой из них – отключение отображения при чтении пароля (упражнение 5.4). Это делается сбрасыванием флага echo
.
Упражнение 5.4. Программа ввода пароля с применение termios
1. Начните вашу программу password.с со следующих определений:
#include
#include
#include
#define PASSWORD_LEN 8
int main() {
struct termios initialrsettings, newrsettings;
char password[PASSWORD_LEN + 1];
2. Далее добавьте строку, считывающую текущие установки из стандартного ввода и копирующую их в только что созданную вами структуру типа termios
:
tcgetattr(fileno(stdin), &initialrsettings);
3. Создайте копию исходных установок, чтобы восстановить их в конце. Сбросьте флаг ECHO
в переменной newrsettings
и запросите у пользователя его пароль:
newrsettings = initialrsettings;
newrsettings.с_lflag &= ~ЕСНО;
printf("Enter password: ");
4. Далее установите атрибуты терминала в newrsettings и считайте пароль. И наконец, восстановите первоначальные значения атрибутов терминала и выведите пароль на экран, чтобы свести на нет все предыдущие усилия по обеспечению безопасности:
if (tcsetattr(fileno(stdin), TCSAFLUSH, &newrsettings) != 0) {
fprintf(stderr, «Could not set attributesn»);
} else {
fgets(password, PASSWORD_LEN, stdin);
tcsetattr(fileno(stdin), TCSANOW, &initialrsettings);
fprintf(stdout, «nYou entered %sn», password);
}
exit(0);
}
Когда вы выполните программу, то увидите следующее:
$ ./password
Enter password: You entered hello
$
Как это работает
В этом примере слово hello
набирается на клавиатуре, но не отображается на экране в строке приглашения Enter password:
. Никакого вывода нет до тех пор, пока пользователь не нажмет клавишу
Будьте осторожны и изменяйте с помощью конструкции X&=~FLAG
(которая очищает бит, определенный флагом FLAG
в переменной X
) только те флаги, которые вам нужно изменить. При необходимости можно воспользоваться конструкцией X|=FLAG
для установки одиночного бита, определяемого FLAG
, хотя в предыдущем примере она не понадобилась.
Для установки атрибутов применяется действие TCSAFLUSH
для очистки буфера клавиатуры, символов, которые пользователи вводили до того, как программа подготовилась к их считыванию. Это хороший способ заставить пользователей не начинать ввод своего пароля, пока не отключено отображение. Перед завершением программы вы также восстанавливаете первоначальные установки.
Другой распространенный пример использования структуры termios
– перевод терминала в состояние, позволяющее вам считывать каждый набранный символ (упражнение 5.5). Для этого отключается канонический режим и используются параметры MIN
и TIME
.
Упражнение 5.5. Считывание каждого символа
Применяя только что полученные знания, вы можете изменить программу menu. Приведенная далее программа menu4.c базируется на программе menu3.c и использует большую часть кода из файла password.с, включенного в нее. Внесенные изменения выделены цветом и объясняются в пунктах описания.
1. Прежде всего, вам следует, включить новый заголовочный файл в начало программы:
#include
#include
#include
#include
char *menu[] = {
«a – add new record»,
«d – delete record»,
«q – quit»,
NULL,
};
2. Затем нужно объявить пару новых переменных в функции main
:
int getchoice(char *greet, char *choices[], FILE *in, FILE *out);
int main() {
int choice = 0;
FILE *input;
FILE *output;
struct termios initial_settengs, new_settings;
3. Перед вызовом функции getchoice
вам следует изменить характеристики терминала, этим определяется место следующих строк:
if (!isatty(fileno(stdout))) {
fprintf(stderr, «You are not a terminal, OK.n»);
}
input = fopen(«/dev/tty», "r");
output = fopen(«/dev/tty», "w");
if (!input || !output) {
fprintf(stderr, «Unable to open /dev/ttyn»);
exit(1);
}
tcgetattr(fileno(input), &initial_settings);
new_settings = initial_settings;
new_settings.c_lfag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VTIME] = 0;
new_settings.c_lflag &= ~ISIG;
if (tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {
fprintf(stderr, «could not set attributesn»);
}
4. Перед завершением вы также должны вернуть первоначальные значения:
do {
choice = getchoice(«Please select an action», menu, input, output);
printf(«You have chosen: %cn», choice);
} while (choice != 'q');
tcsetattr(fileno(input), TCSANOW, &initial_settings);
exit(0);
}
5. Теперь, когда вы в неканоническом режиме, необходимо проверить на соответствие возвраты каретки, поскольку стандартное преобразование CR (возврат каретки) в LF (переход на новую строку) больше не выполняется:
int getchoice (char *greet, char *choices[], FILE *in, FILE *out) {
int chosen = 0;
int selected;
char **option;
do {
fprintf(out, «Choice: %sn», greet);
option = choices;
while (*option) {
fprintf(but, «%sn», *option);
option++;
}
do {
selected = fgetc(in);
} while (selected == 'n' || selected == 'r');
option = choices;
while (*option) {
if (selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if (!chosen) {
fprintf(out, «Incorrect choice, select againn»);
}
} while(!chosen);
return selected;
}
Пока вы не устроите все иначе, теперь, если пользователь нажмет в вашей программе комбинацию клавиш ISIG
в локальных режимах. Для этого в функцию main
включается следующая строка:
new_settings.c_lflag &= ~ISIG;
Если вы внесете эти изменения в вашу программу меню, то будете получать немедленный отклик, и вводимый вами символ не будет отображаться на экране.
$ ./menu4
Choice: Please select an action
a – add new record
d – delete record
q – quit
You have chosen: a
Choice: Please select an action
a – add new record
d – delete record
q – quit
You have chosen: q $
Если вы нажмете комбинацию клавиш
Вывод терминала
С помощью структуры типа termios
вы управляли вводом с клавиатуры, но было бы хорошо иметь такой же уровень управления выходными данными, отображаемыми на экране терминала. В начале главы вы применяли функцию printf
для вывода символов на экран, не имея при этом возможности помещать их в определенное место экрана.
Во многих системах UNIX применяются терминалы, несмотря на то, что сегодня во многих случаях «терминал» может на самом деле быть ПК, выполняющим программу эмуляции терминала или терминальным приложением в оконной среде, таким как xterm в графической оболочке X11.
Исторически существовало очень большое число аппаратных терминалов разных производителей. Несмотря на то, что почти все они применяют escape-последовательности (строки символов, начинающиеся с escape-символа) для управления положением курсора и другими атрибутами, такими как жирное начертание или мерцание, способы реализации управления при этом слабо стандартизованы. У некоторых старых моделей терминалов также разные характеристики прокрутки экрана, который может очищаться или не очищаться, когда посылается символ Backspace
, и т.д.
Примечание
Существует стандарт ANSI для набора escape-последовательностей (в основном базирующихся на последовательностях, применяемых в серии VT-терминалов компании Digital Equipment Corporation, но не идентичных им). Многие терминальные программы обеспечивают эмуляцию стандартного аппаратного терминала, часто VT100, VT220 или ANSI, а иногда и других типов.
Такое разнообразие аппаратных моделей терминалов было бы огромной проблемой для программистов, пытающихся написать программы управления экраном, выполняющиеся на терминалах разных типов. Например, терминал ANSI применяет последовательность символов Escape, [, A
для перемещения курсора вверх на одну строку. Терминал ADM-За (очень распространенный несколько лет назад) использует один управляющий символ от комбинации клавиш
Написание программы, имеющей дело с терминалами разнообразных типов, которые могут быть подключены в системе UNIX, кажется крайне устрашающей задачей. Такой программе понадобится разный программный код для терминала каждого типа.
Как ни странно, решение существует в пакете, известном как terminfo. Вместо необходимости обслуживания любого типа терминала в каждой программе, ей достаточно просмотреть базу данных типов терминалов для получения корректной информации. В большинстве современных систем UNIX, включая Linux, эта база данных объединена с другим пакетом, названным curses, о котором вы узнаете в следующей главе.
Для применения функций terminfo вы, как правило, должны подключить заголовочный файл curses.h пакета curses и собственный заголовочный файл term.h пакета terminfo. В некоторых системах Linux вам, возможно, придется применять реализацию curses, известную как ncurses, и включить файл ncurses.h для предоставления прототипов вашим функциям terminfo.
Установите тип вашего терминалаОкружение ОС Linux содержит переменную TERM
, которая хранит тип используемого терминала. Обычно она устанавливается системой автоматически во время регистрации в системе. Системный администратор может задать тип терминала по умолчанию для каждого непосредственно подключенного терминала и может сформировать подсказку с типом терминала для удаленных сетевых пользователей. Значение TERM
может быть передано rlogin
через telnet.
Пользователь может запросить командную оболочку о соображениях системы по поводу используемого им или ею терминала:
$ echo $TERM
xterm
$
В данном случае оболочка выполняется из программы, называемой xterm – эмулятора терминала для графической оболочки X Window System, или программы, обеспечивающей "такие же функциональные возможности, как KDE's Konsole или GNOME's gnome-terminal.
Пакет terminfo содержит базу данных характеристик и управляющих escape-последовательностей для большого числа терминалов и предоставляет единообразный программный интерфейс для их использования. Отдельная программа, таким образом, сможет извлечь выгоду от применения новых моделей терминалов по мере расширения базы данных и не заботиться о поддержке множества разных терминалов.
Характеристики терминалов в terminfo описываются с помощью атрибутов. Они хранятся в наборе откомпилированных файлов terminfo, которые обычно находятся в каталогах /usr/lib/terminfo или /usr/share/terminfo. Для каждого терминала (и многих принтеров, которые тоже могут быть заданы в terminfo) есть файл, в котором определены характеристики терминала и способ доступа к его функциям. Для того чтобы не создавать слишком большого каталога, реальные файлы хранятся в подкаталогах, имена которых – первый символ типа терминала. Так определение терминала VT100 можно найти в файле …terminfo/v/vt100.
Файлы terminfo пишутся по одному на каждый тип терминала в исходном формате, пригодном (или почти пригодном!) для чтения, который затем компилируется командой tic
в более компактный и эффективный формат, используемый прикладными программами. Странно, стандарт X/Open ссылается на описания исходного и откомпилированного формата, но не упоминает команду tic
, необходимую для реального преобразования исходного формата в откомпилированный. Для вывода пригодной для чтения версии откомпилированного элемента набора terminfo можно использовать программу infocmp.
Далее приведен пример файла terminfo для терминала VT100:
$ infocmp vt100
vt100|vt100-am|dec vt100 (w/advanced video),
am, mir, msgr, xenl, xon, cols#80, it#8, lines#24, vt#3,
acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=E[5m$<2>, bold=E[1m$<2>,
clear=E[HE[J$<50>, cr=r, csr=E[%i%p1%d;%p2%dr,
cub=E[%p1%dD, cub1=b, cud=E[%p1%dB, cud1=n,
cuf=E[%p1%dC, cuf1=E[C$<2>,
cup=E[%i%p1%d; %p2%dH$<5>, cuu=E[%p1%dA,
cuu1=E[A$<2>, ed=E[J$<50>, el=E[K$<3>,
el1=E[1K$<3>, enacs=E(BE)0, home=E[H, ht=t,
hts=EH, ind=n, ka1=EOq, ka3=EOs, kb2=EOr, kbs=b,
kc1=EOp, kc3=EOn, kcub1=EOD, kcud1=EOB,
kcuf1=EOC, kcuu1=EOA, kent=EOM, kf0=EOy, kf1=EOP,
kf10=EOx, kf2=EOQ, kf3=EOR, kf4=EOS, kf5=EOt,
kf6=EOu, kf7=EOv, kf8=EOl, kf9=EOw, rc=E8,
rev=E[7m$<2>, ri=EM$<5>, rmacs=^O, rmkx=E[?11E>,
rmso=E[m$<2>, rmul=E[m$<2>,
rs2=E>E[?31E[?41E[?51E[?7hE[?8h, sc=E7,
sgr=E[0%?%p1%p6%|%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t^N%e^O%;,
sgr0=E[m^0$<2>, smacs=^N, smkx=E[?1hE=,
smso=E[1;7m$<2>; smul=E[4m$<2>, tbc=E[3g,
Каждое определение в terminfo
состоит из трех типов элементов. Каждый элемент называется capname
(имя характеристики) и определяет характеристику терминала.
Булевы или логические характеристики просто обозначают наличие или отсутствие поддержки терминалом конкретного свойства. Например, булева характеристика xon
присутствует, если терминал поддерживает управление потоком XON/XOFF
.
Числовые характеристики определяют размеры или объемы, например lines
– это количество строк на экране, a cols
– количество столбцов. Число отделяется от имени характеристики символом #
. Для описания терминала с 80 столбцами и 24 строками следует написать cols#80, lines#24
.
Строковые характеристики немного сложнее. Они применяются для двух разных типов характеристик: определения строк вывода, необходимых для доступа к функциям терминала, и определения строк ввода, которые будут получены, когда пользователь нажмет определенные клавиши, обычно функциональные или специальные клавиши на цифровой клавиатуре. Некоторые строковые параметры очень просты, например el
, что означает «стереть до конца строки». Для того чтобы сделать это на терминале VT100, потребуется escape-последовательность Esc, [, K
. В исходном формате terminfo это записывается как еl=Е[K
.
Специальные клавиши определены аналогичным образом. Например, функциональная клавиша Esc, O, P
, которая определяется как kf1=EOP
.
Все несколько усложняется, если escape-последовательности требуются какие-либо параметры. Большинство терминалов могут перемещать курсор в заданные строку и столбец. Ясно, что неразумно хранить отдельную характеристику для каждой точки экрана, в которую можно переместить курсор, поэтому применяется общая строковая характеристика с параметрами, определяющими значения, которые вставляются при использовании характеристики. Например, терминал VT100 использует последовательность Esc, [,
для перемещения курсора в заданную позицию. В исходном формате terminfo это записывается довольно устрашающе: cup=E[%i%p1%d;%p2%dH$<5>
.
Эта строка означает следующее:
□ E
– послать escape-символ;
□ [
– послать символ [
;
□ %i
– дать приращение аргументам;
□ %p1
– поместить первый аргумент в стек;
□ %d
– вывести число из стека как десятичное;
□ ;
– послать символ ;
;
□ %р2
– поместить второй аргумент в стек;
□ %d
– вывести число из стека как десятичное;
□ H
—послать символ H
.
Данная запись кажется сложной, но позволяет задавать параметры в строгом порядке, не зависящем от порядка, в котором терминал ожидает их появления в финальной escape-последовательности. Приращение аргументов %i
необходимо, поскольку стандартная адресация курсора задается, начиная от верхнего левого угла экрана (0, 0), а терминал VT100 обозначает начальную позицию курсора как (1, 1). Заключительные символы $<5>
означают, что для обработки терминалом перемещения курсора требуется задержка, эквивалентная времени вывода пяти символов.
Применение характеристик terminfoПримечание
Мы могли бы описывать огромное множество характеристик, но, к счастью, в основном системы UNIX и Linux приходят с большинством предопределенных терминалов. Если нужно добавить новую модель терминала, вы можете найти полный список характеристик на странице интерактивного справочного руководства, посвященной terminfo. Лучше всего начать с поиска включенного в базу данных терминала, похожего на ваш новый, и затем создания описания новой модели как вариации существующего, т. е. осуществить последовательный просмотр характеристик, одну за другой, и исправление нуждающихся в корректировке.
Теперь, когда вы знаете, как определить характеристики терминала, нужно научиться обращаться к ним. Когда используется terminfo, прежде всего вам нужно задать тип терминала, вызвав функцию setupterm
. Она инициализирует структуру TERMINAL
для текущего типа терминала. После этого вы сможете запрашивать характеристики терминала и применять его функциональные возможности. Делается это с помощью вызова setupterm
, подобного приведенному далее:
#include
int setupterm(char *term, int fd, int *errret);
Библиотечная функция setupterm
задает текущий тип терминала в соответствии с заданным параметром term
. Если term
– пустой указатель, применяется переменная окружения TERM
. Открытый дескриптор файла, предназначенный для записи на терминал, должен передаваться в параметре fd
. Результат функции хранится в целой переменной, на которую указывает errret
, если это не пустой указатель. Могут быть записаны следующие значения:
□ -1 – нет базы данных terminfo;
□ 0 – нет совпадающего элемента в базе данных terminfo;
□ 1 – успешное завершение.
Функция setupterm
возвращает константу OK
в случае успешного завершения и ERR
в случае сбоя. Если на параметр errret
установлен как пустой указатель, setupterm
выведет диагностическое сообщение и завершит программу в случае своего аварийного завершения, как в следующем примере:
#include
#include
#include
#include
int main() {
setupterm(«unlisted», fileno(stdout), (int *)0);
printf(«Done.n»);
exit(0);
}
Результат выполнения этой программы в вашей системе может не быть точной копией приведенного далее, но его смысл будет вполне понятен. "Done.
" не выводится, поскольку функция setupterm
после своего аварийного завершения вызвала завершение программы:
$ cc -о badterm badterm.с -lncurses
$ ./badterm
'unlisted': unknown terminal type.
$
Обратите внимание на строку компиляции в примере: в этой системе Linux мы используем реализацию ncurses библиотеки curses со стандартным заголовочным файлом, находящимся в стандартном каталоге. В таких системах вы можете просто включить файл curses.h и задать -lncurses
для библиотеки.
В функции выбора пункта меню хорошо было бы иметь возможность очищать экран, перемещать курсор по экрану и записывать его положение на экране. После вызова функции setupterm
вы можете обращаться к характеристикам базы данных terminfo с помощью вызовов трех функций, по одной на каждый тип характеристики:
#include
int tigetflag(char *capname);
int tigetnum(char *capname);
char *tigetstr(char *capname);
Функции tigetflag
, tigetnum
и tigetstr
возвращают значения характеристик terminfo булева или логического, числового и строкового типов соответственно. В случае сбоя (например, характеристика не представлена) tigetflag
вернет -1, tigetnum
– -2, a tigetstr
– (char*)-1.
Вы можете применять базу данных terminfo для определения размера экрана терминала, извлекая характеристики cols
и lines
с помощью следующей программы sizeterm.c:
#include
#include
#include
#include
int main() {
int nrows, ncolumns;
setupterm(NULL, fileno(stdout), (int *)0);
nrows = tigetnum(«lines»);
ncolumns = tigetnum(«cols»);
printf(«This terminal has %d columns and %d rowsn», ncolumns, nrows);
exit(0);
}
$ echo $TERM
vt100
$ ./sizeterm
This terminal has 80 columns and 24 rows
Если запустить эту программу в окне рабочей станции, вы получите результат, отражающий размер текущего окна:
$ echo $TERM
xterm
$ ./sizeterm
This terminal has 88 columns and 40 rows
$
Если применить функцию tigetstr
для получения характеристики перемещения курсора (cup
) терминала типа xterm, вы получите параметризованный ответ: Е[%p1%d;%p2%dH
.
Этой характеристике требуются два параметра: номер строки и номер столбца, в которые перемещается курсор. Обе координаты измеряются, начиная от нулевого значения в левом верхнем углу экрана.
Вы можете заменить параметры в характеристике реальными значениями с помощью функции tparm
. До девяти параметров можно заменить значениями и получить в результате применяемую escape-последовательность символов.
#include
char *tparm(char *cap, long p1, long p2, ..., long p9);
После формирования escape-последовательности с помощью tparm
, ее нужно отправить на терминал. Для корректной обработки этой последовательности не следует пересылать строку на терминал с помощью функции printf
. Вместо нее примените одну из специальных функций, обеспечивающих корректную обработку любых задержек, необходимых для завершения операции, выполняемой терминалом. К ним относятся следующие:
#include
int putp(char *const str);
int tputs(char *const str, int affcnt, int (*putfunc)(int));
В случае успешного завершения функция putp
вернет константу OK
,в противном случае – ERR
. Эта функция принимает управляющую строку терминала и посылает ее в стандартный вывод stdout.
Итак, для перемещения в строку 5 и столбец 30 на экране можно применить блок программного кода, подобный приведенному далее:
char *cursor;
char *esc_sequence;
cursor = tigetstr(«cup»);
esc_sequence = tparm(cursor, 5, 30);
putp(esc_sequence);
Функция tputs
предназначена для ситуаций, в которых терминал не доступен через стандартный вывод stdout
, и позволяет задать функцию, применяемую для вывода символов. Она возвращает результат заданной пользователем функции putfunc
. Параметр affcnt
предназначен для обозначения количества строк, подвергшихся изменению. Обычно он устанавливается равным 1. Функция, используемая для вывода строки, должна иметь те же параметры и возвращать тип значения как у функции putfunc
. В действительности putp(string)
эквивалентна вызову tputs (string, 1, putchar)
. В следующем примере вы увидите применение функции tputs
, используемой с функцией вывода, определенной пользователем.
Имейте в виду, что в некоторых старых дистрибутивах Linux последний параметр функции tputs
определен как int (*putfunc)(char)
, что заставит вас изменить определение функции char_to_terminal
из упражнения 5.6.
Примечание
Если вы обратитесь к страницам интерактивного справочного руководства за информацией о функции
tparm
и характеристиках терминалов, то можете встретить функциюtgoto
. Причина, по которой мы не используем эту функцию, хотя она, очевидно, предлагает более легкий способ перемещения курсора, заключается в том, что она не включена в стандарт X/Open (Single UNIX Specification Version 2) по данным издания 1997 г. Следовательно, мы не рекомендуем применять любую из этих функций в ваших новых программах.
Вы почти готовы добавить обработку экрана в вашу функцию выбора пункта меню. Единственно, что осталось, – очистить экран просто с помощью свойства clear
. Некоторые терминалы не поддерживают характеристику clear
, которая помещает курсор в левый верхний угол экрана. В этом случае вы можете поместить курсор в левый верхний угол и применить команду ed
– удалить до конца экрана.
Для того чтобы собрать всю полученную информацию вместе, напишем окончательную версию примера программы выбора пункта меню screenmenu.c, в которой вы "нарисуете" варианты пунктов меню на экране для того, чтобы пользователь выбрал нужный пункт (упражнение 5.6).
Упражнение 5.6. Полное управление терминалом
Вы можете переписать функцию getchoice
из программы menu4.c для предоставления полного управления терминалом. В этом листинге функция main
пропущена, потому что она не меняется. Другие отличия от программы menu4.c выделены цветом.
#include
#include
#include
#include
#include
#include
static FILE* output_stream = (FILE *)0;
char *menu[] = {
«a – add new record»,
«d – delete record»,
«q – quit»,
NULL,
};
int getchoice(char *greet, char *choices[], FILE *in, FILE *out);
int char_to_terminal(int_char_to_write);
int main() {
...
}
int getchoice(char *greet, char* choices[], FILE[]* in, FILE* out) {
int chosen = 0;
int selected;
int screenrow, screencol = 10;
char **option;
char* cursor, *clear;
output_stream = out;
setupterm(NULL, fileno(out), (int*)0);
cursor = tigetstr(«cup»);
clear = tigetstr(«clear»);
screenrow =4;
tputs(clear, 1, (int*)char_to_terminal);
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, «Choice: %s», greet);
screenrow += 2;
option = choices;
while (*option) {
ftputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, «%s», *option);
screenrow++;
option++
}
fprintf(out, «n»);
do {
fflush(out);
selected = fgetc(in);
option = choices;
while (*option) {
if (selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if (!chosen) {
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, «Incorrect choice, select againn»);
}
} while (!chosen);
tputs(clear, 1, char_to_terminal);
return selected;
}
int char_to_terminal(int char_to_write) {
if (output_stream) putc(char_to_write, output_stream);
return 0;
}
Сохраните эту программу как menu5.с.
Как это работает
Переписанная функция getchoice
выводит то же меню, что и в предыдущих примерах, но подпрограммы вывода изменены так, чтобы можно было воспользоваться характеристиками из базы данных terminfo
. Если вы хотите видеть на экране сообщение «You have chosen:» дольше, чем одно мгновение перед очисткой экрана и подготовкой его к следующему выбору пункта меню, добавьте в функцию main
вызов sleep
:
do {
choice = getchoice(«Please select an action», menu, input, output);
printf(«nYou have chosen: %cn», choice);
sleep(1);
} while (choice != 'q');
Последняя функция в этой программе char_to_terminal
включает в себя вызов функции putc
, которую мы упоминали в главе 3.
В завершение этой главы бегло рассмотрим пример определения нажатий клавиш.