Назад

Содержание

Вперед


2.1.6. Первый пример.

    Продолжая традиции многих изданий, посвященных программированию на С, мы начинаем с программы, рисующей на экране строку "Hello, world!"'. В этом примере приведены основные шаги, необходимые для работы в X Window. ( xhello.tgz)

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <string.h>

#define WND_X 0
#define WND_Y 0
#define WND_WDT 100
#define WND_HGH 100
#define WND_MIN_WDT 50
#define WND_MIN_HGH 50
#define WND_BORDER_WDT 5

#define WND_TITLE "Hello!"
#define WND_ICON_TITLE "Hello!"
#define PRG_CLASS "Hello!"

/*
* SetWindowManagerHints - процедура передает информацию о
* свойствах программы менеджеру окон. 
*/

static void SetWindowManagerHints ( 
  Display *   prDisplay,  /*Указатель на структуру Display */
  char *      psPrgClass, /*Класс программы */
  char *      argv[],     /*Аргументы программы */
  int         argc,       /*Число аргументов */
  Window      nWnd,       /*Идентификатор окна */
  int         x,          /*Координаты левого верхнего */
  int         y,          /*угла окна */
  int         nWidth,
  int         nHeight,    /*Ширина и высота окна */
  int         nMinWidth,
  int         nMinHeight, /*Минимальные ширина и высота окна */
  char *      psTitle,    /*Заголовок окна */
  char *      psIconTitle,/*Заголовок пиктограммы окна */
  Pixmap      nIconPixmap /*Рисунок пиктограммы */
)
{
  XSizeHints rSizeHints; /*Рекомендации о размерах окна*/

#ifdef X11R3 /*. X11R3 и ниже */
  rSizeHints.flags = PPosition | PSize | PMinSize;
  rSizeHints.x = x;
  rSizeHints.y = y;
  rSizeHints.width = nWidth;
  rSizeHints.height = nHeight;
  rSizeHints.min_width = nMinWidth;
  rSizeHints.min_height = nMinHeight;

  XSetStandardProperties ( prDisplay, nWnd, psTitle,
      psIconTitle, nIconPixmap, argv, argc, &rSizeHints );
#else /* X11R4 и выше */
  XWMHints rWMHints;
  XClassHint rClassHint;
  XTextProperty prWindowName, prIconName;

  if ( !XStringListToTextProperty (&psTitle, 1, &prWindowName ) ||
       !XStringListToTextProperty (&psIconTitle, 1, &prIconName ) ) {
    puts ( "No memory!\n");
    exit ( 1 );
}

rSizeHints.flags = PPosition | PSize | PMinSize;
rSizeHints.min_width = nMinWidth;
rSizeHints.min_height = nMinHeight;
rWMHints.flags = StateHint | IconPixmapHint |
                 InputHint;
rWMHints.initial_state = NormalState;
rWMHints.input = True;
rWMHints.icon_pixmap= nIconPixmap;

rClassHint.res_name = argv[0];
rClassHint.res_class = psPrgClass;

XSetWMProperties ( prDisplay, nWnd, &prWindowName,
    &prIconName, argv, argc, &rSizeHints, &rWMHints,
    &rClassHint );
#endif
}

/*
*main - основная процедура программы
*/

