Программирование в X-Window средствами Free Pascal

Перейти к содержанию

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().

Из листинга видно, что программа может сообщить менеджеру следующие параметры:

Имя окна и имя пиктограммы должны быть в начале преобразованы в «текстовые свойства», описываемые структурами типа 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 не равно 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().