МОНИТОР #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);
}
|
|