/* -------- Работа со временем ------------------------ */
void printTime () {
time_t t; /* текущее время */
struct tm *tm;
extern struct tm *localtime ();
char tmbuf[30];
static char *week[7] = { "Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб" };
static char *month[12] = { "Янв", "Фев", "Мар", "Апр", "Май", "Июн",
"Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" };

time (&t); /* узнать текущее время */
tm = localtime (&t); /* разложить его на компоненты */

sprintf (tmbuf, "%2s %02d:%02d:%02d %02d-%3s-%d",
week[tm -> tm_wday], /* день недели (0..6) */
tm -> tm_hour, /* часы (0..23) */
tm -> tm_min , /* минуты (0..59) */
tm -> tm_sec , /* секунды (0..59) */
tm -> tm_mday, /* число месяца (1..31) */
month[tm -> tm_mon], /* месяц (0..11) */
tm -> tm_year + 1900 /* год */
);

gotoXY (COLS / 2, TIMELINE);
clearEOL ();

gotoXY (COLS - strlen (tmbuf) - 1, TIMELINE);
bold ();
printf ("%s", tmbuf);
normal ();
}


8.14. Напишите программу, выдающую файл на экран порциями по 20 строк и ожидающую
нажатия клавиши. Усложнения:
a) добавить клавишу для возврата к началу файла.
b) используя библиотеку termcap, очищать экран перед выдачей очередной порции
текста.
c) напишите эту программу, используя библиотеку curses.
d) используя curses, напишите программу параллельного просмотра 2-х файлов в 2-х
неперекрывающихся окнах.
e) то же в перекрывающихся окнах.

8.15. Напишите функции включения и выключения режима эхо-отображения набираемых на
клавиатуре символов (ECHO).

8.16. То же про "режим немедленного ввода" (CBREAK). В обычном режиме строка, наб-
ранная на клавиатуре, сначала попадает в некоторый буфер в драйвере терминала|-.





____________________
|- Такие буфера носят название "character lists" - clist. Существуют "сырой" (raw)
clist, в который попадают ВСЕ символы, вводимые с клавиатуры; и "канонический" clist,
в котором хранится отредактированная строка - обработаны забой, отмена строки. Сами
специальные символы (редактирования и генерации сигналов) в каноническую очередь не
попадают (в режиме ICANON).





А. Богатырев, 1992-95 - 370 - Си в UNIX

"Сырая" "Каноническая"
клавиатура--->ОчередьВвода--*-->ОчередьВвода-->read
| файл-устройство
драйвер терминала V эхо /dev/tty??
|
экран<---ОчередьВывода---<--*--<-----------<---write

Этот буфер используется для предчтения - вы можете набирать текст на клавиатуре еще
до того, как программа запросит его read-ом: этот набранный текст сохранится в буфере
и при поступлении запроса будет выдан из буфера. Также, в каноническом режиме ICANON,
буфер ввода используется для редактирования введенной строки: забой отменяет послед-
ний набранный символ, CTRL/U отменяет всю набранную строку; а также он используется
для выполнения некоторых преобразований символов на вводе и выводе|=.
Введенная строка попадает в программу (которая запросила данные с клавиатуры при
помощи read, gets, putchar) только после того, как вы нажмете кнопку <ENTER> с кодом
'\n'. До этого вводимые символы накапливаются в буфере, но в программу не передаются
- программа тем временем "спит" в вызове read. Как только будет нажат символ '\n',
он сам поступит в буфер, а программа будет разбужена и сможет наконец прочесть из
буфера ввода набранный текст.
Для меню, редакторов и других "экранных" программ этот режим неудобен: пришлось
бы слишком часто нажимать <ENTER>. В режиме CBREAK нажатая буква немедленно попадает
в вашу программу (без ожидания нажатия '\n'). В данном случае буфер драйвера исполь-
зуется только для предчтения, но не для редактирования вводимого текста. Редактиро-
вание возлагается на вас - предусмотрите его в своей программе сами!
Заметьте, что код кнопки <ENTER> ("конец ввода") - '\n' - не только "проталки-
вает" текст в программу, но и сам попадает в буфер драйвера, а затем в вашу прог-
рамму. Не забывайте его как-то обрабатывать.
В MS DOS функция чтения кнопки в режиме ~ECHO+CBREAK называется getch(). В UNIX
аналогично ей будет работать обычный getchar(), если перед его использованием устано-
вить нужные режимы драйвера tty вызовом ioctl. По окончании программы режим драйвера
надо восстановить (за вас это никто не сделает). Также следует восстанавливать режим
драйвера при аварийном завершении программы (по любому сигналу|-|-).
Очереди ввода и вывода используются также для синхронизации скорости работы
программы (скажем, скорости наполнения буфера вывода символами, поступающими из прог-
раммы через вызовы write) и скорости работы устройства (с которой драйвер выбирает
символы с другого конца очереди и выдает их на экран); а также для преобразований
символов на вводе и выводе. Пример управления всеми режимами есть в приложении.

