Назад |
X Window или просто X - это система для создания графического пользовательского интерфейса на компьютерах, работающих под управлением операционной системы UNIX. X была создана в Массачусетском Технологическом Институте (США). В настоящее время уже выпущена версия 11.5 (X Window System Version 11 Release 5 или X11R5).
X и сети. Особенностью системы является то, что она поддерживает работу как на отдельной ЭВМ, так и в сети. Это означает, что программа, "живущая" на одном компьютере, может с помощью X Window общаться с пользователем, сидящим за другой машиной. Система обеспечивает вывод графической информации на экран машины, воспринимает сигналы от внешних устройств, таких как клавиатура и мышь, и передает их программам. Заметим, что устройство вывода может иметь несколько экранов. X обеспечивает рисование на любом из них. Все это: экран (или экраны), а также устройства ввода (клавиатура или мышь) называются в терминах X Window - дисплей.
Окна. X позволяет пользователю общаться со многими программами одновременно. Чтобы вывод из них не смешивался, система создает на экране дисплея "виртуальные" подэкраны - окна. Каждое приложение, как правило, рисует лишь в своем окне или окнах. X предоставляет набор средств для создания окон, их перемещения по экрану и изменения их размеров.
X и ресурсы программ. Как правило, программы имеют набор конфигурационных параметров - ресурсов. Это может быть цвет окна, тип шрифта, которым рисуется текст, и многое другое. Система стандартизует способ задания ресурсов приложений и содержит ряд процедур для работы с ними. Эта совокупность функций называется "менеджер ресурсов" (X recource manager или сокращенно Xrm). "Хранилище" параметров программы называется базой данных ресурсов.
"Управляемость событиями" (event-driver architecture). Особенностью X Window является то, что она организует общение между самими программами и между программами и внешней средой путем рассылки событий. Событие - есть единица информации, идентифицирующая происходящие в системе изменения или действия, и содержащая дополнительные сведения о них.
В данной главе рассказывается о том, как создавать приложения, предназначенные для работы в X Window. Излагаемый материал относится к системе X Window версии 11.1 и выше.
В данном пункте с самых общих позиций рассматривается устройство системы X Window. Приводится пример простой программы и освещаются некоторые другие вопросы.
Система X Window представляет совокупность программ и библиотек. "Сердцем" ее является отдельный UNIX-процесс, существующий на компьютере, к которому присоединен дисплей. Именно сервер знает особенности конкретной аппаратуры, знает, что надо предпринять, чтобы закрасить пиксел на экране, нарисовать линию или другой графический объект. Он также умеет воспринимать сигналы, приходящие от клавиатуры и мыши.
Сервер общается с программами-клиентами, посылая или принимая от них порции (пакеты) данных. Если сервер и клиент работают на разных машинах, то данные посылаются по сети, если же компьютер один, то для передачи данных используется внутренний канал. Например, если сервер обнаруживает, что нажата кнопка мыши, то он подготавливает соответствующий пакет и посылает его тому клиенту, в чьем окне находится курсор мыши. И наоборот, если программе надо что-либо вывести на экран дисплея, то она создает необходимый пакет данных и посылает его серверу.
Состав пакетов и их последовательность определяются специальным протоколом. Его описание выходит за рамки настоящего издания и может быть найдено в документации по X Window системе [15].
Но чтобы программировать для X, совсем не обязательно знать детали реализации сервера и протокола обмена. Система предоставляет библиотеку процедур, с помощью которых программы осуществляют доступ к услугам X "на высоком уровне". Так для того, чтобы вывести на экран точку, достаточно вызвать процедуру XDrawPoint(), передав ей соответствующие параметры. Последняя выполняет всю черновую работу по подготовке и передачи пакетов данных серверу. Упомянутая библиотека называется "Xlib". Она помещается в файле "lX11.a", который, как правило, находится в каталоге "/usr/lib". Прототипы функций библиотеки, используемые ею структуры данных, типы и прочее определяется в файлах-заголовках из директории "/usr/include/X11".
На рисунке 2.1 представлена схема общения клиентов и сервера.
Рис.2.1. Общая схема общения программ-клиентов и X-сервера.
Посылка порций данных, особенно если она осуществляется через сеть, операция достаточно медленная. Чтобы повысить производительность системы, Xlib не отправляет пакеты сразу, а буферизует их в памяти машины, на которой выполняется программа-клиент. Собственно передача выполняется в тот момент, когда клиент вызывает процедуру, ожидающую получения событий от сервера, например XNextEvent(). Программа может явно инициировать отправку пакетов, обратившись к функциям XFlush() или Xsync().
Как уже упоминалось ранее, окно - это базовое понятие в X. Оно представляет прямоугольную область на экране, предоставляемую системой программе-клиенту. Последняя использует окно для вывода графической информации. На рисунке 2.2 показан общий вид окна в X Window.
Рис. 2.2. Общий вид окна X Window.
Из рисунка видно, что окно имеет внутренность и край. Основными атрибутами окна являются ширина и высота внутренности, а также ширина края. Далее мы будем говорить ширина и высота, а слово "внутренность" станем опускать. Упомянутые параметры окна называются его геометрией.
С каждым окном связывается система координат. Ее начало находится в левом верхнем углу окна. Ось x направлена вправо, а ось y - вниз. Единица измерения по обеим осям - пиксел.
Окна могут быть двух типов: InputOutput (для ввода - вывода) и InputOnly (только для ввода). Окно первого типа - это обычное окно. Окно второго типа не может использоваться для рисования. У данного окна нет края, оно "прозрачно". Заметим, что окна этого типа используются достаточно редко.
X Window позволяет программе создавать несколько окон одновременно. Они связаны в иерархию, в которой одни являются "родителями", а другие "потомками". Сам сервер на каждом экране создает одно основное окно, которое является самым верхним "родителем" всех остальных окон. Это окно мы будем называть главным или "корневым".
Для получения информации о любом окне X существует утилита xwininfo.
Окна могут располагаться на экране произвольным образом, перекрывая друг друга. X имеет набор средств, пользуясь которыми программа-клиент может изменять размеры окон и их положение на экране. Особенностью системы является то, что она не имеет встроенной возможности управлять окнами с помощью клавиатуры или мыши. Чтобы это можно было осуществить, нужен специальный клиент, который называется "менеджер окон" (Window manager). Стандартный дистрибутив X содержит такую программу - twm. Возможности этого менеджера ограничены, но, тем не менее, он позволяет осуществлять базовые действия: передвигать окна с помощью мыши, изменять их размер и т.д. Наиболее же развитым оконным менеджером является, по всей видимости, программа mwm (Motif Window Manager), которая поставляется в рамках системы OSF/Motif. Сама эта система описывается в главе 4 настоящей книги.
Но менеджер не может корректно управлять окнами, ничего о них не зная. В одних случаях удобно иметь заголовки окон, в других случаях окно не может быть сделано меньше определенных размеров, а в некоторых окно не может быть слишком увеличено. Окно может быть минимизировано (превращено в пиктограмму), в этом случае менеджер должен знать имя и вид пиктограммы. Для того, чтобы сообщить менеджеру свои пожелания относительно окон, клиенты могут использовать два способа. Во-первых, при создании окна X могут быть переданы "рекомендации" (hints) о начальном .положении окна, его ширине и высоте, минимальных и максимальных размерах и т.д. Во-вторых, можно использовать встроенный в X способ общения между программами - механизм "свойств".
Система X Window предназначена для работы на растровых дисплеях. В подобного рода устройствах изображение представляется матрицей светящихся точек - пикселов. Каждый пиксел кодируется определенным числом бит (как правило 2, 4, 8, 16 или 24). Число бит-на-пиксел называют "толщиной" или "глубиной" (deep) дисплея. Биты с одинаковыми номерами во всех пикселах образуют как бы плоскость, параллельную экрану. Ее называют "цветовой плоскостью". X позволяет рисовать в любой цветовой плоскости (или плоскостях), не затрагивая остальные.
Значение пиксела не задает непосредственно цвет точки на экране. Последний определяется с помощью специального массива данных, называемого палитрой. Цвет есть содержимое ячейки палитры, номер которой равен значению пиксела.
X имеет большой набор процедур, позволяющих рисовать графические примитивы - точки, линии, дуги, выводить текст и работать с областями произвольной формы.
В X Window встроены средства для обеспечения обмена информацией между программами-клиентами. Для этого используется механизм "свойств" (properties). "Свойство" - это порция данных, связанная с некоторым объектом (например, окном), и которая доступна всем клиентам X.
Каждое "свойство" имеет имя и уникальный идентификатор - атом (atom). Обычно имена "свойств" записываются большими буквами, например: "MY_SPECIAL_PROPERTY". Атомы используются для доступа к содержимому "свойств" с тем, чтобы уменьшить количество информации, пересылаемой по сети между клиентами и X сервером.
В X предусмотрен набор процедур, позволяющих перевести имя "свойства" в уникальный атом, и, наоборот, по атому получить необходимые данные.
Некоторые "свойства" и соответствующие им атомы являются предопределенными и создаются в момент инициализации сервера. Этим атомам соответствуют символические константы, определенные в файлах-заголовках библиотеки Xlib. Эти константы начинаются с префикса "XA_". Детально механизм "свойств" и атомов описан в п. 2.5.1.
Посмотреть текущий список атомов можно с помощью утилиты xlsatoms.
Продолжая традиции многих изданий, посвященных программированию на С, мы начинаем с программы, рисующей на экране строку "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().
Когда пользователь нажимает на кнопку мыши или клавишу клавиатуры, или когда окно программы нуждается в перерисовке, или когда происходят другие изменения в системе, сервер подготавливает соответствующий пакет данных и отправляет его той или иной программе (или программам). Этот пакет данных называется событием.
Возможных событий достаточно много. Каждое из них имеет свой тип и соответствующую структуру данных. Все они вместе, как было сказано выше, описываются объединением (union) XEvent.
Как мы видели из примера в предыдущем пункте, программа для каждого из своих окон может выбрать события, которые будут ему передаваться. Делается это с помощью функции XSelectInput(). При вызове этой процедуры требуемые события идентифицируются соответствующими флагами. Так событию ButtonPress (нажатие кнопки мыши) соответствует флаг ButtonPressMask. Когда кнопка отпускается, сервер порождает событие ButtonRelease, которому соответствует флаг - ButtonReleaseMask.
Некоторые события посылаются окну независимо от того, выбраны они или нет. Это:
MappingNotify - посылается, когда изменяется состояние клавиатуры (соответствие физических и логических кодов (см. п. 2.3.1.2.);
ClientMessage - так идентифицируются события, посылаемые от клиента к клиенту с помощью процедуры XSendEvent( );
SelectionClear, SelectionNotify, SelectionRequest - эти события используются в стандартном механизме общения между программами, работающими в X (описание этих событий выходит за рамки настоящего издания);
ConfigureExpose, NoExpose - эти события могут посылаться, когда клиент пытается копировать содержимое одного окна в другое.
Программа получает события в своем основном цикле. Для этого можно использовать ряд процедур. Наиболее простая из них XNextEvent (Display *prDisplay, XEvent *prEvent). Она "вынимает" из очереди событие, находящееся в ее "голове", сохраняет информацию о нем в переменной, на которую указывает параметр prEvent, и возвращается. При этом само событие удаляется из очереди. Функция XPeekEvent() также возвращает переданное сервером событие, но не удаляет его из очереди.
Процедура XPending() возвращает общее число событий в очереди программы.
Итак, если событие выбрано для окна, то оно будет передано ему на обработку. А если нет? В этом случае событие передается "родителю" окна. Если и тот не желает "обращать внимание" на данное событие, то оно отправляется дальше, вверх по иерархии окон, и так до тех пор, пока либо не будет найдено окно, выбравшее это событие, либо событие не потеряется.
Задача может влиять на этот процесс продвижения события по иерархии окон. Если программа включает флаг, соответствующий событию, в специальный атрибут окна, то оно, достигнув этого окна, не будет передано родителю, а будет тут же "снято с повестки дня". Этот атрибут - do_not_propagate (см. п. 2.1.8.).
Окно в X Window достаточно сложное образование. Основные его характеристики перечислялись в п.2.1.2. Теперь мы поговорим о них более подробно.
Многие атрибуты окна задаются при его создании с помощью процедуры XCreateWindow() (XCreateSimpleWindow()). В последствии параметры ножно изменить, обратившись к процедуре XChangeWindowAttributes().
Характеристики окна описываются структурой типа XWindowAttributes. Получить их можно с помощью процедуры XGetWindowAttributes().
Все они делятся на две группы. В первую входят параметры, доступные "на чтение" и "на запись". Вторая группа представляет собой внутренние данные. Программа может прочитать их, но не может менять.
Сначала перечислим поля структуры XWindowAttributes, которые относятся к "изменяемым" параметрам.
Фон окна, определяется атрибутами background_pixmap и background_pixel. Первый из них задает картинку (карту пикселов), которая используется для заливки фона окна. При необходимости картина повторяется слева - направо и сверху - вниз. Если параметр background_pixmap равен None (задается по умолчанию), то он игнорируется. Если же при этом поле background_pixel не задано (установлено по умолчанию), то окно считается "прозрачным", в противном случае его фон заливается цветом background_pixel. Атрибуты background_pixmap и background_pixel могут также принимать значение ParentRelative. В этом случае характеристики фона заимствуются у родительского окна.
Вид края окна определяется полями border_pixmap и border_pixel. Первый атрибут определяет карту пикселов, используемую для заполнения края. Если он равен None, то край заполняется цветом border_pixel. Если же и поле border_pixel не задано, то для изображения края используются соответствующие характеристики "родителя". То же самое происходит, если параметр border_pixmap равен CopyFromParent (взять у "родителя"). Последнее значение есть значение по умолчанию.
На перерисовку окна после изменения его размеров влияют атрибуты bit_gravity и win_gravity. Когда окно меняет размер, например, увеличивается или уменьшается, то, в принципе, нет необходимости перерисовывать все его содержимое. Часть окна остается неизменной. Правда, эта часть может поменять свое положение: переместиться вправо, влево, вверх или вниз. Поле bit_gravity "говорит" серверу, что делать с оставшейся частью изображения. Возможные значения параметра следующие:
ForgetGravity - содержимое окна перерисовывается (считается значением по умолчанию);
StaticGravity - остающаяся часть не должна менять положение по отношению к главному ("корневому" (root)) окну сервера;
NorthWestGravity - остающаяся часть смещается к левому верхнему углу;
NorthGravity - остающаяся часть смещается к верху окна;
NorthEastGravity - остающаяся часть смещается к правому верхнему углу;
WestGravity - остающаяся часть смещается к левому краю окна;
CenterGravity - остающаяся часть смещается к центру окна;
EastGravity - остающаяся часть смещается к правому краю окна;
SouthWestGravity - остающаяся часть смещается к левому нижнему углу;
SouthGravity - остающаяся часть смещается к нижнему краю окна;
SouthEastGravity - остающаяся часть смещается к правому нижнему углу.
Параметр win_gravity "говорит" о том, что делать с подокнами окна после изменения размеров последнего. Возможные значения параметра следующие (при перечислении используются следующие обозначения: H - изменение размеров окна по горизонтали, V - изменение размеров по вертикали, (h, v) - смещение подокна на h пикселов по горизонтали и на v пикселов по вертикали):
UnmapGravity - подокна удаляются с экрана; окну посылается событие UnmapNotify, в ответ на которое оно может переместить свои подокна и показать их с помощью процедуры XMapSubWindow( );
StaticGravity - подокна остаются на месте по отношению к главному ("корневому") окну сервера;
NorthWestGravity - устанавливается по умолчанию; соответствует смещению (0, 0);
NorthGravity - смещение (H/2, 0);
NorthEastGravity - смещение (H, 0);
WestGravity - смещение (0, V/2);
CenterGravity - смещение (H/2, V/2);
EastGravity - смещение (H, V/2);
SouthWestGravity - смещение (0, V);
SouthGravity - смещение (H/2, V);
SouthEastGravity - смещение (H, V);
Автоматическое сохранение содержимого окна, когда его часть перекрывается другими окнами, или, когда окно удаляется с экрана, определяется параметрами backing_store, backing_planes и backing_pixel. Сохраненные данные могут использоваться для восстановления окна, что значительно быстрее, чем его перерисовка программой в ответ на событие Expose. Параметр backing_store имеет следующие возможные значения:
NotUseful - (устанавливается по умолчанию) - серверу не рекомендуется сохранять содержимое окна;
WhenMapped - серверу рекомендуется спасти содержимое невидимых частей окна, когда окно показывается на экране;
Always - серверу рекомендуется сохранить содержимое окна даже, если оно не показано на экране.
Сохранение изображений требует, как правило, довольно большого расхода памяти. Атрибуты backing_planes и backing_pixel призваны уменьшить этот расход. Первый из указанных параметров "говорит" серверу, какие плоскости изображения надо сохранять; backing_pixel означает, какой цвет использовать при восстановлении изображения в тех плоскостях, которые не сохранялись. По умолчанию backing_planes - маска, состоящая из единиц, а backing_pixel равно 0.
Иногда при показе окна полезно спасти содержимое экрана под окном. Если окно невелико, и показывается не на долго, то это позволяет экономить время, которое надо будет затратить на перерисовку экрана после того, как окно будет закрыто. Если атрибут save_under равен True, то сервер будет пытаться сохранить изображение под окном. Если же он равен False (по умолчанию), то сервер ничего не предпринимает.
Когда обрабатывает (или не обрабатывает) событие, последнее может быть передано его родительскому окну. Атрибут do_not_propagate_mask (по умолчанию 0) "говорит" и о том, какие события не должны доходить до "родителей".
Изменение размеров окна и его положения на экране контролируется атрибутом override_redirect. Если он равен False, то размер окна и его положение меняются с помощью менеджера окон. Если же он равен True, то окно само решает, где ему быть, и какую ширину и высоту иметь.
Цветовую гамму окна задает параметр colormap. Более подробная информация о цветах и цветовых палитрах приведена в 2.2.8. Значение по умолчанию - CopyFromPatent, которое "говорит", что окно использует палитру своего непосредственного "родителя".
Теперь рассмотрим "неизменяемые" параметры окна. Строго говоря, атрибуты, о которых пойдет речь, нельзя назвать "неизменяемыми". Некоторые из них могут меняться сервером или менеджером окон. Но для обычных программ-клиентов они действительно являются таковыми.
Положение окна и его размеры сообщают поля x, y, width и height. Они дают координаты левого верхнего угла, ширину и высоту окна соответственно. Координаты измеряются в пикселах по отношению к родительскому окну.
Ширина края окна определяется параметром border_width.
Маска, "говорящая" о том, какие события выбраны для передачи окну породившим его клиентом, содержится в поле флагов your_event_mask. Значение параметра образуется комбинацией флагов, идентифицирующих события (см. приложение 1.).
Информация о дисплее, на котором показано окно, содержится в структуре Visual, на которую показывает поле visual. Эти данные, как правило, не обрабатываются обычными программами-клиентами (заметим, что для получения информации о дисплее, в системе предусмотрена процедура XGetVisualInfo( ) (см. 2.2.8.1.)).
Класс окна сообщает поле class. Возможные значения: InputOutput и InputOnly.
Число цветовых плоскостей дисплея (число бит-на-пиксел) помещается в поле depth.
На информацию об экране, на котором помещается окно, указывает поле screen. Она, как правило, не используется обычными программами.
Идентификатор главного ("корневого") окна экрана, на котором помещается окно, находится в поле root.
Если окно имеет палитру, и она в настоящее время активна, то поле map_installed равно True, в противном случае - False.
Видно в настоящее время окно на экране или нет, сообщает атрибут map_state.
Маска всех событий, выбранных всеми программами для данного окна, содержится в атрибуте all_event_mask. Дело в том, что окно обрабатывается не только порождающим его клиентом, но, возможно, и другими приложениями, например, менеджером окон.
Мы рассказали о том, как получить атрибуты окна, и что они означают. Теперь рассмотрим, как их изменить. Для этого можно использовать несколько процедур X Window, основной из которых является XChangeWindowAttributes( ), имеющая следующий прототип:
int XChangeWindowAttributes (Display *prDisplay,
Window nWnd, unsigned long nValueMask,
XSetWindowAttributes *prWinAttr);
Требуемые установки атрибутов передаются через аргумент prWinAttr. Он указывает на переменную типа XSetWindowAttributes. Ее поля те же, что и соответствующие поля XWindowAttributes. Разница заключается лишь в разных именах некоторых из них. Так поле your_event_mask в XWindowAttributes соответствует полю event_mask в XSetWindowAttributes.
Структура XSetWindowAttributes содержит дополнительное поле cursor. Оно определяет вид курсора мыши, когда последний находится в окне. Если поле равно None (значение по умолчанию), то используется курсор родительского окна, в противном случае значением параметра должен быть идентификатор того или иного курсора. Создание и установка курсоров подробно рассматривается в 2.3.2.2.
Параметр nValueMask при вызове указанной процедуры представляет комбинацию флагов, "говорящих" о том, какие из полей переменной prWinAttr принимать во внимание. Поля и соответствующие им флаги перечислены в таблице 1.2.3. приложения 1.
В следующем примере приведен фрагмент кода, в котором изменяются параметры border_pixmap и win_gravity некоторого окна:
.......
Display *prDisplay;
Window prWnd;
XSetWindowAttributes rWndAttr;
unsigned long nValMask;
Pixmap nPixmap;
.......
nValMask = CWBorderPixmap | CWWinGravity;
rWndAttr.border_pixmap = nPixmap;
rWndAttr.win_gravity = StaticSravity;
.......
XChangeWindowAttributes (prDisplay, prWnd, nValMask,
&rWndAttr);
.......
Отдельные атрибуты окна можно изменить более просто с помощью специальных процедур. Так функция XSetWindowBackground( ) меняет фон окна, XSetWindowBorder( ) - его край.
В данном разделе описываются возможности, которые имеет программист для вывода текста и произвольных графических изображений. Особенностью X является то, что рисовать можно не только в окне, но и в специально подготовленной области памяти. Данная область называется картой пикселов и идентифицируется целым числом, имеющим тип Pixmap. Карта "толщиной" в один бит имеет специальное название - битовая.
Прежде чем начать работу с графикой, программа должна выделить себе специальную структуру данных и получить указатель на нее. Эта структура называется графическим контекстом (Graphic Context (GC)). Указатель на GC используется в качестве одного из параметров при вызове "рисующих" функций X. Графический контекст содержит ряд атрибутов, влияющих на изображение объектов: текста, линий, фигур и др. Выделенный GC должен быть освобожден до завершения работы программы.
Графический контекст создается процедурой XCreateGC( ), имеющей следующий прототип:
GC XCreateGC (Display *prDisplay, Drawable nDrawable,
unsigned long nValueMask, XGCValues *prValues);
Первый аргумент - это указатель на структуру типа Display, который программа получает после вызова XOpenDisplay( ); второй - идентификатор окна (или карты пикселов), в котором программа будет рисовать; третий - битовая маска, определяющая, какие атрибуты GC задаются; последний аргумент - структура типа XGCValues, определяемая следующим образом:
typedef struct {
int function;
unsigned long plane_mask;
unsigned long foreground;
unsigned long background;
int line_width;
int line_style;
int cap_style;
int join_style;
int fill_style;
int fill_rule;
int arc_mode;
Pixmap tile;
Pixmap stipple;
int ts_x_origin;
int ts_y_origin;
Font font;
int subwindow_mode;
Bool graphics_exposures;
int clip_x_origin;
int clip_y_origin;
Pixmap clip_mask;
int dash_offset;
char dashes;
} XGCValues;
Значения полей данной структуры будут объяснены ниже. Каждому из них соответствует бит в маске, которая передается в качестве третьего параметра при вызове процедуры XCreateGC( ). Эти биты обозначаются символическими константами, определенными в файле "Xlib.h". Если бит установлен, то значение соответствующего атрибута должно быть взято из переданной XCreateGC( ) структуры XGCValues. Если бит сброшен, то атрибут приникает значение по умолчанию. Соответствие полей структуры XGCValues и флагов приведено в таблице 1.2.1. приложения 1.
Следующий пример показывает процесс создания графического контекста, в котором устанавливаются два атрибута: цвет фона и цвет переднего плана.
. . . . . . .
GC prGC;
XGCValues rValues;
Display prDisplay;
int nScreenNum;
. . . . . . . .
rValues.foreground = BlackPixel (prDisplay, nScreenNum);
rValues.background = WhitePixel (prDisplay, nScreenNum);
. . . . . . . .
prGC = XCreateGC (prDisplay, RootWindow (prDisplay, nScreenNum),
(GCForeground | GCBackground), &rValues);
Вызов XCreateGC( ) не единственный способ создания графического контекста. Так, например, новый контекст может быть получен из уже существующего GC с помощью XCopyGC( ).
Когда контекст порожден, его атрибуты могут изменяться процедурой XChangeGC( ). Например:
rValues.line_width = 10;
XChangeGC (prDisplay, prGC, GCLineWidth, &rValues);
Приведенный фрагмент кода меняет ширину линий, рисуемых с помощью графического контекста.
Для того, чтобы получить значение полей GC, используется процедура XGetGCValues( ).
В предыдущем разделе мы говорили, что GC имеет ряд атрибутов, воздействующих на вывод изображений. Для текста это цвет и шрифт, для линий - цвет и толщина и т.д. Как уже упоминалось выше, атрибуты контекста задаются в момент его создания. Потом они могут меняться с помощью функции XChangeGC( ). Кроме того, X поддерживает специальные функции для изменения параметров GC.
Ниже перечисляются основные характеристики графического контекста и процедуры, меняющие их.
Режим рисования (поле function в структуре XGCValues) указывает, каким образом комбинируются при рисовании цвет графики и цвет изображения, на которое накладывается графика. Данное поле задает некоторую логическую функцию. Возможные значения приведены в таблице 1.2.7. приложения 1.
По умолчанию function равно GXcopy. Устанавливается режим рисования с помощью процедуры XSetFunction( ).
Изменяемые цветовые плоскости. Каждый пиксел задается с помощью N бит. Биты с одним номером во всех пикселах образуют как-бы плоскости, идущие параллельно экрану. Получить число плоскостей для конкретного дисплея можно с помощью макроса DisplayPlanes( ). Поле plane_mask структуры графического контекста определяет, в каких плоскостях идет рисование при вызове функций X. Если бит поля установлен, то при рисовании соответствующая плоскость изменяется, в противном случае она не затрагивается.
Цвет переднего плана и фона (поля foreground и background) задают цвета, используемые при рисовании линий текста и других графических элементов. Устанавливаются значения указанных полей функциями XSetForeground( ) и XSetBackground( ) соответственно.
Атрибуты, влияющие на рисование линий. Шесть параметров определяют вид прямых, дуг и многоугольников, изображаемых с помощью X Window.
LineSolid - сплошная линия,
LineOnOffDash - пунктирная линия; промежутки между штрихами не закрашиваются;
LineDoubleDash - пунктирная линия; промежутки между штрихами закрашиваются цветом фона;
Рис. 2.3. Значения параметра cap_style графического контекста.
Рис. 2.4. Значения параметра join_style графического контекста.
Рис. 2.5. Вид линии при различных значениях параметров dashes и dash_offset графического контекста.
Для установки параметров линии используется процедура XSetLineAttributes( ).
Шрифт. Поле font определяет шрифт, используемый для вывода текста. Задать этот параметр можно с помощью процедуры XSetFont( ). Более подробно о шрифтах и работе с ними будет рассказано в 2.2.3.
Шаблоны, используемые для заполнения рисуемых фигур. Процесс рисования включает в себя два этапа. На первом определяются пикселы, которые должны быть закрашены. После этого цвет выделенных точек изменяется. Так, для линии входящие в нее пикселы определяются по специальному алгоритму, а потом закрашиваются, например, цветом переднего плана.
Способ закраски определяется полем fill_style. Он устанавливается процедурой XSetFillStyle( ) и воздействует на все функции, рисующие линии, текст и фигуры. Исключение составляет случай, когда выводится линия, для которой значение line_width равно 0. Возможные значения параметра fill_style перечислены ниже.
FillSolid - для закраски используются цвета переднего плана и фона.
FillTiled - для закраски используется карта пикселов, определяемая параметром tile графического контекста; при этом карта как-бы располагается в окне так, что ее левый верхний угол имеет координаты ts_x_origin и ts_y_origin; затем определяется ее пересечение с рисуемой графикой, и пикселы, попавшие в пересечение, закрашиваются; значения полей ts_x_origin, ts_y_origin устанавливаются процедурой XSetTSOrigin( ); карта tile должна иметь ту же "толщину" (число бит-на-пиксел), что и окно, в котором производится рисование.
FillStippled - для закраски используется карта пикселов, задаваемая полем stipple; данная карта должна иметь "толщину" в 1 бит; способ закраски такой же, как и в случае FillTiled с той лишь разницей, что рисуются лишь те пикселы графики, которым соответствует установленный бит в карте stipple; цвет пиксела задается полем foreground.
FillOpaqueStippled - аналогично значению FillStippled, только пикселы, для которых не установлен бит в карте stipple, закрашиваются цветом фона.
Для задания полей tile и stipple можно использовать карты любого размера. На некоторых устройствах при определенных размерах рисование идет намного быстрее. Для получения таких размеров можно использовать процедуры XQueryBestSize( ), XQueryBestStipple( ), XQueryBestTile( ).
Режим заполнения многоугольников указывает, как заполнять цветом многоугольники, стороны которых пересекаются. Возможные значения следующие:
EvenOddRule - заполняются точки фигуры, определяемые по следующему правилу: пусть для некоторой линии растра n1, n2, . . . , nk - стороны многоугольника, которые ее пересекают; тогда закрашиваются точки между n1 и n2, n3 и n4, и т.д.
WindingRule - заполняется вся внутренность фигуры.
Режим заполнения дуг (поле arc_mode). Параметр задается процедурой XSetArcMode( ) и влияет на вид фигур, рисуемых процедурами XFillArc( ) и XFillArcs( ). Возможные значения параметра и соответствующие способы заливки приведены на рисунке 2.6.
Рис. 2.6. Заполнение дуг при различных значениях параметра arc_mode графического контекста.
Влияние подокон на рисование графических примитивов определяется полем subwindow_mode. Оно устанавливается процедурой XSetSubwindowMode( ) и имеет следующие значения:
ClipByChildren - часть графики, перекрываемая подокнами, не видна;
IncludeInferiors - графика рисуется поверх всех подокон
Генерация запроса на перерисовку при копировании частей окон (поле graphics_exposures). Когда часть окна копируется куда-либо, то вполне вероятна ситуация, что исходное изображение перекрыто, возможно не полностью, другими окнами или недоступна по другим причинам. В этом случае может быть необходимо сообщить клиенту, в окно которого происходит копирование, что часть нового изображения не может быть получена простым переносом пикселов, а должна быть перерисована. Если поле graphics_exposures равно True, то X посылает при копировании следующее:
Если поле равно False, то событие не посылается. Устанавливается параметр процедурой XSetGraphicsExposures( ).
Область отсечения (задается полями clip_mask, clip_x_origin, clip_y_origin). Это битовая карта, "говорящая" о том, какие пикселы выводятся, а какие нет при всех операциях рисования. Если бит карты установлен, то соответствующий пиксел появится в окне, а если бит сброшен, то пиксел будет пропущен. Положение в окне верхнего левого угла области отсечения определяется параметрами clip_x_origin и clip_y_origin (см. рис. 2.7).
Рис. 2.7. Параметры области отсечения и ее влияние на рисование графических примитивов.
Эти параметры устанавливаются процедурой XSetClipOrigin( ). Сама область отсечения задается с помощью процедур XSetClipMask( ), XSetClipRectangles( ) или XSetClipRegion( ).
Текст был и, видимо, будет важным средством информационного обмена между программами и пользователем. X Window позволяет выводить строки в любой части окна, используя большое количество шрифтов.
Для вывода текста используются процедуры XDrawString( ), XDrawImageString( ) и XDrawText( ). Каждая из них имеет две версии. Первая используется для шрифтов, имеющих не более 256 символов. Если же символов больше ("большие" шрифты), то применяется вторая версия. Функции, работающие с "большими" шрифтами, имеют имена XDrawString16( ), XDrawImageString16( ) и XDrawText16( ). Параметры процедур, выводящих текст, задают дисплей, окно, графический контекст, строку, ее положение и т.д. Рисование идет в соответствии с областью отсечения контекста. Буквы или их части, находящиеся за пределами области отсечения, не изображаются. Наиболее часто употребляется процедура XDrawString( ) ( XDrawString16( ) ). Ее параметры дают строку, ее длину и положение в окне. Текст рисуется цветом переднего плана, выбранного в GC.
Функция XDrawImageString( ) ( XDrawImageString16( ) ) похожа на предыдущую процедуру с той лишь разницей, что фон символов при рисовании закрашивается цветом фона, установленного в GC. XDrawString( ) и XDrawImageString( ) выводят символы, используя шрифт, установленный в GC.
XDrawText( ) ( XDrawText16( ) ) позволяет рисовать несколько строк сразу, используя при этом разные шрифты. Каждая рисуемая единица задается структурой XTextItem.
Процедура XDrawText16( ) использует структуру XDrawText16.
Поле font, в приведенных структурах (XTextItem и XDrawText16) задает шрифт, используемый для рисования. Если значение поля font - None, то применяется шрифт, выбранный в GC.
Более подробная информация об указанных структурах и функциях находится в приложении 1.
Как мы уже говорили ранее, текст, как правило, рисуется шрифтом, выбранным в графическом контексте. X версии 11.4 и ниже поддерживает только растровые шрифты, а начиная с версии 11.5 и выше X Window имеет также и векторные шрифты.
В растровых шрифтах каждому символу соответствует некоторый битовый шаблон, определяющий порядок закраски пикселов при рисовании. Если бит шаблона равен 1, то соответствующий элемент изображения закрашивается цветом переднего плана GC, если же он равен 0, то он закрашивается либо цветом фона, либо вообще не рисуется.
В векторных шрифтах каждый символ описывается последовательностью линий, которые будучи составлены вместе и дают его изображение. Размеры символов варьируются от шрифта к шрифту. Для их описания используется структура XCharStruct. Основные параметры символа показаны на рис. 2.8.
ascent
ascent
Базовая линия
descent
lbearing lbearing
rbearing rbearing
width width
Рис. 2.8. Основные параметры символа шрифта.
Сам шрифт описывается структурой XFontStruct (см. приложение 1.).
Перед тем, как выводить текст, используя тот или иной шрифт, последний должен быть загружен в X Window и выбран в графическом контексте.
Загрузка шрифта осуществляется процедурой XLoadFont( ). Она берет в качестве аргумента имя шрифта, находит его и возвращает программе соответствующий идентификатор. Этот идентификатор передается затем процедуре XSetFont( ), чтобы выбрать шрифт в GC. Заметим, что реально шрифт с данным именем загружается сервером лишь один раз. После этого при обращениях к XLoadFont( ) с тем же именем шрифта, функция возвращает ссылку на шрифт, уже находящийся в памяти компьютера.
По умолчанию X ищет файл со шрифтом в директории "usr/lib/X11/fonts". Программист может задать дополнительные директории для поиска с помощью процедуры XSetFontPath( ).
Имя шрифта в X начинается с "-" и состоит из двух частей. Между ними стоит "--". В свою очередь, каждая из частей состоит из полей - слов, разделенных "-".
В первой части указывается следующее:
Во второй части указывается следующее:
Ниже приведен пример названия шрифта.
-adobe-courier-bold-o-normal--10-100-75-75-m-60-iso8859-1
Части имени могут заменяться символом "*" или "?". В этом случае X подбирает шрифт, сличая имена имеющихся шрифтов с предоставленным шаблоном, так, как это делается при поиске файлов в UNIX. Например, шаблону
*charter-medium-i-*-240-*
соответствуют имена
-hit-charter-medium-i-normal-25-240-75-75-p-136-iso8859-1
-hit-charter-medium-i-normal-33-240-100-75-p-136-iso8859-1
Названия шрифтов, доступных в системе, хранятся в соответствующей базе данных. Получить список имен шрифтов можно с помощью процедуры XlistFonts( ) или XListFontsWithInfo( ). Список шрифтов, возвращаемый этими функциями, должен быть освобожден вызовом XFreeFontNames( ).
Некоторые шрифты, такие как "fixed" или "9x15", доступны всегда.
Получить информацию о загруженном шрифте можно с помощью функции XQueryFont( ), которая возвращает заполненную структуру типа XFontInfo( ). Одновременно загрузить шрифт и получить информацию о нем можно с помощью процедуры XLoadQueryFont( ).
Когда информация о шрифте больше не нужна, ее следует освободить с помощью XFreeFontInfo( ). Когда становится не нужен и сам шрифт, последний надо "сбросить", обратившись к процедуре XUnloadFont( ). Функция XFreeFont( ) объединяет в себе XFreeFontInfo( ) и XUnloadFont( ).
Следующий фрагмент кода загружает шрифт "Courier", создает GC и выводит с его помощью строку "Hellow, world!".
Display *prDisplay;
GC prGC;
Window nWnd;
XFontStruct *prFontInfo;
. . . . . . .
/* Загружаем шрифт */
if ( (prFontInfo=
XLoadQueryFont(prDisplay, "*-courier-*" )) == NULL){
printf("Font not found!\n");
exit(1);
}
. . . . . . .
/* Создаем GC и рисуем строку */
prGC=XCreateGC(prDisplay, nWnd, 0, NULL);
XSetForeground (prDisplay, prGC, BlackPixel(prDisplay, 0));
XSetFont (prDisplay, prGC, prFontInfo->fid);
XDrawString (prDisplay, nWnd, prGC, 10, 50, "Hello, world!",
strlen ("Hello, world!") );
XFreeGC (prDisplay, prGC);
. . . . . . .
/* "Сбрасываем" шрифт */
XUnloadFont (prDisplay, prFontInfo->fid);
. . . . . . .