/*#include <sys/machdep.h> for XENIX and SCO UNIX only */
#include <sys/sysmacros.h>


____________________
|- XENIX - (произносится "зиникс") версия UNIX для IBM PC, первоначально разрабо-
танная фирмой Microsoft и поставляемая фирмой Santa Cruz Operation (SCO).





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

#ifdef M_I386
# define far /* на 32-битной машине far не требуется */
#endif

char far *screen; /* видеопамять как массив байт */
/* far - "длинный" (32-битный) адрес(segment,offset) */
int segm; /* сегмент с видеопамятью */

#define COLS 80 /* число колонок на экране */
#define LINES 25 /* число строк */

#define DELAY 20 /* задержка (миллисекунд) */
int ega; /* дескриптор для доступа к драйверу EGA */


/* структура для обмена с портами */
static struct port_io_struct PORT[ 4 /* не более 4 за раз */] = {
/* операция номер порта данные */
/* .dir .port .data */

/* Переустановить flip/flop:
* заставить порт 0x3C0 ожидать пары адрес/значение
* при последовательной записи байтов в этот порт.
*/
{ IN_ON_PORT, 0x3DA, -1 },
/* IN-чтение */
/* Теперь 3c0 ожидает пары адрес/значение */
{ OUT_ON_PORT, 0x3C0, -1 /* адрес */ },
{ OUT_ON_PORT, 0x3C0, -1 /* значение*/ },
/* OUT-запись */
/* переинициализировать дисплей, установив бит #5 порта 3c0 */
{ OUT_ON_PORT, 0x3C0, 0x20 }
};


void closescr(nsig){ /* конец работы */
setbgcolor(0); /* установить черный фон экрана */
exit(0);
}


/* получение доступа к видеопамяти адаптера VGA/EGA/CGA */
void openscr () {
static struct videodev {
char *dev; int mapmode;
} vd[] = {
{ "/dev/vga", MAPVGA },
{ "/dev/ega", MAPEGA },
{ "/dev/cga", MAPCGA },
{ NULL, -1 }
}, *v; /* устройство для доступа к видеоадаптеру */
for(v=vd; v->dev;v++ )
if((ega = open (v->dev, O_RDWR)) >= 0 ) goto ok;
fprintf( stderr, "Can't open video adapter\n" );
exit(1);









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

ok:
/* fprintf(stderr, "Adapter:%s\n", v->dev); */
/* получить адрес видеопамяти и доступ к ней */
#ifdef M_I386
screen = (char *) ioctl (ega, v->mapmode, 0);
#else
segm = ioctl (ega, v->mapmode, 0);
screen = sotofar (segm, 0); /* (segment,offset) to far pointer */
#endif
signal( SIGINT, closescr );
}


/* макросы для доступа к байтам "символ" и "атрибуты"
* в координатах (x,y) экрана.
*/
#define GET(x,y) screen[ ((x) + (y) * COLS ) * 2 ]
#define PUT(x,y, c) screen[ ((x) + (y) * COLS ) * 2 ] = (c)

#define GETATTR(x,y) screen[ ((x) + (y) * COLS ) * 2 + 1 ]
#define PUTATTR(x,y, a) screen[ ((x) + (y) * COLS ) * 2 + 1 ] = (a)

/* символ изображается как черный пробел ? */
#define white(c,a) ((isspace(c) || c==0) && (attr & 0160)==0)


/* установить цвет фона экрана */
void setbgcolor( color ){
PORT[1].data = 0; /* регистр номер 0 палитры содержит цвет фона */
/* всего в палитре 16 регистров (0x00...0xFF) */

PORT[2].data = color ;
/* новое значение цвета, составленное как битовая маска
* RGBrgb (r- красный, g- зеленый, b- синий, RGB- дополнительные
* тусклые цвета)
*/

/* выполнить обмены с портами */
if( ioctl( ega, EGAIO, PORT ) < 0 ){
fprintf( stderr, "Can't out port\n" );
perror( "out" );
}
}





















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

