Программирование в X-Window
средствами Free Pascal
Авторы: А.П. Полищук, С.А. Семериков
[Оформление в HTML: Valery Votintsev]
Содержание
1. Основы программирования в системе X Window
1.1. Основные понятия
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.
Приложение:
Исходный код программы 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() .
|