.:Публикации:. [www.karlson.ru]


МОНИТОР #02/95

Начала программирования в XWindow

Кожекин Н.
Статья содержит рекомендации по программированию в XWindow и использованию разделяемой памяти. Приводятся листинги двух программ, общающихся при параллельной работе. Демонстрируются возможности системы LINUX. Используется компилятор GNU-C++.

Обычно программирование под XWindow однозначно связывают с использованием Х Toolkit Intrinsics или OSF/Motif. Однако мне кажется, что лучше начинать с программирования со стандартными библиотеками Xlib и Xutil. Тем более в данном случае это только сэкономит наше время. Дело в том, что на моей машине лишь 4 Мб оперативной памяти, и, следовательно, время работы компилятора для меня критично. Но в любом случае использование Xt не сделает приводимую далее программу более лаконичной.

     Однако хватит псевдофилософских рассуждений - лучше для начала разберемся с использованием разделяемой памяти. При этом не стоит забывать о том, что у LINUX ядро пассивно, то есть оно не будет делать ничего самостоятельно, пока мы его не попросим. Для обмена данными между процессами в LINUX реализовано два метода: очереди сообщений и разделяемая память. Мы остановимся на втором из них, так как он является, пожалуй, наиболее мощным. В терминологии UNIX средства межпроцессорного обмена данными называются IPC (Interprocess Communications). Для IPC были разработаны стандартные средства запрещения-разрешения той или иной операции. Они называются семафорами. Для их использования нам нужно подключить к программе следующие файлы определений:

   #include <sys/types.h> 
   #include <sys/ipc.h>
   #include <sys/sem.h>

     И тогда мы сможем использовать два системных вызова: semget для создания массива семафоров и semctl для получения значений семафоров и управления их состоянием. Чтобы узнать о параметрах этих процедур, наберите в системе команду man semget, а чтобы установить комбинацию флагов для доступа, пользуйтесь таблицей 1. Аналогично построена работа с разделяемой памятью. Вы должны подключить файл:

   #include <sys/shm.h>

     Можете использовать системные вызовы shmget для получения доступа к сегменту, shmat для получения адреса, shmdt, в сущности, обратный shmat, и shmctl для изменения режима доступа или удаления сегмента, а флаги доступа полностью аналогичны указанным в таблице 1. При этом для работы со структурой semun я написал две простые, но полезные функции, которые вы можете увидеть в листинге 1.

     Теперь можно приступать к программированию собственно под XWindow. Сформулируем цель: написать две программы,