void main(ac, av) char **av;{
void fall();

openscr();
if( ac == 1 ){
setbgcolor(020); /* темно-зеленый фон экрана */
fall(); /* осыпание букв */
} else {
if(*av[1] == 'g')
/* Установить режим адаптера graphics 640x350 16-colors */
ioctl( ega, SW_CG640x350, NULL);
/* Если вы хотите получить адрес видеопамяти в графическом режиме,
* вы должны СНАЧАЛА включить этот режим,
* ЗАТЕМ сделать screen=ioctl(ega, v->mapmode, NULL);
* и ЕЩЕ РАЗ сделать включение графического режима.
*/
/* Установить режим адаптера text 80x25 16-colors */
else ioctl( ega, SW_ENHC80x25, NULL);
}
closescr(0);
}


/* осыпать буквы вниз */
void fall(){
register i, j;
int rest;
int nextcol;
int n;
int changed = 1; /* не 0, если еще не все буквы опали */
char mask [ COLS ];

while( changed ){
changed = 0;
for( i = 0 ; i < COLS ; i++ )
mask[ i ] = 0;

for( i = 0 ; i < COLS ; i++ ){
rest = COLS - i; /* осталось осыпать колонок */
nextcol = rand() % rest;


j = 0; /* индекс в mask */
n = 0; /* счетчик */

for(;;){
if( mask[j] == 0 ){
if( n == nextcol ) break;
n++;
} j++;
}

changed += fallColumn( j );
mask[j] = 1;
}
}
}







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

/* осыпать буквы в одном столбце */
int fallColumn( x ){
register int y;
char ch, attr;
int firstspace = (-1);
int firstnospace = (-1);

Again:
/* find the falled array */
for( y=LINES-1; y >= 0 ; y-- ){

ch = GET( x, y );
attr = GETATTR( x,y );

if( white(ch, attr)){
firstspace = y;
goto FindNoSpace;
}
}


AllNoSpaces:
return 0; /* ничего не изменилось */
FindNoSpace: /* найти не пробел */
for( ; y >= 0 ; y-- ){

ch = GET( x, y );
attr = GETATTR( x, y );

if( !white(ch, attr)){
firstnospace = y;
goto Fall;
}
}


AllSpaces: /* в данном столбце все упало */
return 0;
Fall:
/* "уронить" букву */
for( y = firstnospace ; y < firstspace ; y++ ){
/* переместить символ на экране на одну позицию вниз */
ch = GET( x, y );
attr = GETATTR( x, y );

PUT( x, y, 0 );
PUTATTR( x, y, 0 );

PUT( x, y+1 , ch );
PUTATTR( x, y+1, attr );

nap( DELAY ); /* подождать DELAY миллисекунд */
}
return 1; /* что-то изменилось */
}


8.2. Для работы может оказаться более удобным иметь указатель на видеопамять как на
массив структур. Приведем пример для системы MS DOS:





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

#include <dos.h> /* там определено MK_FP */
char far *screen =
MK_FP(0xB800 /*сегмент*/, 0x0000 /*смещение*/);
struct symb{
char chr; char attr;
} far *scr, far *ptr;
#define COLS 80 /* число колонок */
#define LINES 25 /* число строк */
#define SCR(x,y) scr[(x) + COLS * (y)]
/* x из 0..79, y из 0..24 */


void main(){
int x, y;
char c;
scr = (struct symb far *) screen;
/* или сразу
* scr = (struct symb far *) MK_FP(0xB800,0x0000);
*/


/* переписать строки экрана справа налево */
for(x=0; x < COLS/2; x++ )
for( y=0; y < LINES; y++ ){
c = SCR(x,y).chr;
SCR(x,y).chr = SCR(COLS-1-x, y).chr;
SCR(COLS-1-x, y).chr = c;
}


/* сделать цвет экрана: желтым по синему */
for(x=0; x < COLS; x++)
for(y=0; y < LINES; y++)
SCR(x,y).attr = (0xE | (0x1 << 4));
/* желтый + синий фон */

/* прочесть любую кнопку с клавиатуры (пауза) */
(void) getch();
}

И, наконец, еще удобнее работа с видеопамятью как с двумерным массивом структур:

#include <dos.h> /* MS DOS */
#define COLS 80
#define LINES 25
struct symb {
char chr; char attr;
} (far *scr)[ COLS ] = MK_FP(0xB800, 0);