8.17. Функциональные клавиши большинства дисплеев посылают в линию не один, а нес-
колько символов. Например на терминалах, работающих в системе команд стандарта ANSI,
кнопки со стрелками посылают такие последовательности:

стрелка вверх "\033[A" кнопка Home "\033[H"
стрелка вниз "\033[B" кнопка End "\033[F"
стрелка вправо "\033[C" кнопка PgUp "\033[I"
стрелка влево "\033[D" кнопка PgDn "\033[G"

(поскольку первым символом управляющих последовательностей обычно является символ
'\033' (escape), то их называют еще escape-последовательностями). Нам же в программе
удобно воспринимать такую последовательность как единственный код с целым значением
большим 0xFF. Склейка последовательностей символов, поступающих от функциональных
клавиш, в такой внутренний код - также задача экранной библиотеки (учет системы
команд дисплея на вводе).

____________________
|= Режимы преобразований, символы редактирования, и.т.п. управляются системным вы-
зовом ioctl. Большой пример на эту тему есть в приложении.
|-|- Если ваша программа завершилась аварийно и моды терминала остались в "странном"
состоянии, то привести терминал в чувство можно командой stty sane





А. Богатырев, 1992-95 - 371 - Си в UNIX

Самым интересным является то, что одиночный символ '\033' тоже может прийти с
клавиатуры - его посылает клавиша Esc. Поэтому если мы строим распознаватель клавиш,
который при поступлении кода 033 начинает ожидать составную последовательность - мы
должны выставлять таймаут, например alarm(1); и если по его истечении больше никаких
символов не поступило - выдавать код 033 как код клавиши Esc.
Напишите распознаватель кодов, поступающих с клавиатуры. Коды обычных букв выда-
вать как есть (0..0377), коды функциональных клавиш выдавать как числа >= 0400.
Учтите, что разные типы дисплеев посылают разные последовательности от одних и тех же
функциональных клавиш: предусмотрите настройку на систему команд ДАННОГО дисплея при
помощи библиотеки termcap. Распознаватель удобно строить при помощи сравнения посту-
пающих символов с ветвями дерева (спускаясь по нужной ветви дерева при поступлении
очередного символа. Как только достигли листа дерева - возвращаем код, приписанный
этому листу):

---> '\033' ---> '[' ---> 'A' --> выдать 0400
| \--> 'B' --> 0401
| \--> 'C' --> 0402
| \--> 'D' --> 0403
\--> 'X' -----------> 0404
...

Нужное дерево стройте при настройке на систему команд данного дисплея.
Библиотека curses уже имеет такой встроенный распознаватель. Чтобы составные
последовательности склеивались в специальные коды, вы должны установить режим keypad:

int c; WINDOW *window;
...
keypad(window, TRUE);
...
c = wgetch(window);

Без этого wgetch() считывает все символы поодиночке. Символические названия кодов
для функциональных клавиш перечислены в <curses.h> и имеют вид KEY_LEFT, KEY_RIGHT
и.т.п. Если вы работаете с единственным окном размером с весь экран, то в качестве
параметра window вы должны использовать стандартное окно stdscr (это имя предопреде-
лено в include-файле curses.h).