void main ( int argc, char * argv[] )
{
  Display *prDisplay;    /* Указатель на структуру Display */
  int nScreenNum;        /* Номер экрана */
  GC prGC;
  XEvent rEvent;
  Window nWnd;

  /* Устанавливаем связь с сервером */
  if ( ( prDisplay = XOpenDisplay ( NULL ) ) == NULL ) {
    puts ("Can not connect to the X server!\n");
    exit ( 1 );
  }

  /* Получаем номер основного экрана */
  nScreenNum = DefaultScreen ( prDisplay );

  /* Создаем окно */
  nWnd = XCreateSimpleWindow ( prDisplay,
         RootWindow ( prDisplay, nScreenNum ),
         WND_X, WND_Y, WND_WDT, WND_HGH, WND_BORDER_WDT,
         BlackPixel ( prDisplay, nScreenNum ),
         WhitePixel ( prDisplay, nScreenNum ) );

  /* Задаем рекомендации для менеджера окон */
  SetWindowManagerHints ( prDisplay, PRG_CLASS, argv, argc,
      nWnd, WND_X, WND_Y, WND_WDT, WND_HGH, WND_MIN_WDT,
      WND_MIN_HGH, WND_TITLE, WND_ICON_TITLE, 0 );

  /* Выбираем события, обрабатываемые программой */
  XSelectInput ( prDisplay, nWnd, ExposureMask | KeyPressMask );

  /* Показываем окно */
  XMapWindow ( prDisplay, nWnd );

  /* Цикл получения и обработки ошибок */
  while ( 1 ) {
    XNextEvent ( prDisplay, &rEvent );

    switch ( rEvent.type ) {
       case Expose :
         /* Запрос на перерисовку */
         if ( rEvent.xexpose.count != 0 )
           break;

         prGC = XCreateGC ( prDisplay, nWnd, 0 , NULL );

         XSetForeground ( prDisplay, prGC,
              BlackPixel ( prDisplay, 0) );
         XDrawString ( prDisplay, nWnd, prGC, 10, 50,
              "Hello, world!", strlen ( "Hello, world!" ) );
         XFreeGC ( prDisplay, prGC );
         break;

       case KeyPress :
         /* Нажатие клавиши клавиатуры */
         XCloseDisplay ( prDisplay );
         exit ( 0 );
    }
  }
}

    Для сборки программы используется команда:

cc -o hello hello.o -lX11

    Программа использует ряд функций, предоставляемых библиотекой Xlib: XOpenDisplay( ), XCreateSimpleWindow( ) и др. Их прототипы, стандартные структуры данных, макросы и константы описаны в следующих основных файлах-заголовках: "Xlib.h", "Xutil.h", "Xos.h". Эти и другие файлы поставляются вместе с X Window.

    Для обозначения переменных в книге принята нотация, пришедшая из Microsoft Windows. Идентификатор начинается с префикса, описывающего тип переменной, за которым следует ее имя. Ниже перечислены наиболее распространенные префиксы.

c - символ (байт) ,
n - байт, целое,
s - строка,
p - указатель,
r - структура.

    Перейдем к рассмотрению самой программы. Она начинается установлением связи с Х-сервером. Делает это функция XOpenDisplay( ). Ее аргумент определяет сервер, с которым надо связаться. Если в качестве параметра XOpenDisplay( ) получает NULL, то она открывает доступ к серверу, который задается переменной среды (environment) DISPLAY. И значение этой переменной и значение параметра функции имеют следующий формат: host:server.screen, где host - имя компьютера, на котором выполняется сервер, server - номер сервера (обычно это 0), а screen - это номер экрана. Например, запись kiev:0.0 задает компьютер - "kiev", а в качестве номера сервера и экрана используется 0. Заметим, что номер экрана указывать не обязательно.

    Процедура XOpenDisplay() возвращает указатель на структуру типа DISPLAY. Это большой набор данных, содержащий информацию о сервере и экранах. Указатель следует запомнить, т.к. он используется в качестве параметра во многих процедурах Xlib.

    После того, как связь с сервером установлена, программа "Hello" определяет номер экрана. Для этого используется макрос DefaultScreen(), возвращающий номер основного экрана. Переменная nScreenNum может иметь значение от 0 до величины (ScreenCount (prDisplay ) - 1). Макрос ScreenCoun() позволяет получить число экранов, обслуживаемых сервером.

    Следующий шаг - создание окна и показ его на дисплее. Для этого программа обращается к процедуре  XCreateWindow() или XCreateSimpleWindow(). Для простоты мы используем вторую процедуру, параметры которой задают характеристики окна.

