Англ. оригинал был взят с http://ktown.kde.org/~fredrik/composite_howto.html
перевод: Иванов Аркадий
Введение
Об этом руководстве
Сегодня, когда Сomposite появился в релизе X.Org, множеству людей будет интересно использовать это расширение не только с точки зрения прозрачности и приятностей для глаз, но и с точки зрения разработчика, которому нужно доступиться к содержимому окон, покрытых сверху другими окнами.
Это руководство о том, как используя расширения Composite и Xrender делать последнее. Отслеживание изменений в содержимом окон с помощью Xdamage крепко связано с доступом к содержимому окон, так что, это тоже обсуждается в данном руководстве.
Это руководство в основном предназначено для тех, кому интересно использовать Composite для создания миниатюр на переключателе рабочих столов, или для тех, кто работает над фичами типа Exposй. Однако представленные концепции применимы также и композитным менеджерам. Это руководство вовсе не одно единственное, что вам может понадобиться для работы с расширением Composite, но я всё-таки постаралося раскрыть все аспекты его использования для вышеупомянутых целей. Оно также должно помочь вам избежать ловушек, в которые вы можете попасть при использовании расширения Composite
Вам необязательно иметь предыдущий опыт использования X напрямую из Qt-приложений, но если у вас есть общие представления о том, как работает X и как приложения взаимодействуют с X сервером, это вам поможет. Опыт программирования с Xlib поможет тоже.
Когда содержимое окон становится доступным через Composite
Перед тем, как начать, я чувствую, что должен прояснить неправильное представление о том, в какой момент Composite делает доступным содержимое окна. Пиксельная карта, обеспечивающая изображение, выделяется в тот момент, когда окно показывается, и уничтожается, когда окно делается скрытым (hidden). Другими словами это означает, что когда окно минимизировано или не находится на текущем рабочем столе, содержимое окна недоступно.
Есть основные две причины, почему composite работает именно так. Первая - это минимизация видеопамяти, требуемой в каждый момент времени для хранения пиксельных карт, обеспечивающих изображения. Второе - в традиционном дизайне X рисование на скрытом окне даст результат NOOP (нет операции). Умные приложения и тулкиты знают об этом и никогда не рисуют в минимизированных окнах.
Всё же это может быть разочарованием для тех, кто надеялся на то, что Composite позволит им, например, находиться над полоской активных задач и выдавать подсказки с помощью миниатюр минимизированных окон или окон с других рабочих столов (чтобы пользователь видел в миниатюрах происходящие изменения в окнах).
"Полоска задач/переключатель рабочих столов", которые хотят сделать такое, в лучшем случае должны сохранить кэшированную миниатюру содержимого окна до момента когда окно будет отмаппировано от экрана. Composite на сегодня даёт лёгкий способ сделать это, что и будет разъяснено в этом руководстве.
Замечу, что хотя содержимое минимизированного окна никогда не будет доступно, содержимое окна на неактивном рабочем столе может быть доступно. Это зависит от дизайна менеджера окон. Если менеджер окон использует виртуальное корневое окно для каждого рабочего стола и не отмаппирует неактивные рабочие столы, тогда содержимое окон на таких рабочих столах будет всё-таки доступно. Однако в разработке KWin и Metacity не используется такой приём.
Причины использования Xrender для доступа к содержимому
Я также кратко поясню откуда берётся мотивация использовать Xrender для доступа к содержимому окна. Если вы хотите использовать протокол ядра X, вы однозначно можете создать GC для окна и затем задействовать XCopyArea() для копирования его содержимого, однако с того момента как расширение Composite extension даёт новые представления(visuals) (например, такие, что включают в себя альфа-каналы), теперь нет гарантии, что формат источника drawable будет соответствовать формату назначения. При использовании протокола ядра X такая ситуация приведёт к ошибке соответствия (match error), что никогда не случиться при использовании расширения Xrender.
Вдобавок протокол ядра X не имеет понятия об альфа-каналах, что означает, что он не сможет наложить окна, которые используют новое ARGB представление. Также нет никакого выигрыша в производительности когда источник и приёмниа имеют одинаковый формат в использовании протокола ядра X11R6.8. Этот релиз первый, который поддерживает новое расширение Composite.
Таким образом нет никаких недостатков в выборе использования Xrender вместо протокола ядра, а только одни преимущества.
Проверка того, что X server поддерживает расширение Composite
Первое, что следует сделать - это проверить во время выполнения задачи, что X-сервер, к которому вы подключены, поддерживает в данный момент расширение Composite. Следует сделать эту проверку и во время компиляции, однако такая проверка даст только то, что вам доступна библиотека поддержки Composite, а не то, что X-сервер, используемый приложением соответствует этому требованию.
То того, как вы вызовете какую-либо Xlib-функцию, вы должны получить указатель от Qt на структуру Display struct, который будет являться первым аргументом при вызове любой Xlib-функции. Структура Display содержит информацию о коннекте к X-серверу, такую, как номер сокета. Этот указатель получается вызовом статической функции QPaintDevice::x11AppDisplay(), Если вам нужен указатель на Display, изнутри класса, который наследует QPaintDevice, вместо этого следует вызвать x11Display().
Display *dpy = QPaintDevice::x11AppDisplay();
Отмечу, что объект KApplication object должен быть создан до того, как издаётся запрос к Qt для получения указателя на Display, поскольку он не будет инициализирован до момента, пока QApplication установит соединение к X-серверу. Как только у нас есть указатель на структуру Display struct, мы можем продолжить проверку наличия расширения Composite:
bool hasNamePixmap = false;
int event_base, error_base;
if ( XCompositeQueryExtension( dpy, &event_base, &error_base ) )
{
// Если мы сюда попали, сервер поддерживает расширение Composite
int major = 0, minor = 2; // Самая верхняя версия, что мы поддерживаем
XCompositeQueryVersion( dpy, &major, &minor );
// major и minor будут содержать верхнюю версию, которую поддерживает сервер
// В протоколе указано, что возвращаемая версия никогда не будет больше
// той, что запрошена. Версия 0.2 - это первая версия, которая имеет
// запрос XCompositeNameWindowPixmap().
if ( major > 0 || minor >= 2 )
hasNamePixmap = true;
}
Перенаправление всех окон верхнего уровня на внеэкранные пиксельные карты
Как только мы удостоверились, что X server поддерживает расширение Composite, следует удостовериться, что содержимое окна, которое мы хотим захватывать, будет доступным через поддерживающую пиксельную карту (backing pixmap).
Это достигается вызовом XCompositeRedirectSubwindows(). Для каждого корневого окна делается отдельный вызов. Указывается, что мы хотим автоматическое перенаправление.:
for ( int i = 0; i < ScreenCount( dpy ); i++ )
XCompositeRedirectSubwindows( dpy, RootWindow( dpy, i ),
CompositeRedirectAutomatic );
Это перенаправит каждое текущее и будущее окно верхнего уровня в внеэкранное хранилище. На многоголовых видекартах у нас будет больше, чем один Screen(экран) (это не касается серверов с поддержкой Xinerama).
Не следует беспокоиться об обратном перенаправлении окон, поскольку это делается автоматически при завершении нашего приложения. Также, если композитный менеджен уже перенаправил окна, данный вызов даст NOOP, так что, вам не следует беспокоиться о нарушении работы, например, xcompmgr.
Сами окна не замечают того факта, что они были перенаправлены в внеэкранный буфер, так что, для того, чтобы работали уже существующие приложения, не требуется никаких модификаций. С точки зрения пользователей и с точки зрения приложений, всё продолжает работать так же, как и до момента перенаправления окон.
Замечу, что если вы знаете, что будете работать не более чем с одним окном, лучше всего его перенаправить используя XCompositeRedirectWindow(). Когда вы сделаете всё, что требуется, вы можете отменить перенаправление(используя XCompositeUnredirectWindow()).
Ссылка на содержимое окна
Если вы хотите иметь доступ к содержимому окна, в первую очередь вы должны знать его ID. Может показаться сооблазнительным создать QPixmap и просто копировать содержимое окна в него. Но следует помнить, что окно уже имеет пиксельную карту, и если вы хотите хранить содержимое многих окон, создавая для каждого окна QPixmap, в конце концов это закончится тем, что каждое окно будет дважды храниться в видеопамяти. Другими словами - это не хорошая идея. Однако если вы всего лишь хотите хранить миниатюры каждого окна, будет хорошей идеей кэшировать их в QPixmaps.
Получение информации об окне
Хорошо, предположим что мы знаем ID окна, однако нам следует узнать некоторые данные об этом окне, например, его размеры, формат пикселей хранящей его пиксельной карты и есть ли у окна альфа-канал или нет.
Оказывается мы можем получить большую часть этих данных вызовом calling XGetWindowAttributes():
// Нам надо определить некоторые данные об окне, такие как его размеры, его позиция
// на экране, и формат пиксельных данных
XWindowAttributes attr;
XGetWindowAttributes( dpy, wId, &attr );
XGetWindowAttributes это синхронный вызов (блокирующий), так что вы не захотите слишком часто вызывать эту функцию. В реальном приложении вам также следует проверять возвращаемое значение, поскольку благодаря асинхронной природе X, окно могло быть разрушено в тот период времени, когда вы сделали вызов.
Когда вызов XGetWindowAttributes() заполнил структуру XWindowAttributes, мы вытащим данные, которые нам необходимы:
XRenderPictFormat *format = XRenderFindVisualFormat( dpy, attr.visual );
bool hasAlpha = ( format->type == PictTypeDirect && format->direct.alphaMask );
int x = attr.x;
int y = attr.y;
int width = attr.width;
int height = attr.height;
Создание Render картинки, чтобы иметь доступ к содержимому окна
Хорошо, теперь мы знаем размеры окна, формат пикселей, и имеет ли окно альфа-канал или нет. Что теперь нам надо, так это создать XRender картинку для окна, которую нам надо будет в будущем отрисовывать с помощью расширения Render. Картинка это элемент управления структурой данных со стороны сервера с некоторой дополнительной информацией о рисуемом объекте (в данном случае объектом является окно). Этой дополнительной информацией является формат, какой регион обрезания следует использовать при отрисовке (если таковой есть), должно ли окно быть tiled, и т.д.
Создание картинки для отрисовываемого объекта делается вызовом XRenderCreatePicture(). В отличии от XGetWindowAttributes() это асинхронная функция, так что она не вернёт ошибку в случае, когда окно не существует. Вместо этого будет приложению будет послана ошибка X после того, как X сервер обработает запрос, и возвращённый управляющий указатель на картинку будет иметь недопустимое значение (что вызовет ошибки X сервера, если этот указатель будет использован в последующих вызовах функций).
Правильная обработка подобных ошибок достаточно сложна и не рассматривается в данном руководстве. Однако эти ошибки безобидны и будут иметь результатом только сообщения об ошибках на консоли. Когда ваше приложение вернёт управление из цикла обработки событий, вы получите сообщение о том, что окно было удалено.
// Создаём Render картинку для того, чтобы мы могли иметь ссылку к содержимому окна.
// Следует установить режим subwindow в IncludeInferiors, иначе графические элементы потомки
// в окне не будут включены в отрисовку, когда мы будем рисовать. А это совсем не то, что мы хотим.
XRenderPictureAttributes pa;
pa.subwindow_mode = IncludeInferiors; // Не откидываем графические элементы потомки
Picture picture = XRenderCreatePicture( dpy, wId, format, CPSubwindowMode, &pa );
Для того, чтобы не повторять эти шаги каждый раз, когда вы захотите отрисовать окно, возможно вы поместите этот код в класс. Вы также можете поместить в этот класс метод draw() дл я отрисовки окна на QPixmap/QWidget.
Корректная обработа непрямоугольных окон
Пиксельные карты содержимого окон всегда прямоугольные, но если окно имеет непрямоугольную форму, нам бы не хотелось получить завершение программы при копировании пикселей, которые не являются частью окна, когда мы его отрисовываем (эти пиксели неопределены). Чтобы этого избежать, необходимо установить регион обрезания для картинки в соответствии с формой региона окна. Чтобы это сделать, нам нужно использовать расширение XFixes. Предварительно следует осведомиться у X сервера, поддерживает ли он XFixes, как мы раньше делали это для расширения Composite.
// Создадим копию ограничивающего региона окна
XserverRegion region = XFixesCreateRegionFromWindow( dpy, wId, WindowRegionBounding );
// Значения границ региона заданы относительно экрана, а не окна, так что, нужно сделать смещение
// на позицию окна
XFixesTranslateRegion( dpy, region, -x, -y );
XFixesSetPictureClipRegion( dpy, picture, 0, 0, region );
XFixesDestroyRegion( dpy, region );
Полезно знать, что когда вы отрисовываете пиксельную карту с помощью Xrender, вы задаёте картинки отрисовываемых объектов и источника и приёмника, и то, что картинки и источника и приёмника могут иметь установленными регионы обрезания. Таким образом если вы не планируете масштабировать окно, когда вы его рисуете, вы можете установить регион обрезания только на на картинке назначения.
Не забудьте, что поскольку этот регион всего лишь копия, его следует изменять после изменения размеров окна или когда форма окна изменилась.
Расширение XShape может обеспечить нас оповещениями в моменты, когда форма окна меняется. Но для этого специально необходимо попросить его об этом. Указание XShape слать такие оповещения делается вызовом функции XShapeSelectInput():
XShapeSelectInput( dpy, wId, ShapeNotifyMask );
Смотрите раздел о том, как перехватывать события X сервера, чтобы получать их себе.
Отрисовка окна на QWidget или на QPixmap
У нас теперь есть вся требуемая информация для того, чтобы мы могли рисовать окно с помощью расширения Xrender, и мы создали и подготовили картинку источник для окна, которую и можно использовать для этой цели.
Функция Xrender, которую мы используем для отрисовки окна, называется XRenderComposite(). В файле заголовке (.h) она определена следующим образом:
void
XRenderComposite (Display *dpy,
int op,
Picture src,
Picture mask,
Picture dst,
int src_x,
int src_y,
int mask_x,
int mask_y,
int dst_x,
int dst_y,
unsigned int width,
unsigned int height);
Как вы можете заметить, эта функция использует картинку источник, картинку приёмник и при необходимости картинку маску. В нашем случае нам нужна маска, но нам будет нужна и картинка приёмник.
Мы хотим рисовать окно на QWidget или QPixmap, и оказывается, что объекты обоих этих типов уже имеют render картинки (если Qt был собран с поддержкой Xft/Xrender). Доступ к картинке делается вызовом метода x11RenderHandle() в QWidget или в QPixmap.
Важным параметром для XRenderComposite() является второй параметр - op, который указывает как будут скомбинированы писксели источника и приёмника. Для наших целей интересны только 2 render операции - PictOpSrc and PictOpOver.
PictOpSrc указывает, что пиксели приёмника должны быть замещены пикселями источника (dst = src), включая и альфа-значения. PictOpOver соответствует оператору Porter/Duff Over, который указывает, что Xrender должен использовать альфа значения пикселей источника pixels для смешивания их с пикселями приёмника (dst = src Over dst).
Таким образом PictOpSrc не будет смешивать окно, в то время как PictOpOver будет. PictOpSrc это более быстрая операция и в большинстве случаев гарантировано, что она будет использовать акселерацию, так что, когда окно не имеет альфа-канала, мы захотим использовать её. Когда окно имеет альфа-канал мы захотим использовать PictOpOver.
В следующем примере приёмник должен быть или QWidget или QPixmap. destX и destY это X и Y координаты в виджете или пиксельной карте, где вы хотите, чтобы окно было нарисовано.
// [Заполним приёмный виджет/пиксельную карту тем, что мы хотим использовать здесь как фон]
XRenderComposite( dpy, hasAlpha ? PictOpOver : PictOpSrc, picture, None,
dest.x11RenderHandle(), 0, 0, 0, 0, destX, destY, width, height );
Отслеживание повреждений(damage) и других изменеий окна
Если вы интересуетесь только тем, чтобы сделать однократный снимок окна, и вам не важны изменения в снимке при изменениях окна, вы можете пропустить этот раздел.
Первое, что нам следует сделать, если мы хотим отслеживать повреждения окна, это запросить X сервер о том, поддерживает ли он расширение damage, и в этот момент нам надо сохранить указать базу событий для будущего использования. Читайте дальше, чтобы узнать зачем.
int damage_event, damage_error; // База событий здесь важна
XDamageQueryExtension( dpy, &damage_event, &damage_error );
После того, как мы убедились, что X сервер поддерживает расширение damage, необходимо создать обработчик damage для каждого окна, для которого нам это интересно. Есть несколько способов, которыми Xdamage может сообщить об изменениях в окне, и в данном случае мы указываем, что мы хотим получать событие когда бы не происходило изменение состояния окна из неповреждённого в повреждённое. Выделенный нами обработчик должен быть уничтожен вызовом XDamageDestroy() после того, как наша потребность в получении событий damage от окна исчезла.
// Создаём обработчик damage для window, и указываем, что нам нужно событие во всех случаях
// когда состояние damage меняется с неповреждённого на повреждённое.
Damage damage = XDamageCreate( dpy, wId, XDamageReportNonEmpty );
Вы можете заметить, что в отличие от большинства других расширений, которые передают оповещения, Xdamage передаёт оповещения damage объектами, а не вызовом XDamageSelectInput(). Вы вспомните это из раздела о фигурных окнах, где расширение XShape позволяет сделать вызов XShapeSelectInput(), чтобы вы могли получать оповещения об изменениях формы окна.
Следует отметить ещё одну интересную вещь о Xdamage - это расширение не отслеживает не только повреждение окон. Оно также может отслеживать повреждения в пиксельных картах. И если вам оно надо, вы можете это использовать.
Перехват событий X events по мере их получений
X постоянно шлёт события в приложение через сетевой сокет, где они demarshalled в вставляются в очередь событий. Приложение вытаскивает по одному события из очереди с помощью вызова XNextEvent().
В приложениях Qt всё это обслуживается объектом QApplication, но в некоторых случаях нам хочется быстренько взглянуть на каждое получаемое X событие до того как QApplication обработает его.
Оказывается есть способ сделать это, даже два способа на самом деле. В приложении Qt это делается пересозданием метода QApplication::x11EventFilter( XEvent * ), который каждый раз вызывается, когда событие вынимается из очереди, но до момента обработки его в Qt. У нас даже есть выбор - проглотим ли мы событие в своём обработчике (возвращая из метода значение true), или указать Qt, что она должна продолжить обрабатывать событие (вернув из метода значение false).
В приложении KDE мы также можем вызвать KApplication::installX11EventFilter( QWidget * ), что скажет KApplication пересылать каждое полученное X собитие в указанную нами x11Event( XEvent * ) функцию виджета.
Как я заметил ранее, важно сохранять базу событий для расширения damage. Подошло время объяснить, что вам надо для этого. Каждое X событие имеет уникальный номер, который идентифицирует это событие. В протоколе ядра X номера событий начинаются с числа 0.
Поскольку может быть любое количество расширений X и любое расширение может добавить любое число событий, базовый номер событий, которые выдаёт расширение, зависит от конкретной реализации X, и должно быть получено от X сервера во время работы
База событий должна добавляться к константам, идентифицирующим события от расширения для того, чтобы рассчитать текущий номер события. Вы посмотрите как это делается в последующем примере.
Для каждого события X есть соответствующая структура данных, и XEvent - это union всех возможных структур событий. Первый элемент, который общий для всех структур, это type, который содержит номер события, который говорит нам, какой тип события содержит структура. Структура XEvent struct должна быть переопределена в структуру соответстующего события, т.е. если typeэто ConfigureNotify, то XEvent должна быть переопределена в XConfigureEvent.
Вот простая реализация функции, которая получает события X, проверяет поле type , и обрабатывает события damage, shape и configure:
bool x11EventFilter( XEvent *event )
{
if ( event->type == damage_event + XDamageNotify ) {
XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>( event );
// e->drawable - ID повреждённого окна
// e->geometry - геометрия повреждённого окна
// e->area - ограничивающий прямоугольних повреждённой области
// e->damage - обработчик damage, который вернул вызов XDamageCreate()
// Вычесть все повреждения, восстановим окно
XDamageSubtract( dpy, e->damage, None, None );
}
else if ( event->type == shape_event + ShapeNotify ) {
XShapeEvent *e = reinterpret_cast<XShapeEvent*>( event );
// Вполне может быть безопасно предположить, что регион формы окна
// в данном месте программы содержит недопустимое значение...
}
else if ( event->type == ConfigureNotify ) {
XConfigureEvent *e = &event->xconfigure;
// Размер окна, Z индекс в стеке окон поменялся
}
return false;
}
Помните, что в очереди событий может быть несколько событий одного типа для одного и того же окна, так что подождите до конца обработки их всех прежде чем предпринимать какие-либо действия. Это важно, поскольку в очереди событий окна может быть и событие DestroyNotify, например, идущее после события damage.
Заметьте, что в вышеприведённом примере все повреждения вычитаются из окна, но текущий регион повреждений откинут. В данном примере регион повреждений извлекается из объекта damage, и устанавливается как область обрезания для картинки:
// Создать пустой регион
XserverRegion region = XFixesCreateRegion( dpy, 0, 0 );
// Скопируем регион повреждений в region, вычитая его из повреждений окна
XDamageSubtract( dpy, e->damage, None, region );
// Сдвинем регион в позицию окна
XFixesTranslateRegion( dpy, region, e->geometry.x, e->geometry.y );
// Установим регион как область обрезания для картинки
XFixesSetPictureClipRegion( dpy, picture, 0, 0, region );
// Освободим регион
XFixesDestroyRegion( dpy, region );
В результате только повреждённые пиксели будут копироваться, когда окно будет отрисовываться, но это делать необязательно, если вы масштабируете окно во время отрисовки. Масштабирование обсуждается в следующем разделе.
Продвинутые концепции
Использование матрицы трансформации: масштабирование и вращение
Если мы хотим получить миниатюру окна, необходимо его отмасштабировать, так что, я вкратце упомяну, как это сделать с помощью расширения render. Делание этого с помощью Xrender имеет преимущество перед деланием этого на стороне сервера, поскольку не требует передачи изображения. А это очень важно, особенно для удалённых соединений с X сервером.
То, что нам надо сделать, так это задать матрицу трансформации для картинки, которая вызовет масштабирование содержимого в требуемый нам размер, когда будет отрисовываться. XTransform работает похожим образом как и QWMatrix.
double scale = .5; // Уменьшим размер окна до 50% от исходного размера
// Матрица масштабирования
XTransform xform = {{
{ XDoubleToFixed( 1 ), XDoubleToFixed( 0 ), XDoubleToFixed( 0 ) },
{ XDoubleToFixed( 0 ), XDoubleToFixed( 1 ), XDoubleToFixed( 0 ) },
{ XDoubleToFixed( 0 ), XDoubleToFixed( 0 ), XDoubleToFixed( scale ) }
}};
XRenderSetPictureTransform( dpy, picture, &xform );
Поскольку XTransform является projective матрицей трансформации, масштабирование - это не единственное возможное преобразование. Для примера ниже дана матрица, с помощью которой изображение поворачивается на 30 градусов по часовой стрелке..
double angle = M_PI / 180 * 30; // 30 градусов
double sina = std::sin( angle );
double cosa = std::cos( angle );
// Rotation matrix
XTransform xform = {{
{ XDoubleToFixed( cosa ), XDoubleToFixed( sina ), XDoubleToFixed( 0 ) },
{ XDoubleToFixed( -sina ), XDoubleToFixed( cosa ), XDoubleToFixed( 0 ) },
{ XDoubleToFixed( 0 ), XDoubleToFixed( 0 ), XDoubleToFixed( 1 ) }
}};
Замечу, что центр вращения - это верхний левый угол, а не центр картинки, так что следует сдвинуть картинку, когда вы её будете рисовать, чтобы скомпенсировать этот момент.
Поскольку конечное изображение получается несколько ступенчатым, вы возможно захотите использовать фильтр во время преобразования. Все реализации render должны поддерживать два фильтра - nearest neighbor(ближний сосед) и bilinear(билинейный), но могут поддерживать любое число фильтров, таких как гауссов или даже arbitrary convolution фильты (произвольные криволинейные?). Запрос к XRenderQueryFilters() возвращает список фильтров, поддерживаемых данной реализацией render.
В этом примере мы говорим render использовать фильтр bilinear. Когда вы используете фильтр вы возможно захотите использовать для рисования операцию PictOpOver, вне зависимости от того, есть ли у картинки источника альфа-канал или нет, поскольку после применения фильтра на границах могут появиться альфа данные.
XRenderSetPictureFilter( dpy, picture, FilterBilinear, 0, 0 );
Следует помнить, что трансформации проводяться в реальном времени каждый раз, когда картинка отрисовывается, так что есть некоторый смысл кэшировать результирующие изображения.
Как предохранить от высвобождения пиксельную карту,хранящую изображение во время спрятывания/разрушения окна
Если вы хотите, чтобы содержимое окна было всё ещё доступно после того, как окно было разрушено, или после того, как окну изменили размеры (но оно всё ещё не перерисовано), вы можете увеличить счётчик ссылок к хранящим пиксельным картам, чтобы предотвратить их освобождение:
Pixmap windowPix = XCompositeNameWindowPixmap( dpy, wId );
Как вы видите, эта функция возвращает элемент управления пиксельной картой, которую в настоящий момент использует окно. И этот элемент управления отличается от ID окна. ID окна, в отличие от управляющего элемента пиксельной картой будет недопустим после разрушения окна.
Чтобы от этой функции была польза, её необходимо вызвать до вызова XRenderCreatePicture(), и подменить в вызове параметр wId parameter на параметр windowPix.
Важно помнить, что после этой процедуры картинка будет ссылаться на эту конкретную храниящую пиксельную карту, а не на какую-либо текущую хранящую пиксельную карту связанную с окном (к которой можно получить доступ через ID окна).
Предостережение состоит в том, что когда окну изменяют размеры, composite выделяет новую хранящую пиксельную карту для окна с новым размером, и когда это случится, картинка продолжит ссылаться на несвязанную с окном пиксельную карту, содержащую вид окна до изменения размеров
Необходимо отслеживать события изменения размерова окна, и когда окно изменило размеры, необходимо убрать ссылку с хранящей пиксельной карты(используя XFreePixmap()) и разрушить картинку, затем вызватьl XCompositeNameWindowPixmap() чтобы получить новый управляющий элемент хранящей пиксельной карты backing pixmap, и пересоздать картинку.
Чтобы получать события изменения размеров окна, необходимо сообщить об этом своём желании X серверу с помощью следующего вызова:
XSelectInput( dpy, wId, StructureNotifyMask );
Чтобы получать текущие события, смотрите раздел о перехвате X событий в приложениях KDE. События, которые вас интересуют называются ConfigureNotify.
Не забывайте уничтожать ссылки на пиксельные карты после уничтожения окон. В противном случае у вас кончится память под пиксельные карты.
Преобразование содержимого окна в QImage
В некоторых случаях интересно получить снимок окна и сохранить его на диск. Одним из путей - создать QPixmap с теми же размерами, что и окно, скопировать содержимое пиксельной карты и затем использовать QPixmap::convertToImage(). Проблемой этого решения является ограничения на работу с альфа-каналами в Qt3 (и в Qt4 TP1). Альфа-канал в окне будет потерян, если вы так сделаете.
Если окно имеет альфа-канал, единственный путь сегодня, это использовать XGetImage(), чтобы получить содержимое окна в XImage, и затем вручную сконвертировать XImage в QImage, используя информацию из XRenderPictFormat. Когда вы это делаете, помните, что представление ARGB32 это перемноженный альфа-формат, в то время как формат QImage таковым не является.
// Конвертация содержимого окна в XImage
XImage *image = XGetImage( dpy, wId, 0, 0, width, height, AllPlanes, ZPixmap );
// [Преобразование image в QImage]
XDestroyImage( image );
Все детали того, как сделать преобразование между XImage и QImage находятся за пределами этого руководства.
Во время преобразования учтите форму окон в случаях, когда они не прямоугольные. XFixesFetchRegion() вернёт XserverRegion в виде массива XRectangles. Вы также можете использоватьс XShapeGetRectangles(), если вы хотите избежать создания XserverRegion. XGetImage(), XFixesFetchRegion() и XShapeGetRectangles() - это синхронные вызовы.
Разница между автоматическим и ручным переназначением
В этом руководстве мы использовали автоматическое переназначение, поскольку целью было продемонстрировать как можно использовать Composite для доступа к содержимому окна вне зависимости от того, закрыты они другими окнами или нет.
Приложение, которое заинтересовано не только в этом, но и также создает изображение на экране в том виде, как оно должно быть представлено пользователю, может захотеть использовать ручное переназначение. Разница между автоматическим переназначением и ручным состоит в том, что с помощью ручного переназначения содержимое окна будет переназначено во внеэкранный буфер, но на экране не произойдёт автоматического изменения при изменении содержимого окна.
Если вы пишите композитный менеджер, вы соответственно захотите ручное переназначение. Для отображения на экране вам также следует создать render картинку для корневого окна, и нарисовать на ней онка вручную, учитывая иерархию окон. Когда вы так делаете, вы полностью контролируете показ на экране, что позволяет вам отрисовывать дополнительные декоративные элементы, такие как, падающие тени или отблески. Но это уже тема другого руководства.
Благодарности
Thanks to Richard Moore and Chris Lee for their feedback.
Ссылки
The X Rendering Extension protocol definition
The X Composite Extension protocol definition
The X Damage Extension protocol definition
The X Fixes Extension protocol definition