# ======================================== Makefile для getch
getch: getch.o
cc getch.o -o getch -ltermlib

getch.o: getch.c getch.h
cc -g -DUSG -c getch.c





















А. Богатырев, 1992-95 - 372 - Си в UNIX

/* Разбор составных последовательностей клавиш с клавиатуры. */
/* ================================================== getch.h */
#define FALSE 0
#define TRUE 1
#define BOOLEAN unsigned char
#define INPUT_CHANNEL 0
#define OUTPUT_CHANNEL 1

#define KEY_DOWN 0400
#define KEY_UP 0401
#define KEY_LEFT 0402
#define KEY_RIGHT 0403

#define KEY_PGDN 0404
#define KEY_PGUP 0405

#define KEY_HOME 0406
#define KEY_END 0407

#define KEY_BACKSPACE 0410
#define KEY_BACKTAB 0411

#define KEY_DC 0412
#define KEY_IC 0413

#define KEY_DL 0414
#define KEY_IL 0415

#define KEY_F(n) (0416+n)

#define ESC ' 33'


extern char *tgetstr();

void _put(char c);
void _puts(char *s);
void keyboard_access_denied(void);
char *strdup(const char *s);
void keyinit(void);
int getc_raw(void);
void keyreset(void);
int getch(void);
int lgetch(BOOLEAN);
int ggetch(BOOLEAN);
int kgetch(void);
void _sigalrm(int n);
void init_keytry(void);
void add_to_try(char *str, short code);
void keypad_on(void);
void keypad_off(void);
int dotest(void);
void tinit(void);
void main(void);










А. Богатырев, 1992-95 - 373 - Си в UNIX

/* ===================================================== getch.c
* The source version of getch.c file was
* written by Pavel Curtis.
*
*/

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <termios.h>
#include <ctype.h>
#include <string.h>
#include <locale.h>
#include "getch.h"

#define keypad_local S[0]
#define keypad_xmit S[1]

#define key_backspace S[2]
#define key_backtab S[3]

#define key_left S[4]
#define key_right S[5]
#define key_up S[6]
#define key_down S[7]

#define key_ic S[8]
#define key_dc S[9]
#define key_il S[10]
#define key_dl S[11]

#define key_f1 S[12]
#define key_f2 S[13]
#define key_f3 S[14]
#define key_f4 S[15]
#define key_f5 S[16]
#define key_f6 S[17]
#define key_f7 S[18]
#define key_f8 S[19]
#define key_f9 S[20]
#define key_f10 S[21] /* f0 */
#define key_f11 S[22] /* f11 */
#define key_f12 S[23] /* f12 */
#define key_home S[24]
#define key_end S[25]
#define key_npage S[26]
#define key_ppage S[27]

#define TOTAL 28















А. Богатырев, 1992-95 - 374 - Си в UNIX

/* descriptors for keys */
char *KEYS[TOTAL+1] = {
"ke", "ks",

"kb", "kB",
"kl", "kr", "ku", "kd",
"kI", "kD", "kA", "kL",

"f1", "f2", "f3", "f4", "f5",
"f6", "f7", "f8", "f9", "f0",
"f.", "f-",

"kh", "kH", "kN", "kP",

NULL

}, *S[TOTAL];


void _put (char c) { write( INPUT_CHANNEL, &c, 1 ); }
void _puts(char *s) { tputs ( s, 1, _put ); }

static int _backcnt = 0;
static char _backbuf[30];

static struct try {
struct try *child;
struct try *sibling;
char ch;
short value;
} *_keytry;

BOOLEAN keypadok = FALSE;

struct termios new_modes;

void keyboard_access_denied(){ printf( "Клавиатура недоступна.\n" ); exit(1); }
char *strdup(const char *s) { return strcpy((char *) malloc(strlen(s)+1), s); }


