void main(void){
register x, y;
for(y=0; y < LINES; y++)
for(x=0; x < COLS; ++x){
scr[y][x].chr = '?';
scr[y][x].attr = (y << 4) | (x & 0xF);
}
getch();
}

Учтите, что при работе с экраном через видеопамять, курсор не перемещается! Если в



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

обычной работе с экраном текст выводится в позиции курсора и курсор автоматически
продвигается, то здесь курсор будет оставаться на своем прежнем месте. Для перемеще-
ния курсора в нужное вам место, вы должны его поставить явным образом по окончании
записи в видеопамять (например, обращаясь к портам видеоконтроллера).
Обратите внимание, что спецификатор модели памяти far должен указываться перед
КАЖДЫМ указателем (именно для иллюстрации этого в первом примере описан неиспользуе-
мый указатель ptr).

8.3. Составьте программу сохранения содержимого экрана IBM PC (видеопамяти) в текс-
товом режиме в файл и обратно (в системе XENIX).

8.4. Пользуясь прямым доступом в видеопамять, напишите функции для спасения прямоу-
гольной области экрана в массив и обратно. Вот функция для спасения в массив:

typedef struct {
short xlen, ylen;
char *save;
} Pict;
extern void *malloc(unsigned);

Pict *gettext (int x, int y, int xlen, int ylen){
Pict *n = (Pict *) malloc(sizeof *n);
register char *s; register i, j;

n->xlen = xlen; n->ylen = ylen;
s = n->save = (char *) malloc( 2 * xlen * ylen );
for(i=y; i < y+ylen; i++)
for(j=x; j < x+xlen; j++){
*s++ = SCR(j,i).chr ;
*s++ = SCR(j,i).attr;
}
return n;
}

Добавьте проверки на корректность xlen, ylen (в пределах экрана). Напишите функцию
puttext для вывода спасенной области обратно; функцию free(buf) лучше в нее не встав-
лять.

void puttext (Pict *n, int x, int y){
register char *s = n->save;
register i, j;
for(i=y; i < y + n->ylen; i++)
for(j=x; j < x + n->xlen; j++){
SCR(j,i).chr = *s++;
SCR(j,i).attr = *s++;
}
}
/* очистка памяти текстового буфера */
void deltext(Pict *n){ free(n->save); free(n); }

Приведем еще одну полезную функцию, которая может вам пригодиться - это аналог printf
при прямой работе с видеопамятью.

#include <stdarg.h>
/* текущий цвет: белый по синему */
static char currentColor = 0x1F;

int videoprintf (int x, int y, char *fmt, ...){
char buf[512], *s;
va_list var;




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

/* clipping (отсечение по границам экрана) */
if( y < 0 || y >= LINES ) return x;

va_start(var, fmt);
vsprintf(buf, fmt, var);
va_end(var);

for(s=buf; *s; s++, x++){
/* отсечение */
if(x < 0 ) continue;
if(x >= COLS) break;
SCR(x,y).chr = *s;
SCR(x,y).attr = currentColor;
}
return x;
}
void setcolor (int col){ currentColor = col; }


8.5. Пользуясь написанными функциями, реализуйте функции для "выскакивающих" окон
(pop-up window):

Pict *save;
save = gettext (x,y,xlen,ylen);

// ... рисуем цветными пробелами прямоугольник с
// углами (x,y) вверху-слева и (x+xlen-1,y+ylen-1)
// внизу-справа...

// ...рисуем некие таблицы, меню, текст в этой зоне...

// стираем нарисованное окно, восстановив то изображение,
// поверх которого оно "всплыло".
puttext (save,x,y);
deltext (save);

Для начала напишите "выскакивающее" окно с сообщением; окно должно исчезать по нажа-
тию любой клавиши.

c = message(x, y, text);

Размер окна вычисляйте по длине строки text. Код клавиши возвращайте в качестве зна-
чения функции.
Теперь сделайте text массивом строк: char *text[]; (последняя строка - NULL).

8.6. Сделайте так, чтобы "выскакивающие" окна имели тень. Для этого надо сохранить в
некоторый буфер атрибуты символов (сами символы не надо!), находящихся на местах $:

##########
##########$
##########$
$$$$$$$$$$

а затем прописать этим символам на экране атрибут 0x07 (белый по черному). При стира-
нии окна (puttext-ом) следует восстановить спасенные атрибуты этих символов (стереть
тень). Если окно имеет размер xlen*ylen, то размер буфера равен xlen+ylen-1 байт.