Таблица 1
Маски семафоров
SEM_R 00400 владелец читает
SEM_W 00200 владелец пишет
(SEM_R >> 3) 00040 группа читает
(SEM_R >> 3) 00020 группа пишет
(SEM_R >> 6) 00004 остальные читают
(SEMR > 6) 00002 остальные пишут
IPC_PRIVATE личный
IPC_CREATE создать, если не существует
каждая из которых открывает свое окно, причем в одном окне можно что-нибудь написать, а в другом это "что-нибудь" появится. Окна в XWindow бывают двух типов: InputOutput и InputOnly. Безусловно, нам нужны первые (последние используются, например, в популярной программе Xeyes). Сделаем в нашей программе еще три включения:

   #include<X11/XIib.h>
   #include<X11/Xutil.h>
   #include<X11/Xos.h>

     После этого мы можем честно пользоваться всеми благами жизни. Программы под XWindow обычно выглядят так: сначала мы подключаемся к Х-серверу (XOpenDisplay), потом получаем номер основного экрана DefaultScreen), создаем окно (XCreateSimpleWindow), задаем рекомендации для менеджера окон (листинг 2: целесообразно написать процедуру SetWindowManadgerHints, поддерживающую разные версии XWindow, - для LINUX это, в принципе, неактуально, но для общей солидарности UNIX-мира полезно), выбираем события для обработки (XSelectInput), показываем окно (XMapWindow) и приступаем к обработке событий. Для работы с клавиатурой подключим:

   #include<X11/keysym h>

     Именно так в программе-клиенте было реализовано соответствие между клавишами Left Ctrl - Left Shift-A и строкой "EXIT", я имею в виду процедуру XRebindKeySym. Таким образом, при нажатии этой комбинации клавиш в программе-клиенте обе программы благополучно окончат работу. Эти идеи реализованы в листингах 3 и 4.

     Одним из основных понятий при работе с графикой в системе XWindow является графический контекст (Graphic Context - GC). Он задается процедурой XCreateGC, при этом значения описываются следующей структурой.

   typedef struct{
   int function;
   /* Режим рисования. По умолчанию GXCopy
   Устанавливается процедурой XSetFunction */
   unsigned long plane_mask;
   /* В каких битовых плоскостях идет рисование */
   unsigned long foreground;
   unsigned long background;
   /* Цвет плана и цвет фона устанавливаются XSetForeground
   и XSetBackground соответственно */
   int line_width; /* Толщина линии в пикселах */
   int line_style; /* Тип линии */
   int cap_style;  /* Вид линии на концах */
   int join_style; /* Вид соединения линий */
   int fill_style; /* Способ закраски устанавливается*/
                   /* XSetFiIIStyle */
   int fiIl_rule;
   /* Режим заполнения многоугольников */
   int arc_mode;
   /* Режимы заполнения дуг - XSetArcMode */
   Pixmap tile;
   Pixmap stipple;
   /* Значения закрасок для fill_style */
   int ts_x_origin;
   int ts_y_origin; /* Сдвиги */
   Font font; /* Шрифт */
   int subwindow_mode;
   /* Режим влияния подокон - XSetSubwindowMode */
   Bool graphics_exposures;
   /* Запросы на перерисовку */
   int сlip_x_origin;
   int clip_y_origin; /* Отсечения */
   Pixmap clip_mask; /* Пикселы для вывода */
   int dash_offset; /* Сдвиг пунктира */
   char dashes; /* Вид пунктира */
   } XGCValues ;

     Из всего этого нам надо уметь только выводить строки ≈ процедурами XDrawString, XDrawText, XDrawStringl6, XDrawTextl6, XDrawImageString, XDrawImageText, XDrawImageStringl6, XDrawImageText 16, причем процедуры, оканчивающиеся на "16", используются для длинных шрифтов, а остальные для коротких.

     Для загрузки шрифта используется вызов XLoadQueryFont. Например, так:

   XFontStruct *prFont;
   ....
   /* Загрузка */
   if (( prFont=XLoadQueryFont(prDisplay,
               "*-charter-*" )) == NULL )
        sys_err("Font not found !");

     Имя шрифта состоит из двух частей, разделенных "-" причем первая часть шрифта представляет строку вида "-изготовитель-имя-жирность-наклон-ширина", а вторая - "размер в пикселах-размер в точках-горизонтальное разрешение вертикальное разрешение-тип-cредняя ширина-множество символов-", причем части могут заменяться знаками "*" и "?".

     Собственно говоря, на этом можно и закончить разбор программы. Я хочу извиниться, если кому-то объяснение показалось слишком подробным, очевидно, я просто писал не для вас. Все остальные могут набрать в системе приводимые далее команды, если, конечно, они не сделали предварительно makefile, и поиграть в "Тетрис".

   g++ window с client с -о client -lХ11 &
   g++ window с server с -о server -lХ11 &

     По окончании компиляции, чтобы убедиться в работоспособности программы, вы можете запустить в XWindow эмулятор какого-нибудь терминала и набрать последовательно команды:

   Server &
   Сlient &

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

Адрес автора:
(Никита Кожекин)
Литература
1. Nye A. Xlib Programming Manual.- USA: O'Relly & Associates, 1988.
2. Nye A. Xlib Reference Manual.- USA: O'Relly & Associates, 1988.
Листинг 1. MESSAGE.H
/* Включаемый модуль: message.h  */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h> /* Работа с семафорами */
#include <sys/shm.h> /* Работа с разделяемой памятью */

/* Работа в X-Window */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/keysym.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define SEM_ID     2001 /* Ключ массива семафоров */
#define SHM_ID     2002 /* Ключ разделяемой памяти */
#define PERMIS     0666 /* Права доступа: все могут
                           читать и писать*/

#define MSG_TYPE_EMPTY   0 /* Типы сообщений:  */
#define MSG_TYPE_STRING  1 /* пустое, строка,  */
#define MSG_TYPE_FINISH  2 /* конец диалога    */

#define MAX_STRING               20
 /* Максимальная длина сообщения */

#define WND_WDT        200 /* Ширина окна */
#define WND_HGH        100 /* Высота окна */
#define WND_MIN_WDT    50 /* Минимальная ширина окна */
#define WND_MIN_HGH    50 /* Минимальная высота окна */
#define WND_BORDER_WDT 5   /* Ширина бордюра */