А. Богатырев, 1992-95 - 375 - Си в UNIX

/* Инициализация таблицы строк */
void keyinit(){
char *key, nkey[80], *p;
register i;

keyreset();
for( i=0; i < TOTAL; i++ ){
p = nkey;
printf("tgetstr(%s)...", KEYS[i]);
key = tgetstr(KEYS[i], &p);

if(S[i]) free(S[i]);
if(key == NULL){
S[i] = NULL; /* No such key */
printf("клавиша не определена.\n");
}else{
/* Decrypted string */
S[i] = strdup(key);
printf("считано.\n");
}
}

init_keytry();
if( tcgetattr(INPUT_CHANNEL, &new_modes) < 0 ){
keyboard_access_denied();
}

/* input flags */

/* отменить преобразование кода '\r' в '\n' на вводе */
new_modes.c_iflag &= ~ICRNL;
if ((new_modes.c_cflag & CSIZE) == CS8) /* 8-битный код */
new_modes.c_iflag &= ~ISTRIP; /* отменить & 0177 на вводе */

/* output flags */

/* отменить TAB3 - замену табуляций '\t' на пробелы */
/* отменить ONLCR - замену '\n' на пару '\r\n' на выводе */
new_modes.c_oflag &= ~(TAB3 | ONLCR);

/* local flags */

/* выключить режим ICANON, включить CBREAK */
/* выключить эхоотображение набираемых символов */
new_modes.c_lflag &= ~(ICANON | ECHO);

/* control chars */ /* при вводе с клавиш ждать не более ... */
new_modes.c_cc[VMIN] = 1; /* 1 символа и */
new_modes.c_cc[VTIME] = 0; /* 0 секунд */
/* Это соответствует режиму CBREAK */

/* Символы, нажатие которых заставляет драйвер терминала послать сигнал
* либо отредактировать набранную строку. Значение 0 означает,
* что соответствующего символа не будет */
new_modes.c_cc[VINTR] = '\0'; /* символ, генерящий SIGINT */
new_modes.c_cc[VQUIT] = '\0'; /* символ, генерящий SIGQUIT */
new_modes.c_cc[VERASE] = '\0'; /* забой (отмена последнего символа)*/
new_modes.c_cc[VKILL] = '\0'; /* символ отмены строки */
}





А. Богатырев, 1992-95 - 376 - Си в UNIX

/* Чтение одного символа непосредственно с клавиатуры */
int getc_raw(){
int n; char c;

n = read(INPUT_CHANNEL, &c, 1);
if (n <= 0) return EOF;
return (c & 0xFF);
}


static BOOLEAN _getback = FALSE;
static char _backchar = '\0';

/* Чтение символа - либо из буфера (если не пуст), либо с клавиатуры */
#define nextc() (_backcnt > 0 ? _backbuf[--_backcnt] : \
_getback ? _getback = FALSE, _backchar : \
getc_raw())

#define putback(ch) _backbuf[_backcnt++] = ch

void keyreset(){
_backcnt = 0; _backchar = '\0';
_getback = FALSE;
}


/* Функция чтения составного символа */
int getch(){
int c = lgetch(TRUE);
keypad_off();
return c;
}


/*
ВНИМАНИЕ!
Если в процессе будет получен сигнал,
в то время как процесс находится внутри вызова getch(),
то системный вызов read() вернет 0 и errno == EINTR.
В этом случае getch() вернет '\0'.
Чтобы избежать этой ситуации используется функция lgetch()
*/
int lgetch(BOOLEAN kpad) {
int c;

while((c = ggetch(kpad)) <= 0);
return c;
}


int ggetch(BOOLEAN kpad) {
int kgetch();

if( kpad ) keypad_on();
else keypad_off();

return keypadok ? kgetch() : nextc();
}






А. Богатырев, 1992-95 - 377 - Си в UNIX

/*
** int kgetch()
**
** Get an input character, but take care of keypad sequences, returning
** an appropriate code when one matches the input. After each character
** is received, set a one-second alarm call. If no more of the sequence
** is received by the time the alarm goes off, pass through the sequence
** gotten so far.
**
*/