8.7. Напишите функцию, рисующую на экране прямоугольную рамку. Используйте ее для
рисования рамки окна.





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

8.8. Напишите "выскакивающее" окно, которое проявляется на экране как бы расширяясь
из точки:

##############
###### ##############
### ###### ##############
###### ##############
##############

Вам следует написать функцию box(x,y,width,height), рисующую цветной прямоугольник с
верхним левым углом (x,y) и размером (width,height). Пусть конечное окно задается
углом (x0,y0) и размером (W,H). Тогда "вырастание" окна описывается таким алгоритмом:

void zoom(int x0, int y0, int W, int H){
int x, y, w, h, hprev; /* промежуточное окно */
for(hprev=0, w=1; w < W; w++){
h = H * w; h /= W; /* W/H == w/h */
if(h == hprev) continue;
hprev = h;
x = x0 + (W - w)/2; /* чтобы центры окон */
y = y0 + (H - h)/2; /* совпадали */
box(x, y, w, h);
delay(10); /* задержка 10 миллисек. */
}
box(x0, y0, W, H);
}


8.9. Составьте библиотеку функций, аналогичных библиотеке curses, для ЭВМ IBM PC в
ОС XENIX. Используйте прямой доступ в видеопамять.

8.10. Напишите рекурсивное решение задачи "ханойские башни" (перекладывание дисков:
есть три стержня, на один из них надеты диски убывающего к вершине диаметра. Требу-
ется переложить их на третий стержень, никогда не кладя диск большего диаметра поверх
диска меньшего диаметра). Усложнение - используйте пакет curses для изображения
перекладывания дисков на экране терминала. Указание: идея рекурсивного алгоритма:

carry(n, from, to, by) = if( n > 0 ){
carry( n-1, from, by, to );
перенесиОдинДиск( from, to );
carry( n-1, by, to, from );
}
Вызов: carry( n, 0, 1, 2 );
n - сколько дисков перенести (n > 0).
from - откуда (номер стержня).
to - куда.
by - при помощи (промежуточный стержень).

n дисков потребуют (2**n)-1 переносов.

8.11. Напишите программу, ищущую выход из лабиринта ("червяк в лабиринте"). Лаби-
ринт загружается из файла .maze (не забудьте про расширение табуляций!). Алгоритм
имеет рекурсивную природу и выглядит примерно так:

#include <setjmp.h>
jmp_buf jmp; int found = 0;

maze(){ /* Это головная функция */
if( setjmp(jmp) == 0 ){ /* начало */
if( неСтенка(x_входа, y_входа))
GO( x_входа, y_входа);



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

}
}
GO(x, y){ /* пойти в точку (x, y) */
if( этоВыход(x, y)){ found = 1; /* нашел выход */
пометить(x, y); longjmp(jmp, 1);}
пометить(x, y);
if( неСтенка(x-1,y)) GO(x-1, y); /* влево */
if( неСтенка(x,y-1)) GO(x, y-1); /* вверх */
if( неСтенка(x+1,y)) GO(x+1, y); /* вправо */
if( неСтенка(x,y+1)) GO(x, y+1); /* вниз */
снятьПометку(x, y);
}
#define пометить(x, y) лабиринт[y][x] = '*'
#define снятьПометку(x, y) лабиринт[y][x] = ' '
#define этоВыход(x, y) (x == x_выхода && y == y_выхода)
/* можно искать "золото": (лабиринт[y][x] == '$') */

неСтенка(x, y){ /* стенку изображайте символом @ или # */
if( координатыВнеПоля(x, y)) return 0; /*край лабиринта*/
return (лабиринт[y][x] == ' ');
}

Отобразите массив лабиринт на видеопамять (или воспользуйтесь curses-ом). Вы увидите
червяка, ползающего по лабиринту в своих исканиях.

8.12. Используя библиотеку termcap напишите функции для:
- очистки экрана.
- позиционирования курсора.
- включения/выключения режима выделения текста инверсией.

8.13. Используя написанные функции, реализуйте программу выбора в меню. Выбранную
строку выделяйте инверсией фона.

