8. Экранные библиотеки и работа с видеопамятью.

Терминал в UNIX с точки зрения программ - это файл. Он представляет собой два устройства: при записи write() в этот файл осуществляется вывод на экран; при чтении read()-ом из этого файла - читается информация с клавиатуры.

Современные терминалы в определенном смысле являются устройствами прямого доступа:

Традиционные терминалы являются самостоятельными устройствами, общающимися с компьютером через линию связи. Протокол* - общения образует систему команд терминала и может быть различен для терминалов разных моделей. Поэтому библиотека работы с традиционным терминалом должна решать следующие проблемы:

В UNIX эти задачи решает стандартная библиотека curses (а только первую задачу более простая библиотека termcap). Для настройки на систему команд конкретного дисплея эти библиотеки считывают описание системы команд, хранящееся в файле /etc/termcap. Кроме них бывают и другие экранные библиотеки, а также существуют иные способы работы с экраном (через видеопамять, см. ниже).

В задачах данного раздела вам придется пользоваться библиотекой curses. При компиляции программ эта библиотека подключается при помощи указания ключа -lcurses, как в следующем примере: cc progr.c -Ox -o progr -lcurses -lm Здесь подключаются две библиотеки:

В начале своей программы вы должны написать директиву #include <curses.h> подключающую файл /usr/include/curses.h, в котором описаны форматы данных, используемых библиотекой curses, некоторые предопределенные константы и.т.п. (это надо, чтобы ваша программа пользовалась именно этими стандартными соглашениями). Посмотрите в этот файл!

Когда вы пользуетесь curses-ом, вы НЕ должны пользоваться функциями стандартной библиотеки stdio для непосредственного вывода на экран; так вы не должны пользоваться функциями printf, putchar. Это происходит потому, что curses хранит в памяти процесса копию содержимого экрана, и если вы выводите что-либо на экран терминала обходя функции библиотеки curses, то реальное содержимое экрана и позиция курсора на нем перестают соответствовать хранимым в памяти, и библиотека curses начнет выводить неправильное изображение. ПРОГРАММА | | | CURSES---копия экрана | printw,addch,move | | V V библиотека STDIO --printf,putchar----> экран Таким образом, curses является дополнительным "слоем" между вашей программой и стандартным выводом и игнорировать этот слой не следует.

Напомним, что изображение, создаваемое при помощи библиотеки curses, сначала формируется в памяти программы без выполнения каких-либо операций с экраном дисплея (т.е. все функции wmove, waddch, waddstr, wprintw изменяют только ОБРАЗЫ окон в памяти, а на экране ничего не происходит!). И лишь только ПОСЛЕ того, как вы вызовете функцию refresh() ("обновить"), все изменения происшедшие в окнах будут отображены на экране дисплея (такое одновременное обновление всех изменившихся частей экрана позволяет провести ряд оптимизаций). Если вы забудете сделать refresh - экран останется неизменным. Обычно эту функцию вызывают перед тем, как запросить у пользователя какой-либо ввод с клавиатуры, чтобы пользователь увидел текущую "свежую" картинку. Хранение содержимого окон в памяти программы позволяет ей считывать содержимое окон, тогда как большинство обычных терминалов не способны выдать в компьютер содержимое какой-либо области экрана.

Общение с терминалом через линию связи (или вообще через последовательный протокол) является довольно медленным. На персональных компьютерах существует другой способ работы с экраном: через прямой доступ в так называемую "видеопамять" - специальную область памяти компьютера, содержимое которой аппаратно отображается на экране консоли. Работа с экраном превращается для программиста в работу с этим массивом байт (запись/чтение). Программы, пользующиеся этим способом, просты и работают очень быстро (ибо доступ к памяти черезвычайно быстр, и сделанные в ней изменения "проявляются" на экране почти мгновенно). Недостаток таких программ - привязанность к конкретному типу машины. Эти программы немобильны и не могут работать ни на обычных терминалах (подключаемых к линии связи), ни на машинах с другой структурой видеопамяти.

