Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net


Содержание

Книги

Статьи
Программирование в X Window средствами Free Pascal
А. П. Полищук, С. А. Семериков


1.1 Основные понятия


Система X Window представляет совокупность программ и библиотек. Сердцем ее является отдельный UNIX-процесс, существующий на компьютере, к которому присоединен дисплей. Именно сервер знает особенности конкретной аппаратуры, знает, что надо предпринять, чтобы закрасить пиксель на экране, нарисовать линию или другой графический объект. Он также умеет воспринимать сигналы, приходящие от клавиатуры и мыши.

Сервер общается с программами-клиентами, посылая или принимая от них порции (пакеты) данных. Если сервер и клиент работают на разных машинах, то данные посылаются по сети, если же компьютер один, то для передачи данных используется внутренний канал. Например, если сервер обнаруживает, что нажата кнопка мыши, то он подготавливает соответствующий пакет и посылает его тому клиенту, в чьем окне находится курсор мыши. И наоборот, если программе надо что-либо вывести на экран дисплея, то она создает необходимый пакет данных и посылает его серверу.

Состав пакетов и их последовательность определяются специальным протоколом. Но чтобы программировать для X, совсем не обязательно знать детали реализации сервера и протокола обмена. Система предоставляет библиотеку процедур, с помощью которых программы осуществляют доступ к услугам X на высоком уровне. Так для того, чтобы вывести на экран точку, достаточно вызвать процедуру XDrawPoint(), передав ей соответствующие параметры. Последняя выполняет всю черновую работу по подготовке и передачи пакетов данных серверу. Упомянутая библиотека называется Xlib. Она помещается в файле lX11.a (libX11.so), который, как правило, находится в каталоге /usr/X11R6/lib. Прототипы функций библиотеки, используемые ею структуры данных, типы и прочее определяется в файлах-заголовках из директории /usr/include/X11.

На рис. 1.1 представлена схема общения клиентов и сервера.


Рис. 1.1. Общая схема общения программ-клиентов и X-сервера

Посылка порций данных, особенно если она осуществляется через сеть, операция достаточно медленная. Чтобы повысить производительность системы, Xlib не отправляет пакеты сразу, а буферизует их в памяти машины, на которой выполняется программа-клиент. Собственно передача выполняется в тот момент, когда клиент вызывает процедуру, ожидающую получения событий от сервера, например XNextEvent(). Программа может явно инициировать отправку пакетов, обратившись к функциям XFlush() или XSync().



Как уже упоминалось ранее, окно - это базовое понятие в X. Оно представляет прямоугольную область на экране, предоставляемую системой программе-клиенту. Последняя использует окно для вывода графической информации. На рис. 1.2 показан общий вид окна в X Window.


Рис. 1.2. Общий вид окна X Window

Из рисунка видно, что окно имеет внутренность и край. Основными атрибутами окна являются ширина и высота внутренности, а также ширина края. Далее мы будем говорить ширина и высота, а слово "внутренность" станем опускать. Упомянутые параметры окна называются его геометрией.

С каждым окном связывается система координат. Ее начало находится в левом верхнем углу окна. Ось x направлена вправо, а ось y - вниз. Единица измерения по обеим осям - пиксель.

Окна могут быть двух типов: InputOutput (для ввода-вывода) и InputOnly (только для ввода). Окно первого типа - это обычное окно. Окно второго типа не может использоваться для рисования. У данного окна нет края, оно "прозрачно". Заметим, что окна этого типа используются достаточно редко.

X Window позволяет программе создавать несколько окон одновременно. Они связаны в иерархию, в которой одни являются родителями, а другие потомками. Сам сервер на каждом экране создает одно основное окно, которое является самым верхним родителем всех остальных окон. Это окно мы будем называть главным или корневым.

