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








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

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

X Window -- восполняя пробелы. Часть 2 -- Xlib

Андрей Зубинский

Короткий обзор X Window в предыдущей части статьи, где не были отражены многие нюансы и даже ряд очень специфических понятий X Window, пока свидетельствует лишь о том, что система действительно сложна и функциональна. И чтобы использовать эту функциональность, необходимы удачный программный интерфейс и реализация этого интерфейса. К счастью, они существуют, называются, соответственно, Xlib.h и Xlib и обязательно входят в комплект поставки дистрибутива той или иной реализации X Window. В файле Xlib.h собраны описания основных типов данных и операций над ними, реализованных в рамках облегчающей разработку клиентских приложений библиотеки Xlib. С точки зрения C-программиста, может оказаться весьма важным своевременное замечание о том, что к большинству элементов сложных структур данных X Window нельзя обращаться "напрямую" -- для этого есть специальные функции, а многие типы данных определены в файле Xlib.h как "непрозрачные" (opaque, для объектов таких типов известны только их типизованные адреса, а сами реализации скрыты).


Дисплеи и экраны

Теперь совершим короткий экскурс в функциональное богатство Xlib. Начнем, естественно, с базовых понятий или, в терминах объектно-ориентированного проекта, -- с объектов. Самый главный в их обширном перечне -- "дисплей". Программа-клиент должна вступить в "общение" с каким-нибудь X-сервером -- иначе она прекратит выполнение (неспособность "сохранения состояния"). Естественно, что в процессе подключения программа-клиент должна как можно больше узнать о дисплее, с которым ей предстоит работать, -- в разношерстном мире систем, на которых работает X Window, любые допущения недопустимы. Инициация процедуры подключения программы-клиента к X-серверу предельно проста -- надо только вызвать функцию XOpenDisplay, передав ей в качестве параметра строку-имя X-сервера. XOpenDisplay возвращает указатель на opaque структуру данных, содержащую всю информацию об X-сервере: от версии ПО до диапазона кода клавиш. Для "вытягивания" этой информации в клиентской программе существует целый ряд операций: например, XDefaultDepth возвращает текущее значение количества битов на пиксел для данного дисплея, а XListDepth -- перечень возможных значений количества битов, используемых для представления пиксела, поддерживаемый дисплеем. XDefayltGC возвращает предустановленный по умолчанию графический контекст, XDefaultScreenOfDisplay -- номер экрана, на котором начнется последующий графический вывод, и т. д. Для оповещения X-сервера об окончании работы программа-клиент должна воспользоваться вызовом XCloseDisplay -- с крайне простым синтаксисом, но с очень сложной семантикой. И опять стоит предупредить читателя -- тонкие вопросы сложной семантики Xlib в статье рассматриваться не будут и из-за ограниченного объема, и по причине наличия хорошей документации на каждый вызов Xlib в базовой поставке системы. Мы же сконцентрируемся на концептуальных особенностях Xlib, без знания которых изучение системы станет неоправданно сложным.


Окна

Следующая обзорная часть -- операции с окнами. Здесь надо четко разделять три концептуальных представления объекта "окно" в X Window. Как уже говорилось в первой части статьи, "окно" является так называемым "ресурсом" (в терминах X Window). Но, кроме того, "окно" -- и сущность, обладающая рядом специфических графических атрибутов и характеристик, и отображаемая сущность, характеризующаяся, например, различными свойствами видимости/невидимости в разные моменты времени (если, конечно, это предусмотрел программист). Поэтому и предоставляемые Xlib функции для работы с объектами типа "окно" можно разбить на три условные категории: "уровня главных ресурсов", "уровня атрибутных ресурсов" и "визуальной". Основные особенности функций этих категорий заключаются в наблюдаемости результатов их выполнения -- вызовы функций "ресурсных" категорий незаметны на экране до вызова "визуальной" функции.

Первая категория -- уровня главных ресурсов -- очень компактна. Она включает в себя инициализацию (создание) объекта-ресурса "окно" и его уничтожение. Для создания окна используются два вызова Xlib -- XCreateWindow и XCreateSimpleWindow. Оба они возвращают идентификатор ресурса "окно", для обоих должен быть обязательно указан главный параметр -- идентификатор "родительского" окна, а отличия между ними минимальны. Впрочем, касаются они весьма специфического нюанса X Window, оставить без внимания который никак нельзя. Дело в том, что объекты типа "окно" могут принадлежать двум классам, существенно различающимся по внешнему представлению. Класс InputOutput -- это "обычные" окна, обладающие отображаемым содержимым (точнее, в терминах X Window, это свойство называется способностью окна принимать и обрабатывать графические запросы). Более непривычны представители класса InputOnly -- эти окна являются прозрачными невидимыми областями, способными только генерировать события, инициируемые устройствами ввода -- позиционирующим (например, мышью) и клавиатурой. "Обычность" окон класса InputOutput и вызвала потребность в создании дополнительного (но необязательного) вызова инициализации ресурса объекта "окно". XCreateSimpleWindow, который всегда создает окно класса InputOutput. Соответственно, функция XCreateWindow более универсальна и позволяет инициировать ресурсы типа "окно" любого класса. Раз объект "окно" можно инициировать (иначе -- создать ресурс или кэшировать X-сервером), значит, его можно и уничтожить. Xlib для выполнения такого действия предлагает две группы операций: во-первых, программист может принудительно уничтожить строго определенное окно и все окна, для которого оно является "родителем", во-вторых, программист может... этого не делать вообще -- в большинстве случаев ему достаточно сообщить X-серверу о желании завершить работу вызовом XcloseDisplay, остальное -- забота самого X-сервера. Имя команды принудительного удаления окна и его "детей" звучит вполне очевидно -- XdestroyWindow, и ей, естественно, надо указать идентификатор конкретного ресурса-"окна". Собственно, этим коротким перечнем и ограничивается первая, "ресурсная", категория функций Xlib, отвечающих за работу с окнами.

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