PrWind = XCreateSimpleWindow (
            prDisplay, /* указатель на структуру Display,
                          описывающую сервер */
            RootWindow (prDisplay, nScreenNum),
                        /* родительское окно, в данном случае,
                           это основное окно программы */
            WND_X, WND_Y,
                        /* начальные x и y координаты верхнего
                           левого угла окна программы */
            WND_WIDTH, WND_HEIGHT,
                        /* ширина окна и высота окна */
            WND_BORDER_WIDTH, /* ширина края окна */
            BlackPixel ( prDisplay, nScreenNum ),
                       /* цвет переднего плана окна */
            WhitePixel ( prDisplay, nScreenNum )
                       /* цвет фона окна */
         );

    Для задания цветов окна используются макросы BlackPixel() и WhitePixel(). Они возвращают значения пикселов, которые считаются на данном дисплее и экране соответствующими "черному" и "белому" цветам. Функция XCreateSimpleWindow() ( XCreateWindow() ) возвращает значение типа Window. Это целое число, идентифицирующее созданное окно.

    Среди параметров функций, создающих окна, есть те, которые определяют положение окна и его размеры. Эти аргументы принимаются во внимание системой X Window. Исключение составляет случай, когда родительским для создаваемого окна является "корневое" окно экрана. В этом случае решение о положение окна и его размерах принимает менеджер окон. Программа может пытаться повлиять на решение менеджера окон, сообщив ему свои "пожелания" с помощью функций XSetStandardProperties() и XSetWMHints() (для X версии 11.3 и ниже) или XSetWMProperties() (для X версии 11.4 и выше).

    Из листинга 2.1 видно, что программа может сообщить менеджеру следующие параметры:

    Имя окна и имя пиктограммы в X11R3 и ниже передаются как строки, через параметры функции XSetStandardProperties(). В X11R4 и выше строки должны быть в начале преобразованы в "текстовые свойства", описываемые структурами типа XTextProperty. Это выполняется процедурой XStringListToTextProperty().

    Для передачи информации о желаемой геометрии окна используется структура XSizeHints().

    X Window версии 11.4 (и выше) позволяет сообщить менеджеру также следующее:

    После того, как "рекомендации" менеджеру окон переданы, программа выбирает события, на которые она будет реагировать. Для этого вызывается функция XSelectInput(). Ее последний аргумент есть комбинация битовых масок (флагов). В нашем случае - это ExposureMask | KeyPressMask. ExposureMask сообщает X Window, что программа обрабатывает событие Expose. Оно посылается сервером каждый раз, когда окно должно быть перерисовано. KeyPressMask выбирает событие KeyPress - нажатие клавиши клавиатуры. Типы событий и соответствующие им маски и структуры данных описаны в приложении 1.

    Теперь окно программы создано, но не показано на экране. Чтобы это произошло, надо вызвать процедуру XMapWindow(). Заметим, что из-за буферизации событий библиотекой Xlib, окно не будет реально нарисовано, пока программа не обратится к процедуре получения сообщений от сервера ( XNextEvent() ).

    Программы для X построены по принципу управляемости событиями. Поэтому, после того, как окно создано, заданы необходимые параметры для менеджера окон, основная ее работа - это получать сообщения от сервера и откликаться на них. Выполняется это в бесконечном цикле. Очередное событие "вынимается" процедурой XNextEvent(). Само оно есть переменная типа XEvent, который представляет собой объединение (union) структур. Каждое событие (Expose, KeyPress и т.д.) имеет свои данные (и, следовательно, свое поле в объединении XEvent) Более подробно они описаны в приложении 1.

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

prGC = XCreateGC (prDisplay, prWnd, 0, NULL);

    После этого рисуется строка "Hello, world!". Более графический контекст не нужен - он уничтожается:

XFreeGC (prDisplay, prGC);

    Окно может получить несколько событий Expose одновременно. Чтобы не перерисовывать себя многократно, программа дожидается прихода последнего из них и только потом осуществляет вывод.

    Приход события KeyPress означает, что программу надо завершить: прекратить связь с сервером

XCloseDisplay (prDisplay);

    и вызвать функцию exit().