/*#!/bin/cc termio.c -O -o termio -ltermcap
* Смотри man termio, termcap и screen.
* Работа с терминалом в стиле System-V.
* Работа с системой команд терминала через /etc/termcap
* Работа со временем.
* Работа с будильником.
*/

#include <stdio.h> /* standard input/output */
#include <sys/types.h> /* system typedefs */
#include <termio.h> /* terminal input/output */
#include <signal.h> /* signals */
#include <fcntl.h> /* file control */
#include <time.h> /* time structure */

void setsigs(), drawItem(), drawTitle(), prSelects(), printTime();















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

/* Работа с описанием терминала TERMCAP ---------------------------------*/
extern char *getenv (); /* получить переменную окружения */
extern char *tgetstr (); /* получить строчный описатель /termcap/ */
extern char *tgoto (); /* подставить %-параметры /termcap/ */

static char Tbuf[2048], /* буфер для описания терминала, обычно 1024 */
/* Tbuf[] можно сделать локальной автоматической переменной
* в функции tinit(), чтобы не занимать место */
Strings[256], /* буфер для расшифрованных описателей */
*p; /* вспомогательная перем. */
char *tname; /* название типа терминала */
int COLS, /* число колонок экрана */
LINES; /* число строк экрана */
char *CM; /* описатель: cursor motion */
char *CL; /* описатель: clear screen */
char *CE; /* описатель: clear end of line */
char *SO,
*SE; /* описатели: standout Start и End */
char *BOLD,
*NORM; /* описатели: boldface and NoStandout */
int BSflag; /* можно использовать back space '\b' */


void tinit () { /* Функция настройки на систему команд дисплея */
p = Strings;
/* Прочесть описание терминала в Tbuf */
switch (tgetent (Tbuf, tname = getenv ("TERM"))) {
case -1:
printf ("Нет файла TERMCAP (/etc/termcap).\n");
exit (1);
case 0:
printf ("Терминал %s не описан.\n", tname);
exit (2);
case 1:
break; /* OK */
}
COLS = tgetnum ("co"); /* Прочесть числовые описатели. */
LINES = tgetnum ("li");

CM = tgetstr ("cm", &p); /* Прочесть строчные описатели. */
CL = tgetstr ("cl", &p); /* Описатель дешифруется и заносится */
CE = tgetstr ("ce", &p); /* в массив по адресу p. Затем */
SO = tgetstr ("so", &p); /* указатель p продвигается на */
SE = tgetstr ("se", &p); /* свободное место, а адрес расшиф- */
BOLD = tgetstr ("md", &p); /* рованной строки выдается из ф-ции */
NORM = tgetstr ("me", &p);

BSflag = tgetflag( "bs" ); /* Узнать значение флажка:
1 - есть, 0 - нет */
}














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

/* Макрос, внесенный в функцию.
Дело в том, что tputs в качестве третьего аргумента
требует имя функции, которую она вызывает в цикле: (*f)(c);
Если подать на вход макрос, вроде putchar,
а не адрес входа в функцию, мы
и не достигнем желанного эффекта,
и получим ругань от компилятора.
*/
void put (c) char c;
{ putchar (c); }


/* очистить экран */
void clearScreen () {
if (CL == NULL) /* Функция tputs() дорасшифровывает описатель */
return; /* (обрабатывая задержки) и выдает его */
tputs (CL, 1, put); /* посимвольно ф-цией put(c) 1 раз */
/* Можно выдать команду не 1 раз, а несколько: например если это */
/* команда сдвига курсора на 1 позицию влево '\b' */
}


/* очистить конец строки, курсор остается на месте */
void clearEOL () { /* clear to the end of line */
if (CE == NULL)
return;
tputs (CE, 1, put);
}


/* позиционировать курсор */
void gotoXY (x, y) { /* y - по вертикали СВЕРХУ-ВНИЗ. */
if (x < 0 || y < 0 || x >= COLS || y >= LINES) {
printf ("Точка (%d,%d) вне экрана\n", x, y);
return;
}
/* CM - описатель, содержащий 2 параметра. Подстановку параметров
* делает функция tgoto() */
tputs (tgoto (CM, x, y), 1, put);
}


/* включить выделение */
void standout () {
if (SO) tputs (SO, 1, put);
}