struct msg_t {
   int type;
   char string[MAX_STRING];
}; /* Структура сообщения */

inline void sys_err( char * msg )
{
  puts( msg );
  exit( 1 );
} /* Системная ошибка */

/*  Работа со структурой semun */
inline union semun semun (int i)  {
  union semun s;
  s.val = i;
  return s;
}
inline union semun
        semun (struct semid_ds * b)
{
   union semun s;
   s.buf = b;
   return s;
}

/* Инициализация окна написана в window.c */
void SetWindowManagerHints (
            Display *  prDisplay,
            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);

Листинг 2. WINDOW.C
/* Инициализация окна */

#include "message.h"

void SetWindowManagerHints (
     Display *  prDisplay,
     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))  {
         sys_err("No memory!");
  }

  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
}

Листинг 3. CLIENT.C
/* Листинг программы клиента: client.c
Компиляция : g++ window.c client.c -o client -lX11 & */

#include "message.h"

#define WND_X            0 /* Координаты окна */
#define WND_Y          100


#define WND_TITLE       "Client Window"
/* Заголовок окна */
#define WND_ICON_TITLE  "Client Icon"
/* Заголовок пиктограммы */
#define PRG_CLASS       "Client"

int main(int argc, char * argv[])
/* Основная процедура */
{
  Display    *prDisplay;
  Window     nWnd;
  int        semid,shmid,x,y,nScreenNum;
  GC         prGC;
  XEvent     rEvent;
  msg_t      *msg_p;
  char       s[MAX_STRING];
  KeySym     nKeySym,naModList[2];

  if (( semid = semget(SEM_ID,1, 0)) < 0 )
   sys_err("CLIENT: can't get semaphore!");
   /* Чтение массива семафоров */
  if (( shmid = shmget(SHM_ID,sizeof(msg_t),0))<0)
   sys_err("CLIENT: can't get shared memory segment!");
   /* Доступ к сегменту разделяемой памяти */
  if ((msg_p = (msg_t * ) shmat(shmid, 0, 0)) == NULL)
   sys_err("CLIENT: shared memory attach error!");
   /* Получение адреса */

  if ((prDisplay = XOpenDisplay(NULL))==NULL)
    sys_err("CLIENT: can't connect to the X server!");
  /* Подключение к X-серверу */

  /* Регистрация окна */
  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);

  /* Установка новой кнопки клавиатуры */
  naModList[0] = XK_Control_L;
  naModList[1] = XK_Shift_L;
  XRebindKeysym( prDisplay, XK_A, naModList, 2, "EXIT",
                                        strlen("EXIT"));
  x = 0;
  y = 0;
  /* Создание графического контекста */
  prGC = XCreateGC(prDisplay, nWnd, 0, NULL);

  /* Главный цикл: чтение событий */
  for (;;) {
    /* Выбор события из очереди */
    XNextEvent(prDisplay, &rEvent);

    switch (rEvent.type) {
      case Expose : /* Запрос на перерисовку */
        if (rEvent.xexpose.count!=0) break;
        XSetForeground(prDisplay, prGC,
                       BlackPixel(prDisplay, 0));
        /* Вывод строки */
        XDrawString(prDisplay, nWnd, prGC, 10, 50,
                    "Client,", strlen("Client,"));
        break;

      case KeyPress :
        /* Нажатие клавиши */
        memset(s, 0, sizeof(s));
        XSetForeground(prDisplay, prGC,
                       BlackPixel(prDisplay, 0));
        XDrawString(prDisplay, nWnd, prGC, 10, 66,
                    "TALK:", strlen("TALK:"));
       /* Чтение строки-соответствия нажатой клавиши */
        XLookupString(&rEvent.xkey, s, sizeof(s),
                      &nKeySym, NULL);
        /* Ждем-с ... */
        while ( semctl (semid, 0, GETVAL, semun(0) ) ||
                        msg_p->type != MSG_TYPE_EMPTY);
        /* Блокировка */
        semctl(semid, 0, GETVAL, semun(1));
        strncpy(msg_p->string,s,MAX_STRING);
        /* Эхо-отображение */
        XDrawString(prDisplay, nWnd, prGC, 10+x,
                    80 + y, s, strlen(s));
        if ((10+x) > WND_WDT) {x =0; y=y+10;}
                             else x = x + 10;
        if (strcmp(s,"EXIT"))
              msg_p->type= MSG_TYPE_STRING;
              else msg_p->type = MSG_TYPE_FINISH;
        /* Снятие блокировки */
        semctl(semid, 0, SETVAL, semun(0));
        if (!strcmp(s,"EXIT"))  {
        /* Конец работы */
                           shmdt((char*)msg_p);
                           XFreeGC(prDisplay,prGC);
                           XCloseDisplay(prDisplay);
                           exit(0);
        }
        break;
     }
  }
  /* Освобождение графического контекста */
  XFreeGC(prDisplay,prGC);
  XCloseDisplay(prDisplay);
  exit(0);
}
Листинг 4. SERVER.C