Корневое окно всегда занимает весь экран. Это окно не может уничтожаться, меняться размеры или сворачиваться. Когда приложение создает окна, сначала должно быть создано по крайней мере одно окно верхнего уровня. Это окно становится прямым потомком корневого окна до тех пор, пока не отобразится. Прежде, чем это окно отобразится, оконный менеджер извещается об операции размещения окна. Оконный менеджер имеет привилегию "удочерить" новое окна верхнего уровня. Это используется для добавления окна, которое будет содержать новое окно и использоваться, чтобы нарисовать рамку, заголовок окна, системное меню и т.п.

Если такое окно верхнего уровня (который в действительности не окно верхнего уровня после того, как "удочерение" произошло) создано, приложение может создать в этом окне дочернее. Потомок может отображаться только в своем родительском окне - при перемещении его окно "обрезается" границей родительского. Любое окно может содержать более чем одно дочернее окно, и в этом случае эти окна упорядочиваются во внутренний стек. Когда окно верхнего уровня "поднимается", все его окна-потомки "поднимаются" вместе с ним, с сохранением их внутреннего упорядочения. Если окно потомка "поднято", оно поднимается только относительно своих собратьев.



Окна могут располагаться на экране произвольным образом, перекрывая друг друга. X имеет набор средств, пользуясь которыми программа-клиент может изменять размеры окон и их положение на экране. Особенностью системы является то, что она не имеет встроенной возможности управлять окнами с помощью клавиатуры или мыши. Чтобы это можно было осуществить, нужен специальный клиент, который называется менеджер окон (Window manager). Стандартный дистрибутив X содержит такую программу - twm. Возможности этого менеджера ограничены, но, тем не менее, он позволяет осуществлять базовые действия: передвигать окна с помощью мыши, изменять их размер и т.д. Более развитым оконным менеджером является, по всей видимости, программа mwm (Motif Window Manager), которая поставляется в рамках системы OpenMotif.

Но менеджер не может корректно управлять окнами, ничего о них не зная. В одних случаях удобно иметь заголовки окон, в других случаях окно не может быть сделано меньше определенных размеров, а в некоторых окно не может быть слишком увеличено. Окно может быть минимизировано (превращено в пиктограмму), в этом случае менеджер должен знать имя и вид пиктограммы. Для того, чтобы сообщить менеджеру свои пожелания относительно окон, клиенты могут использовать два способа. Во-первых, при создании окна X могут быть переданы рекомендации (hints) о начальном .положении окна, его ширине и высоте, минимальных и максимальных размерах и т.д. Во-вторых, можно использовать встроенный в X способ общения между программами - механизм свойств.



Система X Window предназначена для работы на растровых дисплеях. В подобного рода устройствах изображение представляется матрицей светящихся точек - пикселей. Каждый пиксель кодируется определенным числом бит (как правило 2, 4, 8, 16 или 24). Число бит-на-пиксель называют "толщиной" или глубиной дисплея. Биты с одинаковыми номерами во всех пикселях образуют как бы плоскость, параллельную экрану. Ее называют цветовой плоскостью. X позволяет рисовать в любой цветовой плоскости (или плоскостях), не затрагивая остальные.

Значение пикселя не задает непосредственно цвет точки на экране. Последний определяется с помощью специального массива данных, называемого палитрой. Цвет есть содержимое ячейки палитры, номер которой равен значению пикселя.

X имеет большой набор процедур, позволяющих рисовать графические примитивы - точки, линии, дуги, выводить текст и работать с областями произвольной формы.



В X Window встроены средства для обеспечения обмена информацией между программами-клиентами. Для этого используется механизм свойств (properties). Свойство - это порция данных, связанная с некоторым объектом (например, окном), и которая доступна всем клиентам X.

Каждое свойство имеет имя и уникальный идентификатор - атом. Обычно имена свойств записываются большими буквами, например: MY_SPECIAL_PROPERTY. Атомы используются для доступа к содержимому свойств с тем, чтобы уменьшить количество информации, пересылаемой по сети между клиентами и X сервером.