Итак, не вдаваясь в детали, после вызова функции создания окна и необязательного пост-изменения его атрибутов X-сервер формирует в собственной памяти объект "окно" определенного класса (InputOutput/InputOnly). На экране же, обслуживаемом X-сервером, визуально ничего не изменилось -- ведь были использованы только так называемые "ресурсные" вызовы. Естественно, что эти замечательные возможности совсем не соответствуют предназначению графической подсистемы и пора пускать в ход "визуальные" вызовы Xlib. Их также немного -- всего два: XMapWindow и XMapRaised. Слово "map" (помечать, отмечать) в именах этих функций использовано не случайно -- вызывая их для определенного идентификатора окна, мы "отмечаем" X-серверу, что хотели бы увидеть изображение на экране. X-сервер проверяет соблюдение ряда условий и, наконец, отображает желаемую "картинку". Разница между двумя вызовами невелика и заключается в положении отображаемого окна "по глубине" экрана. XMapWindow указывает X-серверу, что окно надо "нарисовать" именно на той "глубине", где оно было создано, ведь мы могли построить достаточно сложную иерархию из "окон"-ресурсов задолго до первого вызова "визуальной" функции. XMapRaised, вызванная для идентификатора некоторого окна, напротив, отмечает наше пожелание "нарисовать" это окно поверх всех остальных. А как быть, если мы хотим на время убрать "картинку" с экрана, но не уничтожать отвечающие за ее формирование ресурсы из памяти X-сервера, чтобы иметь возможность впоследствии быстро ее восстановить? Для этого предназначен вызов с очевидным названием XUnmapWindow, также попадающий в категорию "визуальных".

На этом этапе уже можно и подвести краткие итоги, и сделать некоторые успокаивающие уточнения. Итак, концептуальная последовательность операций на уровне Xlib, которая должна присутствовать в любой программе для X Window, выглядит приблизительно так:

Установка соединения с X-сервером:

XOpenDisplay

Создание оконной иерархии будущего приложения:

ряд вызовов XCreateWindow/XCreateSimpleWindow

Необязательная установка атрибутов некоторых окон:

ряд вызовов XChangeWindowAttributes

Указание X-серверу отобразить построенную оконную иерархию:

вызовы XMapWindow/XMapRaised для всех созданных окон

...

Необязательное принудительное закрытие окон:

вызовы XDestroyWindow для всех созданных окон

Отсоединение от X-сервера:

XCloseDisplay

Естественно, что даже в такой концептуальной картине есть нечто настораживающее. А именно, скрытое требование к внимательности программиста--разработчика ПО для X Window. Ведь идентификаторы окон всей иерархии надо не просто хранить, но и, например, гарантированно выполнять для всех них "визуальные" вызовы, чтобы на экране отобразилась "правильная картинка". Вот и настало время успокаивающего уточнения -- Xlib располагает дополнительными функциями, играющими роль итераторов. Для большинства упомянутых функций, работающих с одним из множества объектов, есть их итеративные аналоги. Так, например, вызов XMapWindow для одного окна дублирован вызовом-итератором XMapSubwindows, который "отмечает" указанием "отобразить" все окна, чьим "родителем" является переданное в вызове. По аналогичному принципу работают вызовы XUnmapSubwindows и XDestroySubwindows -- укажите им идентификатор некоторого окна и переложите на X Window заботу и об "обходе" всех окон, родителем которых оно является, и о выполнении для каждого соответствующей операции.


События

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

В терминах X Window под событиями понимается информация о чем-то "стороннем" по отношению к программе-клиенту, но о чем она должна быть проинформирована. События разделены на две основные категории -- "непосредственные", инициируемые человеком-пользователем, и "косвенные", инициируемые X-сервером или другой программой-клиентом. Очевидно, что человек-пользователь может воздействовать на X-сервер только с помощью двух доступных средств -- клавиатуры и позиционирующего устройства (ПУ). И ничего неожиданного и сложного нет в том, что в категорию "непосредственных" событий входят ButtonPress (нажатие клавиши ПУ), KeyPress (нажатие клавиши клавиатуры), MotionNotify (перемещение ПУ) и т. п. "Косвенные" события интересней и намного сложней -- они формируются X-сервером на основе кэшированной информации об оконных иерархиях и отражают изменения в последних. Впрочем, весьма условные вопросы классификации всего тридцати событий -- это далеко не самое главное и сложное в механизме событий X Window. Самое главное (с точки зрения программиста) -- концептуальная модель самого этого механизма, выраженная в уже известных и новых терминах Xlib. А она одновременно сложна и проста, и именно ей мы посвятим этот раздел статьи.