Выбор между "традиционной" работой с экраном и прямым доступом (фактически - между мобильностью и скоростью) - вопрос принципиальный, тем не менее принятие решения зависит только от вас. Видеопамять IBM PC в текстовом режиме 80x25 16 цветов имеет следующую структуру: struct symbol{ /* IBM PC family */ char chr; /* код символа */ char attr; /* атрибуты символа (цвет) */ } mem[ 25 ] [ 80 ]; /* 25 строк по 80 символов */ Структура байта атрибутов: ------------------------------------------ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # бита ------------------|----------------------- |blink| R | G | B | intensity | r | g | b | цвет ------------------|----------------------- background (фон) | foreground (цвет букв) R - red (красный) G - green (зеленый) B - blue (синий)
blink - мерцание букв (не фона!)
intensity - повышенная яркость

Координатная система на экране: верхний левый угол экрана имеет координаты (0,0), ось X горизонтальна, ось Y вертикальна и направлена сверху вниз.

Цвет символа получается смешиванием 3х цветов: красного, зеленого и синего (электронно-лучевая трубка дисплея имеет 3 электронные пушки, отвечающие этим цветам). Кроме того, допустимы более яркие цвета. 4 бита задают комбинацию 3х основных цветов и повышенной яркости. Образуется 2**4=16 цветов: I R G B номер цвета BLACK 0 0 0 0 0 черный BLUE 0 0 0 1 1 синий GREEN 0 0 1 0 2 зеленый CYAN 0 0 1 1 3 циановый (серо-голубой) RED 0 1 0 0 4 красный MAGENTA 0 1 0 1 5 малиновый BROWN 0 1 1 0 6 коричневый LIGHTGRAY 0 1 1 1 7 светло-серый (темно-белый) DARKGRAY 1 0 0 0 8 темно-серый LIGHTBLUE 1 0 0 1 9 светло-синий LIGHTGREEN 1 0 1 0 10 светло-зеленый LIGHTCYAN 1 0 1 1 11 светло-циановый LIGHTRED 1 1 0 0 12 ярко-красный LIGHTMAGENTA 1 1 0 1 13 ярко-малиновый YELLOW 1 1 1 0 14 желтый WHITE 1 1 1 1 15 (ярко)-белый

Физический адрес видеопамяти IBM PC в цветном алфавитно-цифровом режиме (80x25, 16 цветов) равен 0xB800:0x0000. В MS DOS указатель на эту память можно получить при помощи макроса make far pointer: MK_FP (это должен быть far или huge указатель!). В XENIX** - указатель получается при помощи системного вызова ioctl, причем система предоставит вам виртуальный адрес, ибо привелегия работы с физическими адресами в UNIX принадлежит только системе. Работу с экраном в XENIX вы можете увидеть в примере "осыпающиеся буквы".

8.1.

/*#! /bin/cc fall.c -o fall -lx * "Осыпающиеся буквы". * Использование видеопамяти IBM PC в ОС XENIX. * Данная программа иллюстрирует доступ к экрану * персонального компьютера как к массиву байт; * все изменения в массиве немедленно отображаются на экране. * Функция nap() находится в библиотеке -lx * Показана также работа с портами IBM PC при помощи ioctl(). */ #include <stdio.h> #include <fcntl.h> /* O_RDWR */ #include <signal.h> #include <ctype.h> #include <sys/types.h> #include <sys/at_ansi.h> #include <sys/kd.h> /* for System V/4 and Interactive UNIX only */ /*#include <sys/machdep.h> for XENIX and SCO UNIX only */ #include <sys/sysmacros.h> #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); 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" ); } } 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; } } } /* осыпать буквы в одном столбце */ 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; /* что-то изменилось */ }

* - Под протоколом в программировании подразумевают ряд соглашений двух сторон (сервера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети "host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех людей состоит из одних и тех же звуков и может быть записана одними и теми же буквами (а данные - байтами). Но если два человека говорят на разных языках - т.е. поразному конструируют фразы и интерпретируют звуки - они не поймут друг друга!

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

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

Назад | Содержание | Вперед