В X предусмотрен набор процедур, позволяющих перевести имя свойства в уникальный атом, и, наоборот, по атому получить необходимые данные.

Некоторые свойства и соответствующие им атомы являются предопределенными и создаются в момент инициализации сервера. Этим атомам соответствуют символические константы, определенные в файлах-заголовках библиотеки Xlib. Эти константы начинаются с префикса XA_.



Продолжая традиции многих изданий, посвященных программированию, начнем с программы, рисующей на экране строку "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
      break;

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



Когда пользователь нажимает на кнопку мыши или клавишу клавиатуры, или когда окно программы нуждается в перерисовке, или когда происходят другие изменения в системе, сервер подготавливает соответствующий пакет данных и отправляет его той или иной программе (или программам). Этот пакет данных называется событием.

Типичная GUI программа имеет следующую структуру:
1.Выполняются инициализационные процедуры.
2.Устанавливается соединение с Х сервером.
3.Выполняются инициализационные процедуры, связанные с Х.
4.Пока не завершились:
1.Получаем следующее событие от Х сервера.
2.Обрабатываем событие, возможно посылая различные запросы на рисование к Х серверу.
3.Если событие было завершающим, заканчиваем цикл.
5.Закрываем соединение с Х сервером.
6.Выполняем завершающие действия.

Возможных событий достаточно много; их список можно найти в файле X.pp. Каждое из них имеет свой тип и соответствующую структуру данных. Все они вместе, как было сказано выше, описываются объединением XEvent.

Как мы видели из примера в предыдущем пункте, программа для каждого из своих окон может выбрать события, которые будут ему передаваться. Делается это с помощью функции XSelectInput(). При вызове этой процедуры требуемые события идентифицируются соответствующими флагами. Так событию, ButtonPress (нажатие кнопки мыши) соответствует флаг ButtonPressMask. Когда кнопка отпускается, сервер порождает событие ButtonRelease, которому соответствует флаг - ButtonReleaseMask.

Маска выбираемых событий может составляться с помощью побитового "ИЛИ" из таких значений:
0 - не ожидать никаких событий
KeyPressMask - ожидать событие нажатия клавиши
KeyReleaseMask - ожидать событие отпускания клавиши
ButtonPressMask - ожидать событие нажатия кнопки мыши
ButtonReleaseMask - ожидать событие отпускания кнопки мыши
EnterWindowMask - ожидать событие входа в окно
LeaveWindowMask - ожидать событие выхода из окна
PointerMotionMask - ожидать событие движения мышиного курсора
PointerMotionHintMask - ожидать событие движения мышиного курсора с дополнительными указаниями
Button1MotionMask - ожидать событие движения мышиного курсора при нажатой первой кнопке
Button2MotionMask - ожидать событие движения мышиного курсора при нажатой второй кнопке
Button3MotionMask - ожидать событие движения мышиного курсора при нажатой третьей кнопке
ButtonMotionMask - ожидать событие движения мышиного курсора при любой нажатой кнопке
ExposureMask - ожидать событие необходимости перерисовки окна
VisibilityChangeMask - ожидать событие изменения видимости
ResizeRedirectMask - ожидать событие изменения размеров окна
FocusChangeMask - ожидать событие изменения фокуса ввода

