1.1.6 Первый пример
Продолжая традиции многих изданий, посвященных программированию, начнем с программы, рисующей на экране строку "Hello, world!". В этом примере приведены основные шаги, необходимые для работы в X Window.
uses x,xlib,x11,xutil,strings;
const
WND_X=0;
WND_Y=0;
WND_WDT=100;
WND_HGH=100;
WND_MIN_WDT=50;
WND_MIN_HGH=50;
WND_BORDER_WDT=5;
WND_TITLE='Hello!';
WND_ICON_TITLE='Hello!';
PRG_CLASS='Hello!';
(* SetWindowManagerHints - процедура передает информацию о свойствах программы менеджеру окон. *)
procedure SetWindowManagerHints(
prDisplay: PDisplay; (*Указатель на структуру TDisplay *)
psPrgClass: PChar; (*Класс программы *)
argv: PPChar; (*Аргументы программы *)
argc: integer; (*Число аргументов *)
nWnd: TWindow; (*Идентификатор окна *)
x, (*Координаты левого верхнего *)
y, (*угла окна *)
nWidth,
nHeight, (*Ширина и высота окна *)
nMinWidth,
nMinHeight:integer; (*Минимальные ширина и высота окна *)
psTitle: PChar; (*Заголовок окна *)
psIconTitle: PChar; (*Заголовок пиктограммы окна *)
nIconPixmap: TPixmap (*Рисунок пиктограммы *)
);
var
rSizeHints: TXSizeHints; (*Рекомендации о размерах окна*)
rWMHints: TXWMHints;
rClassHint: TXClassHint;
prWindowName, prIconName: TXTextProperty;
begin
if (XStringListToTextProperty(@psTitle, 1, @prWindowName)=0) or
(XStringListToTextProperty(@psIconTitle, 1, @prIconName)=0) then
begin
writeln('No memory!');
halt(1);
end;
rSizeHints.flags:= PPosition OR PSize OR PMinSize;
rSizeHints.min_width:= nMinWidth;
rSizeHints.min_height:= nMinHeight;
rWMHints.flags:= StateHint OR IconPixmapHint OR 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);
end;
(* main - основная процедура программы *)
//void main(int argc, char *argv[])
var
prDisplay: PDisplay; (* Указатель на структуру Display *)
nScreenNum: integer; (* Номер экрана *)
prGC: TGC;
rEvent: TXEvent;
nWnd: TWindow;
begin
(* Устанавливаем связь с сервером *)
prDisplay:= XOpenDisplay(nil);
if prDisplay = nil then begin
writeln('Can not connect to the X server!');
halt (1);
end;
(* Получаем номер основного экрана *)
nScreenNum:= XDefaultScreen(prDisplay);
(* Создаем окно *)
nWnd:= XCreateSimpleWindow(prDisplay, XRootWindow (prDisplay, nScreenNum), WND_X, WND_Y, WND_WDT, WND_HGH, WND_BORDER_WDT, XBlackPixel (prDisplay, nScreenNum),
XWhitePixel (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 OR KeyPressMask);
(* Показываем окно *)
XMapWindow(prDisplay, nWnd);
(* Цикл получения и обработки событий *)
while (true) do begin
XNextEvent(prDisplay, @rEvent);
case (rEvent.eventtype) of
Expose:
begin
(* Запрос на перерисовку *)
if (rEvent.xexpose.count ‹› 0) then continue;
prGC:= XCreateGC (prDisplay, nWnd, 0, nil );
XSetForeground(prDisplay, prGC, XBlackPixel (prDisplay, 0));
XDrawString(prDisplay, nWnd, prGC, 10, 50, 'Hello, world!', strlen ('Hello, world!'));
XFreeGC (prDisplay, prGC);
end;
KeyPress:
begin
(* Нажатие клавиши клавиатуры *)
XCloseDisplay(prDisplay);
halt(0);
end;
end;
end;
end.
Для сборки программы используется команда:
fpc hello.pas
Здесь fpc - имя исполняемого файла компилятора. Как правило, это символическая ссылка на реальное имя компилятора (например, ppc386).
В современных версиях UNIX для создания программных продуктов используются не только компиляторы командной строки, но и самые разнообразные интегрированные среды. Одной из наиболее удобных, по нашему мнению, является интегрированная среда разработки Анюта (Anjuta). Ее создатель - индийский программист Наба Кумар - позаботился о том, чтобы мы чувствовали себя в ней комфортно.
Для того, чтобы разрешить в Анюте поддержку русского языка, необходимо добавить в файл свойств этой программы (~/.anjuta/session.properties) строку
character.set=204
Для подключения компилятора FreePascal необходимо добавить в диалог "Команды" следующие установки:
На рис. 1.3 показан внешний вид приложения после его запуска.
Рис. 1.3. Окно приложения xhello в среде KDE
Программа использует ряд функций, предоставляемых библиотекой Xlib: XOpenDisplay(), XCreateSimpleWindow() и др. Их прототипы, стандартные структуры данных, макросы и константы описаны в следующих основных файлах-модулях: Xlib, Xutil, X, X11.
Перейдем к рассмотрению самой программы. Она начинается установлением связи с Х-сервером. Делает это функция XOpenDisplay(). Ее аргумент определяет сервер, с которым надо связаться. Если в качестве своего параметра XOpenDisplay () получает nil, то она открывает доступ к серверу, который задается переменной среды (environment) DISPLAY. И значение этой переменной, и значение параметра функции имеют следующий формат: host:server.screen, где host - имя компьютера, на котором выполняется сервер, server - номер сервера (обычно это 0), а screen - это номер экрана. Например, запись kiev:0.0 задает компьютер kiev, а в качестве номера сервера и экрана используется 0. Заметим, что номер экрана указывать не обязательно.
Процедура XOpenDisplay() возвращает указатель на структуру типа TDisplay. Это большой набор данных, содержащий информацию о сервере и экранах. Указатель следует запомнить, т.к. он используется в качестве параметра во многих процедурах Xlib.
XOpenDisplay() соединяет программу с X сервером, используя протоколы TCP или DECnet, или же с использованием некоторого локального протокола межпроцессного взаимодействия. Если имя машины и номер дисплея разделяются одним знаком двоеточия (:), то XOpenDisplay() производит соединение с использованием протокола TCP. Если же имя машины отделено от номера дисплея двойным двоеточием (::), то для соединения используется протокол DECnet. При отсутствии поля имени машины в имени дисплея, то для соединения используется наиболее быстрые из доступных протоколов. Конкретный X сервер может поддерживать как все, так и некоторые из этих протоколов связи. Конкретные реализации Xlib могут дополнительно поддерживать другие протоколы.
Если соединение проведено удачно, XOpenDisplay() возвращает указатель на структуру TDisplay, которая определяется в Xlib.pp. Если же установить соединение не удалось, то XOpenDisplay() возвращает NIL. После успешного вызова XOpenDisplay() клиентской программой могут использоваться все экраны дисплея. Номер экрана возвращается функцией XDefaultScreen(). Доступ к полям структур TDisplay и TScreen возможен только посредством использования макроопределений и функций.
После того, как связь с сервером установлена, программа "Hello" определяет номер экрана. Для этого используется функция XDefaultScreen(), возвращающий номер основного экрана. Переменная nScreenNum может иметь значение от 0 до величины (ScreenCount(prDisplay)-1). Макрос XScreenCount() позволяет получить число экранов, обслуживаемых сервером.
Следующий шаг - создание окна и показ его на дисплее. Для этого программа обращается к процедуре XCreateWindow() или XCreateSimpleWindow(). Для простоты мы используем вторую процедуру, параметры которой задают характеристики окна.
PrWind:= XCreateSimpleWindow (
prDisplay, (* указатель на структуру TDisplay, описывающую сервер *)
XRootWindow(prDisplay, nScreenNum), (* родительское окно, в данном случае, это основное окно программы *)
WND_X, WND_Y, (* начальные x и y координаты верхнего левого угла окна программы *)
WND_WIDTH, WND_HEIGHT, (* ширина окна и высота окна *)
WND_BORDER_WIDTH, (* ширина края окна *)
XBlackPixel(prDisplay, nScreenNum), (* цвет переднего плана окна *)
XWhitePixel(prDisplay, nScreenNum) (* цвет фона окна *)
);
Для задания цветов окна используются функции XBlackPixel() и XWhitePixel(). Они возвращают значения пикселей, которые считаются на данном дисплее и экране соответствующими "черному" и "белому" цветам. Функция XCreateSimpleWindow() (XCreateWindow()) возвращает значение типа TWindow. Это целое число, идентифицирующее созданное окно.
Среди параметров функций, создающих окна, есть те, которые определяют положение окна и его размеры. Эти аргументы принимаются во внимание системой X Window. Исключение составляет случай, когда родительским для создаваемого окна является "корневое" окно экрана. В этом случае решение о положение окна и его размерах принимает менеджер окон. Программа может пытаться повлиять на решение менеджера окон, сообщив ему свои "пожелания" с помощью функции XSetWMProperties().
Из листинга видно, что программа может сообщить менеджеру следующие параметры:
• имя (заголовок) окна;
• имя пиктограммы окна;
• саму пиктограмму;
• параметры argc и argv, передаваемые от UNIX программе;
• желаемое положение окна, его размеры, другие рекомендации о его геометрии.
Имя окна и имя пиктограммы должны быть в начале преобразованы в "текстовые свойства", описываемые структурами типа TXTextProperty. Это выполняется процедурой XStringListToTextProperty().
Для передачи информации о желаемой геометрии окна используется структура TXSizeHints.
X Window позволяет сообщить менеджеру также следующее:
• начальное состояние окна: нормальное или минимизированное;
• воспринимает ли окно ввод с клавиатуры;
• класс программы и ее имя для чтения ресурсов из базы данных ресурсов.
После того, как "рекомендации" менеджеру окон переданы, программа выбирает события, на которые она будет реагировать. Для этого вызывается функция XSelectInput(). Ее последний аргумент есть комбинация битовых масок (флагов). В нашем случае это (ExposureMask or KeyPressMask). ExposureMask сообщает X Window, что программа обрабатывает событие Expose. Оно посылается сервером каждый раз, когда окно должно быть перерисовано. KeyPressMask выбирает событие KeyPress - нажатие клавиши клавиатуры.
Теперь окно программы создано, но не показано на экране. Чтобы это произошло, надо вызвать процедуру библиотеки XMapWindow(). Заметим, что из-за буферизации событий библиотекой Xlib, окно не будет реально нарисовано, пока программа не обратится к процедуре получения сообщений от сервера XNextEvent().
Программы для X построены по принципу управляемости событиями. Поэтому, после того, как окно создано, заданы необходимые параметры для менеджера окон, основная ее работа - это получать сообщения от сервера и откликаться на них. Выполняется это в бесконечном цикле. Очередное событие "вынимается" процедурой XNextEvent(). Само оно есть переменная типа XEvent, который представляет собой объединение структур. Каждое событие (Expose, KeyPress и т.д.) имеет свои данные (и, следовательно, свое поле в объединении XEvent).
При получении сообщения Expose программа перерисовывает окно. Это событие является одним из наиболее важных событий, которые приложение может получить. Оно будет послано нашему окну в одном из различных случаев:
• другое окно перекрыло часть нашего;
• наше окно было выведено поверх всех других окон;
• наше окно впервые прорисовывается на экране;
• наше окно было восстановлено после сворачивания.
Когда мы получаем событие Expose, мы должны взять данные события из члена xexpose объединения XEvent. Он содержит различные интересные поля:
• count - количество других событий Expose, ожидающие в очереди событий сервера. Это может быть полезным, если мы получаем несколько таких сообщений подряд - рекомендуется избегать перерисовывать окно, пока мы не получим последнее из их (то есть пока count не равно 0).
• window - идентификатор окна, которому было послано сообщение Expose (в случае, если приложение зарегистрировало это событие в различных окнах).
• x, y - координаты верхнего левого угла области окна, которая должна быть перерисована.
• width, height - ширина и высота области окна, которая должна быть перерисована.
Действия по обработке Expose начинаются с создания графического контекста - структуры, которая содержит данные, необходимые для вывода информации, в нашем случае - текста:
prGC:= XCreateGC(prDisplay, prWnd, 0, NIL);
После этого рисуется строка "Hello, world!". Более графический контекст не нужен - он уничтожается:
XFreeGC(prDisplay, prGC);
Окно может получить несколько событий Expose одновременно. Чтобы не перерисовывать себя многократно, программа дожидается прихода последнего из них и только потом осуществляет вывод.
Приход события KeyPress означает, что программу надо завершить: прекратить связь с сервером, для чего вызывать XCloseDisplay(prDisplay), и вызвать функцию halt().
XCloseDisplay() закрывает соединение с Х сервером, закрывает все окна и удаляет идентификаторы ресурсов, созданных клиентом на дисплее. Для удаления только окна без разрыва связи с Х сервером необходимо использовать функции XDestroyWindow() и XDestroySubWindows().