Продолжая традиции многих изданий, посвященных программированию, начнем с программы, рисующей на экране строку «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.
Приложение:
Исходный код программы hello.pas
Для сборки программы используется команда:
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()
.