#define CRNL(c) (((c) == '\r') ? '\n' : (c))

/* борьба с русской клавиатурой */
#if !defined(XENIX) || defined(VENIX)
# define unify(c) ( (c)&(( (c)&0100 ) ? ~0240 : 0377 ))
#else
# define unify(c) (c)
#endif













































А. Богатырев, 1992-95 - 378 - Си в UNIX

/* ==================================================================== */
#if !defined(XENIX) && !defined(USG) && !defined(M_UNIX) && !defined(unix)

/* Для семейства BSD */

static BOOLEAN alarmed;
jmp_buf jbuf;

int kgetch()
{
register struct try *ptr;
int ch;
char buffer[10]; /* Assume no sequences longer than 10 */
register char *bufp = buffer;
void (*oldsig)();
void _sigalrm();

ptr = _keytry;

oldsig = signal(SIGALRM, _sigalrm);
alarmed = FALSE;

if( setjmp( jbuf )) /* чтоб свалиться сюда с read-а */
ch = EOF;

do
{
if( alarmed )
break;
ch = nextc();
if (ch != EOF) /* getc() returns EOF on error, too */
*(bufp++) = ch;
if (alarmed)
break;

while (ptr != (struct try *)NULL &&
(ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch)) ))
ptr = ptr->sibling;

if (ptr != (struct try *)NULL)
{
if (ptr->value != 0)
{
alarm(0);
signal(SIGALRM, oldsig);
return(ptr->value);
}
else
{
ptr = ptr->child;
alarm(1);
}
}

} while (ptr != (struct try *)NULL);

alarm(0);
signal(SIGALRM, oldsig);

if (ch == EOF && bufp == buffer)
return ch;



А. Богатырев, 1992-95 - 379 - Си в UNIX

while (--bufp > buffer)
putback(*bufp);
return(*bufp & 0377);
}


void _sigalrm(int n)
{
alarmed = TRUE;
longjmp(jbuf, 1);
}





















































А. Богатырев, 1992-95 - 380 - Си в UNIX

/* ==================================================================== */
#else /* XENIX or USG */

/* Для семейства SYSTEM V */

static BOOLEAN alarmed;

int kgetch()
{
register struct try *ptr;
int ch;
char buffer[10]; /* Assume no sequences longer than 10 */
register char *bufp = buffer;
void (*oldsig)();
void _sigalrm();

ptr = _keytry;

oldsig = signal(SIGALRM, _sigalrm);
alarmed = FALSE;

do
{
ch = nextc();
if (ch != EOF) /* getc() returns EOF on error, too */
*(bufp++) = ch;
if (alarmed)
break;

while (ptr != (struct try *)NULL &&
(ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch)) ))
ptr = ptr->sibling;

if (ptr != (struct try *)NULL)
{
if (ptr->value != 0)
{
alarm(0);
signal(SIGALRM, oldsig);
return(ptr->value);
}
else
{
ptr = ptr->child;
alarm(1);
}
}

} while (ptr != (struct try *)NULL);

alarm(0);
signal(SIGALRM, oldsig);

if (ch == EOF && bufp == buffer)
return ch;
while (--bufp > buffer)
putback(*bufp);
return(*bufp & 0377);
}





А. Богатырев, 1992-95 - 381 - Си в UNIX

void _sigalrm(int n)
{
alarmed = TRUE;
signal(SIGALRM, _sigalrm);
}

#endif /*XENIX*/


/* ==================================================================== */
/*
** init_keytry()
** Построение дерева разбора последовательностей символов.
**
*/