/* Листинг программы сервера: server.c
Компиляция : g++ window.c server.c -o server -lX11 & */

#include "message.h"

#define WND_X 300 /* Координаты */
#define WND_Y 100

#define WND_TITLE       "Server Window" /* Заголовки */
#define WND_ICON_TITLE  "Server Icon"
#define PRG_CLASS       "Server"

int main(int argc, char * argv[])
{
  Display    *prDisplay;
  Window     nWnd;
  int        semid,shmid,x,y,nScreenNum;
  GC         prGC;
  XEvent     rEvent;
  msg_t      *msg_p;
  char       s[MAX_STRING];

  /* Создание семафоров */
  if ((semid = semget(SEM_ID,1, PERMIS | IPC_CREAT))
                                               < 0 )
  sys_err("SERVER: can't create semaphore!");
  /* Создание разделяемого сегмента */
  if ((shmid = shmget(SHM_ID,sizeof(msg_t),
                             PERMIS | IPC_CREAT))<0)
  sys_err("SERVER: can't create shared memory segment!");
  if ((msg_p = (msg_t * ) shmat(shmid, 0, 0))
                                       == NULL)
  sys_err("SERVER: shared memory attach error!");
  semctl( semid, 0 , SETVAL, semun(0));
  msg_p->type = MSG_TYPE_EMPTY;

  /* Подключение к X-серверу */
  if ((prDisplay = XOpenDisplay(NULL))==NULL)
  sys_err("SERVER: can't connect to the X server!");

  /* Инициализация окна и экрана */
  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);
  x = 0;
  y = 0;
  /* Создание графического контекста */
  prGC = XCreateGC(prDisplay, nWnd, 0 ,NULL);
  for (;;) {
    if (msg_p->type != MSG_TYPE_EMPTY) {
        /* Блокировано - ждем-с ... */
        if (semctl(semid, 0, GETVAL, semun(0)))
                                      continue;
        /* Блокировка */
        semctl(semid, 0, SETVAL, semun(1));
        if (msg_p->type == MSG_TYPE_STRING) {
          XSetForeground(prDisplay, prGC,
                         BlackPixel(prDisplay, 0));
          /* Вывод прочитанной строки */
          XDrawString(prDisplay, nWnd, prGC, 10 + x,
                              78 + y, msg_p->string,
                              strlen(msg_p->string));
          if ((10+x) > WND_WDT) {x =0; y=y+10;}
                                else x = x + 10;
        }
        if (msg_p->type == MSG_TYPE_FINISH) break;
        msg_p->type = MSG_TYPE_EMPTY;
        /* Отмена блокировки */
        semctl(semid, 0, SETVAL, semun(0));
    }
        /* Вывод приглашения для тех, кто понял */
        XSetForeground(prDisplay, prGC,
                       BlackPixel(prDisplay, 0));
        XDrawString(prDisplay, nWnd, prGC, 10, 50,
                      "Server,",strlen("Server,"));
        XDrawString(prDisplay, nWnd, prGC, 20, 66,
                      "LISTEN:",strlen("LISTEN:"));
  }
  /* Освобождение графического контекста */
  XFreeGC(prDisplay, prGC);
  /* Закрытие дисплея */
  XCloseDisplay(prDisplay);
  if (semctl(semid, 0, IPC_RMID,
           semun(((struct semid_ds *) 0)) )<0)
  sys_err("SERVER: semaphore remove error!");
  /* Удаление массива семафоров */
  shmdt((char *)msg_p);
  /* Удаление сегмента разделяемой памяти */
  if ( shmctl( shmid, IPC_RMID,
              (struct shmid_ds *) 0)<0)
  sys_err("SERVER: shared memory remove error!");
  /* Выход */
  exit(0);
}

...домик на крыше...,поиск,гостевая книга,cv. Be free, use Linux!