В основе механизма сообщений лежит понятие "очереди сообщений" (events queue). "Очередь" -- абстрактный тип данных, обеспечивающий две базовые асинхронные операции: запись_в_очередь/чтение_из_очереди и поддерживающий политику "первым вошел -- первым вышел", т. е. данные из очереди читаются в порядке их записи в очередь.

X Window иногда называют "системой N+1 очередей" -- в этом названии достаточно точно отражена основная идея ее системы событий: X-сервер поддерживает главную очередь событий, а Xlib реализует дополнительные очереди -- для каждого клиентского приложения. Собственно, в этом и заключается вся "архитектурная сложность", а настоящие сложности начинаются в нюансах. Первый, естественно, связан с ограничениями, диктуемыми реальностью, а именно, с разнообразием событий и множеством окон в каждом приложении. Тридцать событий и несколько сотен окон (или даже несколько тысяч) образуют слишком большое количество комбинаций, а если учесть, что реакции на одинаковые события в разных окнах могут понадобиться разные... И здесь разработчики X Window нашли весьма успешное решение проблемы -- реализовав так называемый "механизм интересов окна" (название это ни в коей мере не официальное). Суть его заключается в том, что в перечне атрибутов каждого окна присутствует перечисление событий, информация о которых нужна данному окну. Раз это атрибут окна, значит, он устанавливается при создании окна посредством тех же функций XCreateWindow/XCreateSimpleWindow, а впоследствии его можно изменить с помощью опять же уже известного нам вызова XChangeWindowAttributes или специального -- XSelectInput. Перечисление событий, попадающих в "круг интересов окна", реализуется тривиально -- битово-масочным способом, при котором в битовом массиве достаточного размера каждый бит является аналогом тумблера "Вкл/Выкл" для соответствующего события. Количество событий X Window (30) обеспечивает эффективную упаковку всего перечисления в одно машинное слово современных 32-битовых машин и оставляет возможности для расширения функциональности системы событий в будущем. Но вернемся к общей картине: "механизм интересов окна" -- это то, что, с одной стороны, позволяет Xlib по-умному распоряжаться ресурсами, избегая ненужного копирования всех событий от одного X-сервера в очередях всех клиентских приложений, с другой -- освобождает программиста от необходимости мучительного отбора только интересующих его событий. Естественно, что вся реализация самих очередей событий и механизма отбора нужных событий в очереди клиентских программ никаких действий от программиста не требует -- после конфигурирования "механизма интересов окна" он должен только все время "просматривать" очередь событий своей задачи и активировать необходимые действия при нахождении соответствующих событий. Термин "все время" в данном случае означает все время исполнения клиентской программы, а для просмотра очереди сообщений в Xlib предусмотрен богатый набор функций, таких, как XNextEvent, XMaskEvent, XCheckMaskEvent, XWindowEvent, XCheckWindowEvent, XIfEvent, XCheckIfEvent и т. д. Сразу можно обратить внимание на "неожиданное" изобилие операций с очередью сообщений по сравнению с предыдущим минимализмом -- мы столкнулись с ключевыми функциями X Window. Но и они совсем не сложны. Так, например, XNextEvent просто читает из очереди событий клиентского приложения информацию о любом событии, а если очередь пуста -- ожидает появления такой информации.

Теперь мы можем уточнить нашу концептуальную модель программы для X Window, добавив ей способность реагировать на события.

Установка соединения с X-сервером:

XOpenDisplay

Создание оконной иерархии будущего приложения:

ряд вызовов XCreateWindow/XCreateSimpleWindow

Необязательная установка атрибутов некоторых окон:

ряд вызовов XChangeWindowAttributes

Конфигурирование "механизма интересов окна":

ряд вызовов XSelectInput

Указание X-серверу отобразить построенную оконную иерархию:

вызовы XMapWindow/XMapRaised для всех созданных окон
Выполнять всегда:

прочитать событие из очереди сообщений:

XNextEvent
определить тип прочитанного события
выполнить действия, ассоциированные с событием данного типа.

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

И это все?

Конечно же, нет. За пределами статьи осталась тьма нюансов, возможностей, "подводных камней" и расширений Xlib. Впрочем, автор и не ставил перед собой заведомо невыполнимой задачи -- "обучить X Window за 10 минут". Зато все оказалось совсем не таким страшным, и заинтересовавшиеся читатели могут продолжить самостоятельное изучение X Window с посещения персонального сайта признанного специалиста Кентона Ли, где собрана каталогизированная подборка ссылок на множество посвященных X Window-тематике ресурсов.

 

Андрей Зубинский
itc.ua

Окончание статьи