void init_keytry()
{
_keytry = (struct try *) NULL;

add_to_try(key_backspace, KEY_BACKSPACE);
add_to_try("\b", KEY_BACKSPACE);
add_to_try("\177", KEY_BACKSPACE);

add_to_try(key_backtab, KEY_BACKTAB);
add_to_try(key_dc, KEY_DC);
add_to_try(key_dl, KEY_DL);
add_to_try(key_down, KEY_DOWN);

add_to_try(key_f1, KEY_F(1));
add_to_try(key_f2, KEY_F(2));
add_to_try(key_f3, KEY_F(3));
add_to_try(key_f4, KEY_F(4));
add_to_try(key_f5, KEY_F(5));
add_to_try(key_f6, KEY_F(6));
add_to_try(key_f7, KEY_F(7));
add_to_try(key_f8, KEY_F(8));
add_to_try(key_f9, KEY_F(9));
add_to_try(key_f10, KEY_F(10));
add_to_try(key_f11, KEY_F(11));
add_to_try(key_f12, KEY_F(12));
add_to_try(key_home, KEY_HOME);
add_to_try(key_ic, KEY_IC);
add_to_try(key_il, KEY_IL);
add_to_try(key_left, KEY_LEFT);
add_to_try(key_npage, KEY_PGDN);
add_to_try(key_ppage, KEY_PGUP);
add_to_try(key_right, KEY_RIGHT);
add_to_try(key_up, KEY_UP);
add_to_try(key_end, KEY_END);
}













А. Богатырев, 1992-95 - 382 - Си в UNIX

void add_to_try(char *str, short code)
{
static BOOLEAN out_of_memory = FALSE;
struct try *ptr, *savedptr;

if (str == NULL || out_of_memory)
return;

if (_keytry != (struct try *) NULL)
{
ptr = _keytry;

for (;;)
{
while (ptr->ch != *str && ptr->sibling != (struct try *)NULL)
ptr = ptr->sibling;

if (ptr->ch == *str)
{
if (*(++str))
{
if (ptr->child != (struct try *)NULL)
ptr = ptr->child;
else
break;
}
else
{
ptr->value = code;
return;
}
}
else
{
if ((ptr->sibling =
(struct try *) malloc(sizeof *ptr)) == (struct try *)NULL)
{
out_of_memory = TRUE;
return;
}

savedptr = ptr = ptr->sibling;
ptr->child = ptr->sibling = (struct try *)NULL;
ptr->ch = *str++;
ptr->value = 0;

break;
}
} /* end for (;;) */
}
else /* _keytry == NULL :: First sequence to be added */
{
savedptr = ptr = _keytry = (struct try *) malloc(sizeof *ptr);

if (ptr == (struct try *) NULL)
{
out_of_memory = TRUE;
return;
}

ptr->child = ptr->sibling = (struct try *) NULL;



А. Богатырев, 1992-95 - 383 - Си в UNIX

ptr->ch = *(str++);
ptr->value = 0;
}

/* at this point, we are adding to the try. ptr->child == NULL */

while (*str)
{
ptr->child = (struct try *) malloc(sizeof *ptr);

ptr = ptr->child;

if (ptr == (struct try *)NULL)
{
out_of_memory = TRUE;

ptr = savedptr;
while (ptr != (struct try *)NULL)
{
savedptr = ptr->child;
free(ptr);
ptr = savedptr;
}

return;
}

ptr->child = ptr->sibling = (struct try *)NULL;
ptr->ch = *(str++);
ptr->value = 0;
}

ptr->value = code;
return;
}


/* Включение альтернативного режима клавиатуры */
void keypad_on(){
if( keypadok ) return;
keypadok = TRUE;
if( keypad_xmit ) _puts( keypad_xmit );
}

/* Включение стандартного режима клавиатуры */
void keypad_off(){
if( !keypadok ) return;
keypadok = FALSE;
if( keypad_local ) _puts( keypad_local );
}














А. Богатырев, 1992-95 - 384 - Си в UNIX