/* выключить выделение */
void standend () {
if (SE) tputs (SE, 1, put);
/* else normal(); */
}


/* включить жирный шрифт */
void bold () {
if (BOLD) tputs (BOLD, 1, put);
}





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

/* выключить любой необычный шрифт */
void normal () {
if (NORM) tputs (NORM, 1, put);
else standend();
}


/* Управление драйвером терминала --------------------------------- */

#define ESC '\033'
#define ctrl(c) ((c) & 037 )

int curMode = 0;
int inited = 0;

struct termio old,
new;
int fdtty;

void ttinit () {
/* открыть терминал в режиме "чтение без ожидания" */
fdtty = open ("/dev/tty", O_RDWR | O_NDELAY);

/* узнать текущие режимы драйвера */
ioctl (fdtty, TCGETA, &old);

new = old;

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

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

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

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

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

setsigs ();
inited = 1; /* уже инициализировано */
}



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

void openVisual () { /* open visual mode (включить "экранный" режим) */
if (!inited)
ttinit ();
if (curMode == 1)
return;

/* установить моды драйвера из структуры new */
ioctl (fdtty, TCSETAW, &new);
curMode = 1; /* экранный режим */
}


void closeVisual () { /* canon mode (включить канонический режим) */
if (!inited)
ttinit ();
if (curMode == 0)
return;

ioctl (fdtty, TCSETAW, &old);
curMode = 0; /* канонический режим */
}


/* завершить процесс */
void die (nsig) {
normal();
closeVisual (); /* При завершении программы (в том числе по
* сигналу) мы должны восстановить прежние режимы драйвера,
* чтобы терминал оказался в корректном состоянии. */
gotoXY (0, LINES - 1);
putchar ('\n');
if (nsig)
printf ("Пришел сигнал #%d\n", nsig);
exit (nsig);
}


void setsigs () {
register ns;

/* Перехватывать все сигналы; завершаться по ним. */
/* UNIX имеет 15 стандартных сигналов. */
for (ns = 1; ns <= 15; ns++)
signal (ns, die);
}



















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

/* Работа с меню -------------------------------------------- */

struct menu {
char *m_text; /* выдаваемая строка */
int m_label; /* помечена ли она ? */
} menuText[] = {
/* названия песен Beatles */
{ "Across the Universe", 0 } ,
{ "All I've got to do", 0 } ,
{ "All my loving", 0 } ,
{ "All together now", 0 } ,
{ "All You need is love",0 } ,
{ "And I love her", 0 } ,
{ "And your bird can sing", 0 } ,
{ "Another girl", 0 } ,
{ "Any time at all", 0 } ,
{ "Ask me why", 0 } ,
{ NULL, 0 }
};


#define Y_TOP 6
int nitems; /* количество строк в меню */
int nselected = 0; /* количество выбранных строк */

char title[] =
"ПРОБЕЛ - вниз, ЗАБОЙ - вверх, ESC - выход, \
ENTER - выбрать, TAB - отменить";

# define TIMELINE 1

