Терминал в 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 вы можете увидеть в примере
"осыпающиеся буквы".
* - Под протоколом в программировании подразумевают ряд соглашений двух сторон (сервера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети "host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых
друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех людей состоит из одних и тех же звуков и может быть записана одними и теми же буквами
(а данные - байтами). Но если два человека говорят на разных языках - т.е. поразному конструируют фразы и интерпретируют звуки - они не поймут друг друга!
** - XENIX - (произносится "зиникс") версия UNIX для IBM PC, первоначально разработанная фирмой Microsoft и поставляемая фирмой Santa Cruz Operation (SCO).
© Copyright А. Богатырев, 1992-95
Назад | Содержание | Вперед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; /* что-то изменилось */
}
Си в UNIX