Некоторые события посылаются окну независимо от того, выбраны они или нет. Это:
MappingNotify - посылается, когда изменяется состояние клавиатуры (соответствие физических и логических кодов;
ClientMessage - так идентифицируются события, посылаемые от клиента к клиенту с помощью процедуры XSendEvent();
SelectionClear, SelectionNotify, SelectionRequest - эти события используются в стандартном механизме общения между программами, работающими в X;
Expose, NoExpose - эти события могут посылаться, когда клиент пытается копировать содержимое одного окна в другое.

Частой ошибкой начинающих программистов является добавление кода для обработки нового события без добавления маски для этого события в XSelectInput(). Можно часам сидеть и отлаживать программу в недоумении, почему она не реагирует на отпускание кнопки, только из-за того, что для кнопок событие нажатия зарегистрировано, а событие отпускания - нет.

Программа получает события в своем основном цикле. Для этого можно использовать ряд процедур. Наиболее простая из них:
function XNextEvent(prDisplay : PDisplay; prEvent : PXEvent) : longint; cdecl; external;
Она "вынимает" из очереди событие, находящееся в ее "голове", сохраняет информацию о нем в переменной, на которую указывает параметр prEvent, и возвращается. При этом само событие удаляется из очереди. Функция XPeekEvent() также возвращает переданное сервером событие, но не удаляет его из очереди.

Процедура XPending() возвращает общее число событий в очереди программы.

Итак, если событие выбрано для окна, то оно будет передано ему на обработку. А если нет? В этом случае событие передается родителю окна. Если и тот не желает обращать внимание на данное событие, то оно отправляется дальше, вверх по иерархии окон, и так до тех пор, пока либо не будет найдено окно, выбравшее это событие, либо событие не потеряется.

Задача может влиять на этот процесс продвижения события по иерархии окон. Если программа включает флаг, соответствующий событию, в специальный атрибут окна, то оно, достигнув это окно, не будет передано родителю, а будет тут же "снято с повестки дня". Этот атрибут - do_not_propagate.



Многие атрибуты окна задаются при его создании с помощью процедуры XCreateWindow() или XCreateSimpleWindow(). Впоследствии параметры можно изменить, обратившись к процедуре XChangeWindowAttributes().

Характеристики окна описываются структурами типа TXSetWindowAttributes и TXWindowAttributes. Получить их можно с помощью процедуры XGetWindowAttributes().

Все они делятся на две группы. В первую входят параметры, доступные "на чтение" и "на запись". Вторая группа представляет собой внутренние данные. Программа может прочитать их, но не может менять.

Сначала перечислим поля этих структур, которые относятся к "изменяемым" параметрам.

Фон окна определяется атрибутами 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. Значение по умолчанию - CopyFromParent, которое говорит, что окно использует палитру своего непосредственного родителя.

Теперь рассмотрим "неизменяемые" параметры окна. Строго говоря, атрибуты, о которых пойдет речь, нельзя назвать неизменяемыми. Некоторые из них могут меняться сервером или менеджером окон. Но для обычных программ-клиентов они действительно являются таковыми.

Положение окна и его размеры сообщают поля x, y, width и height. Они дают координаты левого верхнего угла, ширину и высоту окна соответственно. Координаты измеряются в пикселях по отношению к родительскому окну.

Ширина края окна определяется параметром border_width.

Маска, говорящая о том, какие события выбраны для передачи окну породившим его клиентом, содержится в поле флагов your_event_mask. Значение параметра образуется комбинацией флагов, идентифицирующих события.

Информация о дисплее, на котором показано окно, содержится в структуре Visual, на которую показывает поле visual. Эти данные, как правило, не обрабатываются обычными программами-клиентами (заметим, что для получения информации о дисплее, в системе предусмотрена процедура XGetVisualInfo()).

Класс окна сообщает поле class. Возможные значения: InputOutput и InputOnly.

Число цветовых плоскостей дисплея (число бит-на-пиксел) помещается в поле depth.

На информацию об экране, на котором помещается окно, указывает поле screen. Она, как правило, не используется обычными программами.

Идентификатор главного (корневого) окна экрана, на котором помещается окно, находится в поле root.

Если окно имеет палитру, и она в настоящее время активна, то поле map_installed равно True, в противном случае - False.

Видно в настоящее время окно на экране или нет, сообщает атрибут map_state.

Маска всех событий, выбранных всеми программами для данного окна, содержится в атрибуте all_event_mask. Дело в том, что окно обрабатывается не только порождающим его клиентом, но, возможно, и другими приложениями, например, менеджером окон.

Мы рассказали о том, как получить атрибуты окна, и что они означают. Теперь рассмотрим, как их изменить. Для этого можно использовать несколько процедур X Window, основной из которых является XChangeWindowAttributes(), имеющая следующий прототип:
function XChangeWindowAttributes(prDisplay : PDisplay;
       nWnd : TWindow; nValueMask : cardinal;
       prWinAttr : PXSetWindowAttributes) : longint; cdecl; external;
Требуемые установки атрибутов передаются через аргумент prWinAttr. Он указывает на переменную типа TXSetWindowAttributes. Ее поля те же, что и соответствующие поля TXWindowAttributes. Разница заключается лишь в разных именах некоторых из них. Так, поле your_event_mask в TXWindowAttributes соответствует полю event_mask в TXSetWindowAttributes.

Структура TXSetWindowAttributes содержит дополнительное поле cursor. Оно определяет вид курсора мыши, когда последний находится в окне. Если поле равно None (значение по умолчанию), то используется курсор родительского окна, в противном случае значением параметра должен быть идентификатор того или иного курсора.

Параметр nValueMask при вызове указанной процедуры представляет комбинацию флагов, говорящих о том, какие из полей переменной prWinAttr принимать во внимание.

В следующем примере приведен фрагмент кода, в котором изменяются параметры border_pixmap и win_gravity некоторого окна:
.......
var
   prDisplay : PDisplay;
   prWnd     : TWindow;
   rWndAttr  : TXSetWindowAttributes;
   nValMask  : cardinal;
const
   nPixmap   : TPixmap =0;
.......
nValMask := CWBorderPixmap or CWWinGravity;
rWndAttr.border_pixmap := nPixmap;
rWndAttr.win_gravity := StaticGravity;
.......
XChangeWindowAttributes (prDisplay, prWnd, nValMask, @rWndAttr);
.......
Отдельные атрибуты окна можно изменить более просто с помощью специальных процедур. Так функция XSetWindowBackground() меняет фон окна, XSetWindowBorder() - его край.



Манипулировать окнами можно не только с помощью атрибутов: Xlib предоставляет набор функций для изменения их размеров, перемещения на экране и в стеке окон, сворачивания и т.п.

Первая пара операций, которые можно применить к окну - отображение или скрытие. Отображение окна заставляют окно появиться на экране, скрытие приводит к удалению с экрана (хотя логическое окно в памяти все еще существует). Например, если в вашей программе есть диалоговое окно, вместо создания его каждый раз по запросу пользователя, мы можем создать окно один раз в скрытом режиме и, когда пользователь запросит открыть диалог, просто отобразить окно на экране. Когда пользователь нажимает "OK" или "Cancel", окно скрывается. Это значительно быстрее создания и уничтожения окна, однако стоит ресурсов, как на стороне клиента, так и на стороне X сервера.

Отображение окна может быть выполнено с помощью XMapWindow(), скрытие - с помощью XUnmapWindow(). Функция отображения заставит событие Expose послаться программе, если только окно полностью не закрыто другими окнами.

Другое действие, которое можно выполнить над окнами - переместить их в другую позицию. Это может быть выполнено функцией XMoveWindow(), которая принимает новые координаты окна. Имейте в виду, что после перемещения окно может быть частично скрытым другими окнами (или наоборот, открыто ими), и таким образом, может быть сгенерировано сообщение Expose.

Изменить размер окна можно с помощью функции XResizeWindow(). Мы можем также объединить перемещение и изменение размеров, используя одну функцию XMoveResizeWindow().

Все приведенные выше функции изменяли свойства одного окна. Существует ряд свойств, связанных с данным окном и другими окнами. Одно из них - порядок засылки в стек: порядок, в котором окна располагаются друг над другом. Говорят, что окно переднего плана находится на верхе стека, а окно заднего плана - на дне стека. Перемещение окна на вершину стека осуществляет функция XRaiseWindow(), перемещение окна на дно стека - функция XLowerWindow().

С помощью функции XIconifyWindow() окно может быть свернуто, а с помощью XMapWindow() - восстановлено. Для того, чтобы понять, почему для XIconifyWindow() нет обратной функции, необходимо заметить, что, когда окно сворачивается, на самом деле оно скрывается, а вместо него отображается окно иконки. Таким образом, чтобы восстановить исходное окно, нужно просто отобразить его снова. Иконка является на самом деле другим окном, которое просто тесно связано сильно с нашим нормальным окном - это не другое состояние нашего окна.

Следующий пример демонстрирует использование операций над окнами:
uses x,xlib,xutil,crt,dos;

(*
 create_simple_window - создает окно с белым фоном заданного размера.
 Принимает в качестве параметров дисплей, размер окна (в пикселях)
 и положение окна (также в пикселях). Возвращает дескриптор окна.
 Окно создается с черной рамкой шириной в 2 пикселя и автоматичсеки
 отображается после создания.
*)
function create_simple_window(display : PDisplay;
              width, height, x, y : integer): TWindow;
var
  screen_num, win_border_width: integer;
  win: TWindow;
begin
 screen_num := XDefaultScreen(display);
 win_border_width := 2;

(*
 создаем простое окно как прямой потомок корневого окна экрана,
 используя черный и белый цвета в качестве основного и фонового, и
 размещая новое окно в верхнем левом углу по заданным координатам
*)
 win := XCreateSimpleWindow(display, XRootWindow(display, screen_num),
              x, y, width, height, win_border_width,
              XBlackPixel(display, screen_num),
              XWhitePixel(display, screen_num));

 (* Отображаем окно на экране. *)
 XMapWindow(display, win);

 (* Заставляем выполниться все запросы к Х серверу. *)
 XFlush(display);

 create_simple_window:=win;
end;


//void main(int argc, char* argv[])
var
 display: PDisplay; (* указатель на структуру дисплея Х *)
 screen_num: integer; (* количество экранов для размещения окон *)
 win: TWindow; (* дескриптор создаваемого окна *)
 display_width, display_height: word; (* высота и ширина Х дисплея *)
 win_width, win_height: word; (* высота и ширина нового окна *)
 display_name: array [0..30] of Char;
 name: string;
 i: integer;
 win_attr: TXWindowAttributes;
 xx, y, scr_x, scr_y: integer;
 child_win: TWindow;
 (* переменная для хранения дескриптора родительского окна *)
 parent_win: TWindow;
 (* эта переменная будет хранить дескриптор корневого окна *)
 (* экрана, на котором отображено наше окно *)
 root_win: TWindow;
 (* эта переменная будет хранить массив дескрипторов *)
 (* дочерних окон нашего окна, *)
 child_windows: PWindow;
 (* а эта - их количество *)
 num_child_windows: integer;

begin
 name := getenv('DISPLAY'); (* имя Х дисплея *)
 for i:=1 to byte(name[0]) do
   display_name[i-1]:=name[i];
 display_name[byte(name[0])]:=#0;
 (* устанавливаем соединение с Х сервером *)
 display := XOpenDisplay(display_name);
 if (display = NIL) then begin
  writeln(paramstr(0),': не могу соединиться с Х сервером ',
      display_name);
  halt(1);
 end;

 (* получаем геометрию экрана по умолчанию для нашего дисплея *)
 screen_num := XDefaultScreen(display);
 display_width := XDisplayWidth(display, screen_num);
 display_height := XDisplayHeight(display, screen_num);

 (* создаем новое окно в 1/9 площади экрана *)
 win_width := (display_width div 3);
 win_height := (display_height div 3);
 (* отладочная печать в стандартный вывод *)
 writeln('ширина окна - ', win_width, '; высота - ', win_height);

 (* создаем простое окно как прямой потомок корневого окна экрана,  *)
 (* используя черный и белый цвета в качестве основного и фонового, и*)
 (* размещая новое окно в верхнем левом углу по заданным координатам *)
 win := create_simple_window(display, win_width, win_height, 0, 0);

 XFlush(display);

 (* отдохнем после трудов праведных *)
 delay(3000);

 (* пример изменения размеров окна *)
 begin

  (* в цикле уменьшаем окно *)
  for i:=0 to 39 do begin
   dec(win_width,3);
   dec(win_height,3);
   XResizeWindow(display, win, win_width, win_height);
   XFlush(display);
   delay(20);
  end;

  (* в цикле увеличиваем окно *)
  for i:=0 to 39 do begin
   inc(win_width,3);
   inc(win_height,3);
   XResizeWindow(display, win, win_width, win_height);
   XFlush(display);
   delay(20);
  end;
 end;

 delay(1000);

 (* пример перемещения окна *)
 begin

  (* вначале получаем текущие атрибуты окна *)
  XGetWindowAttributes(display, win, @win_attr);

  xx := win_attr.x;
  y := win_attr.y;

  (* затем находим окно родителя *)
  begin

   (* выполним запрос необходимых значений *)
   XQueryTree(display, win,
         @root_win,
         @parent_win,
         @child_windows, @num_child_windows);

   (* мы должны освободить список дочерних дескрипторов, *)
   (* так как он был динамически выделен XQueryTree()  *)
   XFree(child_windows);
  end;

  (* Транслируем локальные координаты в экранные, используя  *)
  (* корневое окно как окно, относительно которого выполняется *)
  (* трансляция. Это работает потому, что корневое окно всегда *)
  (*занимает весь экран, и его левый верхний угол совпадает  *)
  (* с левым верхним углом экрана               *)
  XTranslateCoordinates(display,
             parent_win, win_attr.root,
             xx, y,
             @scr_x, @scr_y,
             @child_win);

  (* перемещаем окно влево *)
  for i:=0 to 39 do begin
   dec(scr_x,3);
   XMoveWindow(display, win, scr_x, scr_y);
   XFlush(display);
   delay(20);
  end;

  (* перемещаем окно вниз *)
  for i:=0 to 39 do  begin
   inc(scr_y,3);
   XMoveWindow(display, win, scr_x, scr_y);
   XFlush(display);
   delay(20);
  end;

  (* перемещаем окно вправо *)
  for i:=0 to 39 do  begin
   inc(scr_x,3);
   XMoveWindow(display, win, scr_x, scr_y);
   XFlush(display);
   delay(20);
  end;

  (* перемещаем окно вверх *)
  for i:=0 to 39 do  begin
   dec(scr_y,3);
   XMoveWindow(display, win, scr_x, scr_y);
   XFlush(display);
   delay(20);
  end;
 end;

 delay(1000);

 (* пример сворачивания и восстановления окна *)
 begin
  (* сворачиваем окно *)
  XIconifyWindow(display, win, XDefaultScreen(display));
  XFlush(display);
  delay(2000);
  (* восстанавливаем окно *)
  XMapWindow(display, win);
  XFlush(display);
  delay(2000);
 end;

 XFlush(display);

 (* короткая передышка *)
 delay(2000);

 (* закрываем соединение с Х сервером *)
 XCloseDisplay(display);
end.


1. Используя компилятор командной строки, выполните компиляцию примера из п. 1.1 и выполните полученную программу.


2. Настройте интегрированную среду Анюта для работы с кириллицей и выполните компиляцию в ней предыдущей программы.

3. На основе примера напишите программу, которая при нажатии клавиши мыши пишет сообщение Pressed, а при отпускании - Released.


4. Используя функции XFlush()/XSync() и delay(), напишите программу без цикла обработки сообщений, отображающую черное окно размером 100х100 пикселей в течение 5 секунд.