void main (ac, av) char **av; {
char **line;
register i;
int c;
int n; /* текущая строка */
extern char readkey (); /* forward */

extern char *ttyname (); /* имя терминала */
char *mytty;

extern char *getlogin (); /* имя пользователя */
char *userName = getlogin ();

srand (getpid () + getuid ()); /* инициализировать
* датчик случайных чисел */
/* считаем строки меню */
for (nitems = 0; menuText[nitems].m_text != NULL; nitems++);

/* инициализируем терминал */
tinit (); ttinit();
mytty = ttyname(fdtty);
openVisual ();











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

again:
clearScreen ();
if (mytty != NULL && userName != NULL) {
gotoXY (0, TIMELINE);
bold ();
printf ("%s", userName);
normal ();
printf (" at %s (%s)", mytty, tname);
}

drawTitle ("", Y_TOP - 4);
drawTitle (title, Y_TOP - 3);
drawTitle ("", Y_TOP - 2);

/* рисуем меню */
for (i = 0; i < nitems; i++) {
drawItem (i, 20, Y_TOP + i, 0);
}


/* цикл перемещений по меню */
for (n=0; ; ) {
printTime (); /* выдаем текущее время */
drawItem (n, 20, Y_TOP + n, 1);

c = getcharacter ();

drawItem (n, 20, Y_TOP + n, 0);


switch (c) {
case ' ':
go_down:
n++;
if (n == nitems)
n = 0;
break;


case '\b': case 0177:
n--;
if (n < 0)
n = nitems - 1;
break;

case ESC:
goto out;


case '\t': /* Unselect item */
if (menuText[n].m_label != 0) {
menuText[n].m_label = 0;
drawItem (n, 20, Y_TOP + n, 0);
nselected--;
prSelects ();
}
goto go_down;







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

case '\r': /* Select item */
case '\n':
bold ();
drawTitle (menuText[n].m_text, LINES - 2);
/* last but two line */
normal ();

if (menuText[n].m_label == 0) {
menuText[n].m_label = 1;
drawItem (n, 20, Y_TOP + n, 0);
nselected++;
prSelects ();
}
goto go_down;


default:
goto go_down;
}
}


out:
clearScreen ();

gotoXY (COLS / 3, LINES / 2);
bold ();
printf ("Нажми любую кнопку.");
normal ();

/* замусорить экран */
while (!(c = readkey ())) {
/* случайные точки */
gotoXY (rand () % (COLS - 1), rand () % LINES);
putchar ("@.*"[rand () % 3]); /* выдать символ */
fflush (stdout);
}


standout ();
printf ("Нажата кнопка с кодом 0%o\n", c & 0377);
standend ();

if (c == ESC) {
sleep (2); /* подождать 2 секунды */
goto again;
}

die (0); /* успешно завершиться,
* восстановив режимы драйвера */
}













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

/* Нарисовать строку меню номер i
* в координатах (x,y) с или без выделения
*/
void drawItem (i, x, y, out) {
gotoXY (x, y);
if (out) {
standout ();
bold ();
}
printf ("%c %s ",
menuText[i].m_label ? '-' : ' ', /* помечено или нет */
menuText[i].m_text /* сама строка */
);

if (out) {
standend ();
normal ();
}
}


/* нарисовать центрированную строку в инверсном изображении */
void drawTitle (title, y) char *title; {
register int n;
int length = strlen (title); /* длина строки */

gotoXY (0, y);
/* clearEOL(); */
standout ();

for (n = 0; n < (COLS - length) / 2; n++)
putchar (' ');

printf ("%s", title); n += length;

/* дорисовать инверсией до конца экрана */
for (; n < COLS - 1; n++)
putchar (' ');
standend ();
}


/* выдать общее число выбранных строк */
void prSelects () {
char buffer[30];

if (nselected == 0) {
gotoXY (0, LINES - 1);
clearEOL ();
}
else {
sprintf (buffer, "Выбрано: %d/%d", nselected, nitems);
drawTitle (buffer, LINES - 1);
}
}









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

/* Работа с будильником -------------------------- */

#define PAUSE 4
int alarmed; /* флаг будильника */

/* реакция на сигнал "будильник" */
void onalarm (nsig) {
alarmed = 1;
}

/* Прочесть символ с клавиатуры, но не позже чем через PAUSE секунд.
* иначе вернуть код 'пробел'.
*/
int getcharacter () {
int c;

fflush(stdout);

/* заказать реакцию на будильник */
signal (SIGALRM, onalarm);
alarmed = 0; /* сбросить флаг */

/* заказать сигнал "будильник" через PAUSE секунд */
alarm (PAUSE);

/* ждать нажатия кнопки.
* Этот оператор завершится либо при нажатии кнопки,
* либо при получении сигнала.
*/
c = getchar ();

/* проверяем флаг */
if (!alarmed) { /* был нажат символ */
alarm (0); /* отменить заказ будильника */
return c;
}

/* был получен сигнал "будильник" */
return ' '; /* продвинуть выбранную строку вниз */
}


/* ---- NDELAY read ----------------------------- */

/* Вернуть 0 если на клавиатуре ничего не нажато,
* иначе вернуть нажатую кнопку
*/
char readkey () {
char c;
int nread;

nread = read (fdtty, &c, 1);
/* обычный read() дожидался бы нажатия кнопки.
* O_NDELAY позволяет не ждать, но вернуть "прочитано 0 символов".
*/
return (nread == 0) ? 0 : c;
}







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