/* Тестовая функция */
int dotest()
{
struct termios saved_modes;
int c;
char *s;
char keyname[20];

if( tcgetattr(INPUT_CHANNEL, &saved_modes) < 0 ){
err: keyboard_access_denied();
}
if( tcsetattr(INPUT_CHANNEL, TCSADRAIN, &new_modes) < 0 )
goto err;

keyreset();

for(;;){
c = getch();

switch(c){
case KEY_DOWN: s = "K_DOWN" ; break;
case KEY_UP: s = "K_UP" ; break;
case KEY_LEFT: s = "K_LEFT" ; break;
case KEY_RIGHT: s = "K_RIGHT" ; break;
case KEY_PGDN: s = "K_PGDN" ; break;
case KEY_PGUP: s = "K_PGUP" ; break;
case KEY_HOME: s = "K_HOME" ; break;
case KEY_END: s = "K_END" ; break;
case KEY_BACKSPACE: s = "K_BS" ; break;
case '\t': s = "K_TAB" ; break;
case KEY_BACKTAB: s = "K_BTAB" ; break;
case KEY_DC: s = "K_DEL" ; break;
case KEY_IC: s = "K_INS" ; break;
case KEY_DL: s = "K_DL" ; break;
case KEY_IL: s = "K_IL" ; break;

case KEY_F(1): s = "K_F1" ; break;
case KEY_F(2): s = "K_F2" ; break;
case KEY_F(3): s = "K_F3" ; break;
case KEY_F(4): s = "K_F4" ; break;
case KEY_F(5): s = "K_F5" ; break;
case KEY_F(6): s = "K_F6" ; break;
case KEY_F(7): s = "K_F7" ; break;
case KEY_F(8): s = "K_F8" ; break;
case KEY_F(9): s = "K_F9" ; break;
case KEY_F(10): s = "K_F10" ; break;
case KEY_F(11): s = "K_F11" ; break;
case KEY_F(12): s = "K_F12" ; break;

case ESC: s = "ESC" ; break;
case EOF: s = "K_EOF" ; break;
case '\r': s = "K_RETURN"; break;
case '\n': s = "K_ENTER" ; break;
default:
s = keyname;
if( c >= 0400 ){
sprintf(keyname, "K_F%d", c - KEY_F(0));
} else if( iscntrl(c)){
sprintf(keyname, "CTRL(%c)", c + 'A' - 1);
} else {
sprintf(keyname, "%c", c );



А. Богатырев, 1992-95 - 385 - Си в UNIX

}
}
printf("Клавиша: %s\n\r", s);

if(c == ESC)
break;
}
tcsetattr(INPUT_CHANNEL, TCSADRAIN, &saved_modes);
}


/* Функция настройки на систему команд дисплея */
void tinit (void) {
/* static */ char Tbuf[2048];
/* Tbuf должен сохраняться все время, пока могут вызываться функции tgetstr().
* Для этого он либо должен быть static, либо вызов функции keyinit()
* должен находиться внутри tinit(), что и сделано.
*/
char *tname;
extern char *getenv();

if((tname = getenv("TERM")) == NULL){
printf("TERM не определено: неизвестный тип терминала.\n");
exit(2);
}
printf("Терминал: %s\n", tname);

/* Прочесть описание терминала в Tbuf */
switch (tgetent(Tbuf, tname)) {
case -1:
printf ("Нет файла TERMCAP (/etc/termcap).\n");
exit (1);
case 0:
printf ("Терминал '%s' не описан.\n", tname);
exit (2);
case 1:
break; /* OK */
}
if(strlen(Tbuf) >= 1024)
printf("Описание терминала слишком длинное - возможны потери в конце описания\n");

keyinit(); /* инициализировать строки, пока Tbuf[] доступен */
}


void main(void){
setlocale(LC_ALL, "");
tinit();
/* keyinit(); */
dotest();
exit(0);
}

По поводу этого алгоритма надо сказать еще пару слов. Его модификация может с успехом
применяться для поиска слов в таблице (команд, ключей в базе данных, итп.): список
слов превращается в дерево. В таком поисковом алгоритме не требуются таймауты, необ-
ходимые при вводе с клавиатуры, поскольку есть явные терминаторы строк - символы
'\0', которых нет при вводе с клавиатуры. В чем эффективность такого алгоритма?
Сравним последовательный перебор при помощи strcmp и поиск в дереве букв:





