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


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

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

  • информация может быть выведена в любое место экрана, а не только последовательно строка за строкой.
  • некоторые терминалы позволяют прочесть содержимое произвольной области экрана в вашу программу.

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

  • настройка на систему команд данного устройства, чтобы одна и та же программа работала на разных типах терминалов.
  • эмуляция недостающих в системе команд; максимальное использование предоставленных терминалом возможностей.
  • мимнимизация передачи данных через линию связи (для ускорения работы).
  • было бы полезно, чтобы библиотека предоставляла пользователю некоторые логические абстракции, вроде ОКОН - прямоугольных областей на экране, ведущих себя подобно маленьким терминалам.

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

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


    cc progr.c -Ox -o progr -lcurses -lm

Здесь подключаются две библиотеки:
  • /usr/lib/libcurses.a (работа с экраном) и
  • /usr/lib/libm.a (математические функции, вроде sin, fabs). Ключи для подключения библиотек должны быть записаны в команде САМЫМИ ПОСЛЕДНИМИ. Заметим, что стандартная библиотека языка Си (содержащая системные вызовы, библиотеку stdio (функции printf, scanf, fread, fseek, ...), разные часто употребляемые функции (strlen, strcat, sleep, malloc, rand, ...)) /lib/libc.a подключается автоматически и не требует указания ключа -lc.

В начале своей программы вы должны написать директиву


    #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).

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

Используются технологии uCoz