А. Богатырев, 1992-95 - 386 - Си в UNIX

"zzzzzzzzzza"
"zzzzzzzzzzb"
"zzzzzzzzzzbx"
"zzzzzzzzzzc"
"zzzzzzzzzzcx"

Для линейного перебора (даже в отсортированном массиве) поиск строки zzzzzzzzzzcx
потребует

zzzzzzzzzza | 11 сравнений, отказ
zzzzzzzzzzb | 11 сравнений, отказ
zzzzzzzzzzbx | 12 сравнений, отказ
zzzzzzzzzzc | 11 сравнений, отказ
zzzzzzzzzzcx V 12 сравнений, успех

Всего: 57 шагов. Для поиска в дереве:

__z__z__z__z__z__z__z__z__z__z__a__\0
|_b__\0
| |_x__\0
|
|_c__\0
|_x__\0

потребуется проход вправо (вниз) на 10 шагов, потом выбор среди 'a','b','c', потом -
выбор среди '\0' и 'x'. Всего: 15 шагов. За счет того, что общий "корень" проходится
ровно один раз, а не каждый раз заново. Но это и требует предварительной подготовки
данных: превращения строк в дерево!

8.18. Напишите функцию для "экранного" редактирования вводимой строки в режиме
CBREAK. Напишите аналогичную функцию на curses-е. В curses-ной версии надо уметь
отрабатывать: забой (удаление символа перед курсором), отмену всей строки, смещение
влево/вправо по строке, удаление символа над курсором, вставку пробела над курсором,
замену символа, вставку символа, перерисовку экрана. Учтите, что параллельно с изме-
нением картинки в окне, вы должны вносить изменения в некоторый массив (строку),
которая и будет содержать результат. Эта строка должна быть аргументом функции редак-
тирования.
Забой можно упрощенно эмулировать как

addstr( "\b \b" );
или
addch( '\b' ); delch();

Недостатком этих способов является некорректное поведение в начале строки (при x==0).
Исправьте это!

8.19. На curses-е напишите функцию редактирования текста в окне. Функция должна
возвращать массив строк с обрезанными концевыми пробелами. Вариант: возвращать одну
строку, в которой строки окна разделяются символами '\n'.

8.20. Напишите функцию, рисующую прямую линию из точки (x1,y1) в (x2,y2). Указание:
используйте алгоритм Брезенхема (минимального отклонения). Ответ: пусть функция
putpixel(x,y,color) рисует точку в координатах (x,y) цветом color.

void line(int x1, int y1, int x2, int y2,
int color){
int dx, dy, i1, i2, i, kx, ky;
register int d; /* "отклонение" */
register int x, y;
short /* boolean */ l;




А. Богатырев, 1992-95 - 387 - Си в UNIX

dy = y2 - y1; dx = x2 - x1;
if( !dx && !dy ){
putpixel(x1,y1, color); return;
}
kx = 1; /* шаг по x */
ky = 1; /* шаг по y */
/* Выбор тактовой оси */
if( dx < 0 ){ dx = -dx; kx = -1; } /* Y */
else if( dx == 0 ) kx = 0; /* X */
if( dy < 0 ){ dy = -dy; ky = -1; }
if( dx < dy ){ l = 0; d = dx; dx = dy; dy = d; }
else l = 1;

i1 = dy + dy; d = i1 - dx; i2 = d - dx;
x = x1; y = y1;

for( i=0; i < dx; i++ ){
putpixel( x, y, color );

if( l ) x += kx; /* шаг по такт. оси */
else y += ky;
if( d < 0 ) /* горизонтальный шаг */
d += i1;
else{ /* диагональный шаг */
d += i2;
if( l ) y += ky; /* прирост высоты */
else x += kx;
}
}
putpixel(x, y, color); /* последняя точка */
}


8.21. Составьте программу, которая строит график функции sin(x) на отрезке от 0 до
2*пи. Учтите такие вещи: соседние точки графика следует соединять отрезком прямой,