1/*
2 * Copyright 2013 by Martin Gräßlin <mgraesslin@kde.org>
3
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19#include "windowthumbnail.h"
20// KF5
21#include <kwindowsystem.h>
22// Qt
23#include <QGuiApplication>
24#include <QIcon>
25#include <QOpenGLContext>
26#include <QQuickWindow>
27#include <QRunnable>
28
29// X11
30#if HAVE_XCB_COMPOSITE
31#include <QX11Info>
32#include <xcb/composite.h>
33#if HAVE_GLX
34#include <GL/glx.h>
35typedef void (*glXBindTexImageEXT_func)(Display *dpy, GLXDrawable drawable,
36 int buffer, const int *attrib_list);
37typedef void (*glXReleaseTexImageEXT_func)(Display *dpy, GLXDrawable drawable, int buffer);
38#endif
39#if HAVE_EGL
40typedef EGLImageKHR(*eglCreateImageKHR_func)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *);
41typedef EGLBoolean(*eglDestroyImageKHR_func)(EGLDisplay, EGLImageKHR);
42typedef GLvoid(*glEGLImageTargetTexture2DOES_func)(GLenum, GLeglImageOES);
43#endif // HAVE_EGL
44#endif
45
46#include <cstdlib>
47
48namespace Plasma
49{
50
51#if HAVE_XCB_COMPOSITE
52#if HAVE_GLX
53class DiscardGlxPixmapRunnable : public QRunnable {
54public:
55 DiscardGlxPixmapRunnable(
56 uint,
57 QFunctionPointer,
58 xcb_pixmap_t
59 );
60 void run() override;
61private:
62 uint m_texture;
63 QFunctionPointer m_releaseTexImage;
64 xcb_pixmap_t m_glxPixmap;
65};
66
67DiscardGlxPixmapRunnable::DiscardGlxPixmapRunnable(uint texture, QFunctionPointer deleteFunction, xcb_pixmap_t pixmap)
68 : QRunnable(),
69 m_texture(texture),
70 m_releaseTexImage(deleteFunction),
71 m_glxPixmap(pixmap)
72{}
73
74void DiscardGlxPixmapRunnable::run()
75{
76 if (m_glxPixmap != XCB_PIXMAP_NONE) {
77 Display *d = QX11Info::display();
78 ((glXReleaseTexImageEXT_func)(m_releaseTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT);
79 glXDestroyPixmap(d, m_glxPixmap);
80 glDeleteTextures(1, &m_texture);
81 }
82}
83#endif //HAVE_GLX
84
85#if HAVE_EGL
86class DiscardEglPixmapRunnable : public QRunnable {
87public:
88 DiscardEglPixmapRunnable(
89 uint,
90 QFunctionPointer,
91 EGLImageKHR
92 );
93 void run() override;
94private:
95 uint m_texture;
96 QFunctionPointer m_eglDestroyImageKHR;
97 EGLImageKHR m_image;
98};
99
100DiscardEglPixmapRunnable::DiscardEglPixmapRunnable(uint texture, QFunctionPointer deleteFunction, EGLImageKHR image)
101 : QRunnable(),
102 m_texture(texture),
103 m_eglDestroyImageKHR(deleteFunction),
104 m_image(image)
105{}
106
107void DiscardEglPixmapRunnable::run()
108{
109 if (m_image != EGL_NO_IMAGE_KHR) {
110 ((eglDestroyImageKHR_func)(m_eglDestroyImageKHR))(eglGetCurrentDisplay(), m_image);
111 glDeleteTextures(1, &m_texture);
112 }
113}
114#endif//HAVE_EGL
115#endif //HAVE_XCB_COMPOSITE
116
117WindowTextureNode::WindowTextureNode()
118 : QSGSimpleTextureNode()
119{
120}
121
122WindowTextureNode::~WindowTextureNode()
123{
124}
125
126void WindowTextureNode::reset(QSGTexture *texture)
127{
128 setTexture(texture);
129 m_texture.reset(texture);
130}
131
132WindowThumbnail::WindowThumbnail(QQuickItem *parent)
133 : QQuickItem(parent)
134 , QAbstractNativeEventFilter()
135 , m_xcb(false)
136 , m_composite(false)
137 , m_winId(0)
138 , m_paintedSize(QSizeF())
139 , m_thumbnailAvailable(false)
140 , m_redirecting(false)
141 , m_damaged(false)
142 , m_depth(0)
143#if HAVE_XCB_COMPOSITE
144 , m_openGLFunctionsResolved(false)
145 , m_damageEventBase(0)
146 , m_damage(XCB_NONE)
147 , m_pixmap(XCB_PIXMAP_NONE)
148 , m_texture(0)
149#if HAVE_GLX
150 , m_glxPixmap(XCB_PIXMAP_NONE)
151 , m_bindTexImage(nullptr)
152 , m_releaseTexImage(nullptr)
153#endif // HAVE_GLX
154#if HAVE_EGL
155 , m_eglFunctionsResolved(false)
156 , m_image(EGL_NO_IMAGE_KHR)
157 , m_eglCreateImageKHR(nullptr)
158 , m_eglDestroyImageKHR(nullptr)
159 , m_glEGLImageTargetTexture2DOES(nullptr)
160#endif // HAVE_EGL
161#endif
162{
163 setFlag(ItemHasContents);
164
165 if (QGuiApplication *gui = dynamic_cast<QGuiApplication *>(QCoreApplication::instance())) {
166 m_xcb = (gui->platformName() == QStringLiteral("xcb"));
167 if (m_xcb) {
168 gui->installNativeEventFilter(this);
169#if HAVE_XCB_COMPOSITE
170 xcb_connection_t *c = QX11Info::connection();
171 xcb_prefetch_extension_data(c, &xcb_composite_id);
172 const auto *compositeReply = xcb_get_extension_data(c, &xcb_composite_id);
173 m_composite = (compositeReply && compositeReply->present);
174
175 xcb_prefetch_extension_data(c, &xcb_damage_id);
176 const auto *reply = xcb_get_extension_data(c, &xcb_damage_id);
177 m_damageEventBase = reply->first_event;
178 if (reply->present) {
179 xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
180 }
181#endif
182 }
183 }
184}
185
186WindowThumbnail::~WindowThumbnail()
187{
188 if (m_xcb) {
189 QCoreApplication::instance()->removeNativeEventFilter(this);
190 stopRedirecting();
191 }
192}
193
194void WindowThumbnail::itemChange(ItemChange change, const ItemChangeData &data)
195{
196 switch (change) {
197 case ItemSceneChange:
198 if (m_scene) {
199 disconnect(m_scene.data(), &QWindow::visibleChanged, this, &WindowThumbnail::sceneVisibilityChanged);
200 }
201 m_scene = data.window;
202 if (m_scene) {
203 connect(m_scene.data(), &QWindow::visibleChanged, this, &WindowThumbnail::sceneVisibilityChanged);
204 // restart the redirection, it might not have been active yet
205 stopRedirecting();
206 if (startRedirecting()) {
207 update();
208 }
209 }
210 break;
211
212 case ItemEnabledHasChanged:
213 Q_FALLTHROUGH();
214 case ItemVisibleHasChanged:
215 if (data.boolValue) {
216 if (startRedirecting()) {
217 update();
218 }
219 } else {
220 stopRedirecting();
221 releaseResources();
222 }
223 break;
224
225 default:
226 break;
227 }
228}
229
230void WindowThumbnail::releaseResources()
231{
232#if HAVE_XCB_COMPOSITE
233
234#if HAVE_GLX && HAVE_EGL
235 //only one (or none) should be set, but never both
236 Q_ASSERT(m_glxPixmap == XCB_PIXMAP_NONE || m_image == EGL_NO_IMAGE_KHR);
237#endif
238#if HAVE_GLX || HAVE_EGL
239 QQuickWindow::RenderStage m_renderStage = QQuickWindow::NoStage;
240#endif
241
242 //data is deleted in the render thread (with relevant GLX calls)
243 //note runnable may be called *after* this is deleted
244 //but the pointer is held by the WindowThumbnail which is in the main thread
245#if HAVE_GLX
246 if (m_glxPixmap != XCB_PIXMAP_NONE) {
247 window()->scheduleRenderJob(new DiscardGlxPixmapRunnable(m_texture,
248 m_releaseTexImage,
249 m_glxPixmap),
250 m_renderStage);
251
252 m_glxPixmap = XCB_PIXMAP_NONE;
253 m_texture = 0;
254 }
255#endif
256#if HAVE_EGL
257 if (m_image != EGL_NO_IMAGE_KHR) {
258 window()->scheduleRenderJob(new DiscardEglPixmapRunnable(m_texture,
259 m_eglDestroyImageKHR,
260 m_image),
261 m_renderStage);
262
263 m_image = EGL_NO_IMAGE_KHR;
264 m_texture = 0;
265 }
266#endif
267#endif
268}
269
270
271
272uint32_t WindowThumbnail::winId() const
273{
274 return m_winId;
275}
276
277void WindowThumbnail::setWinId(uint32_t winId)
278{
279 if (m_winId == winId) {
280 return;
281 }
282 if (!KWindowSystem::self()->hasWId(winId)) {
283 // invalid Id, don't updated
284 return;
285 }
286 if (window() && winId == window()->winId()) {
287 // don't redirect to yourself
288 return;
289 }
290 stopRedirecting();
291 m_winId = winId;
292
293 if (isEnabled() && isVisible()) {
294 startRedirecting();
295 }
296
297 emit winIdChanged();
298}
299
300qreal WindowThumbnail::paintedWidth() const
301{
302 return m_paintedSize.width();
303}
304
305qreal WindowThumbnail::paintedHeight() const
306{
307 return m_paintedSize.height();
308}
309
310bool WindowThumbnail::thumbnailAvailable() const
311{
312 return m_thumbnailAvailable;
313}
314
315QSGNode *WindowThumbnail::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
316{
317 Q_UNUSED(updatePaintNodeData)
318 auto *node = static_cast<WindowTextureNode *>(oldNode);
319 if (!node) {
320 node = new WindowTextureNode();
321 node->setFiltering(QSGTexture::Linear);
322 }
323 if (!m_xcb || m_winId == 0 || (window() && window()->winId() == m_winId)) {
324 iconToTexture(node);
325 } else {
326 windowToTexture(node);
327 }
328 node->setRect(boundingRect());
329 const QSizeF size(node->texture()->textureSize().scaled(boundingRect().size().toSize(), Qt::KeepAspectRatio));
330 if (size != m_paintedSize) {
331 m_paintedSize = size;
332 emit paintedSizeChanged();
333 }
334 const qreal x = boundingRect().x() + (boundingRect().width() - size.width()) / 2;
335 const qreal y = boundingRect().y() + (boundingRect().height() - size.height()) / 2;
336 node->setRect(QRectF(QPointF(x, y), size));
337 return node;
338}
339
340bool WindowThumbnail::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
341{
342 Q_UNUSED(result)
343 if (!m_xcb || !m_composite || eventType != QByteArrayLiteral("xcb_generic_event_t")) {
344 // currently we are only interested in XCB events
345 return false;
346 }
347#if HAVE_XCB_COMPOSITE
348 xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
349 const uint8_t responseType = event->response_type & ~0x80;
350 if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
351 if (reinterpret_cast<xcb_damage_notify_event_t *>(event)->drawable == m_winId) {
352 m_damaged = true;
353 update();
354 }
355 } else if (responseType == XCB_CONFIGURE_NOTIFY) {
356 if (reinterpret_cast<xcb_configure_notify_event_t *>(event)->window == m_winId) {
357 releaseResources();
358 m_damaged = true;
359 update();
360 }
361 } else if (responseType == XCB_MAP_NOTIFY) {
362 if (reinterpret_cast<xcb_configure_notify_event_t *>(event)->window == m_winId) {
363 releaseResources();
364 m_damaged = true;
365 update();
366 }
367 }
368#else
369 Q_UNUSED(message)
370#endif
371 // do not filter out any events, there might be further WindowThumbnails for the same window
372 return false;
373}
374
375void WindowThumbnail::iconToTexture(WindowTextureNode *textureNode)
376{
377 QIcon icon;
378 if (KWindowSystem::self()->hasWId(m_winId)) {
379 icon = KWindowSystem::self()->icon(m_winId, boundingRect().width(), boundingRect().height());
380 } else {
381 // fallback to plasma icon
382 icon = QIcon::fromTheme(QStringLiteral("plasma"));
383 }
384 QImage image = icon.pixmap(boundingRect().size().toSize()).toImage();
385 textureNode->reset(window()->createTextureFromImage(image, QQuickWindow::TextureCanUseAtlas));
386}
387
388#if HAVE_XCB_COMPOSITE
389#if HAVE_GLX
390bool WindowThumbnail::windowToTextureGLX(WindowTextureNode *textureNode)
391{
392 if (glXGetCurrentContext()) {
393 if (!m_openGLFunctionsResolved) {
394 resolveGLXFunctions();
395 }
396 if (!m_bindTexImage || !m_releaseTexImage) {
397 return false;
398 }
399 if (m_glxPixmap == XCB_PIXMAP_NONE) {
400 xcb_connection_t *c = QX11Info::connection();
401 auto attrCookie = xcb_get_window_attributes_unchecked(c, m_winId);
402 auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap);
403 QScopedPointer<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> attr(xcb_get_window_attributes_reply(c, attrCookie, nullptr));
404 QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> geo(xcb_get_geometry_reply(c, geometryCookie, nullptr));
405
406 if (attr.isNull()) {
407 return false;
408 }
409
410 if (geo.isNull()) {
411 return false;
412 }
413
414 m_depth = geo->depth;
415 m_visualid = attr->visual;
416
417 if (!loadGLXTexture()) {
418 return false;
419 }
420
421 textureNode->reset(window()->createTextureFromId(m_texture, QSize(geo->width, geo->height), QQuickWindow::TextureCanUseAtlas));
422 }
423 textureNode->texture()->bind();
424 bindGLXTexture();
425 return true;
426 }
427 return false;
428}
429#endif // HAVE_GLX
430
431#if HAVE_EGL
432bool WindowThumbnail::xcbWindowToTextureEGL(WindowTextureNode *textureNode)
433{
434 EGLContext context = eglGetCurrentContext();
435
436 if (context != EGL_NO_CONTEXT) {
437 if (!m_eglFunctionsResolved) {
438 resolveEGLFunctions();
439 }
440 if (QByteArray((char *)glGetString(GL_RENDERER)).contains("llvmpipe")) {
441 return false;
442 }
443 if (!m_eglCreateImageKHR || !m_eglDestroyImageKHR || !m_glEGLImageTargetTexture2DOES) {
444 return false;
445 }
446 if (m_image == EGL_NO_IMAGE_KHR) {
447 xcb_connection_t *c = QX11Info::connection();
448 auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap);
449
450 const EGLint attribs[] = {
451 EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
452 EGL_NONE
453 };
454 m_image = ((eglCreateImageKHR_func)(m_eglCreateImageKHR))(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
455 EGL_NATIVE_PIXMAP_KHR,
456 (EGLClientBuffer)m_pixmap, attribs);
457
458 if (m_image == EGL_NO_IMAGE_KHR) {
459 qDebug() << "failed to create egl image";
460 return false;
461 }
462
463 glGenTextures(1, &m_texture);
464 QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> geo(xcb_get_geometry_reply(c, geometryCookie, nullptr));
465 QSize size;
466 if (!geo.isNull()) {
467 size.setWidth(geo->width);
468 size.setHeight(geo->height);
469 }
470 textureNode->reset(window()->createTextureFromId(m_texture, size, QQuickWindow::TextureCanUseAtlas));
471 }
472 textureNode->texture()->bind();
473 bindEGLTexture();
474 return true;
475 }
476 return false;
477}
478
479void WindowThumbnail::resolveEGLFunctions()
480{
481 EGLDisplay display = eglGetCurrentDisplay();
482 if (display == EGL_NO_DISPLAY) {
483 return;
484 }
485 auto *context = window()->openglContext();
486 QList<QByteArray> extensions = QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' ');
487 if (extensions.contains(QByteArrayLiteral("EGL_KHR_image")) ||
488 (extensions.contains(QByteArrayLiteral("EGL_KHR_image_base")) &&
489 extensions.contains(QByteArrayLiteral("EGL_KHR_image_pixmap")))) {
490 if (context->hasExtension(QByteArrayLiteral("GL_OES_EGL_image"))) {
491 qDebug() << "Have EGL texture from pixmap";
492 m_eglCreateImageKHR = context->getProcAddress(QByteArrayLiteral("eglCreateImageKHR"));
493 m_eglDestroyImageKHR = context->getProcAddress(QByteArrayLiteral("eglDestroyImageKHR"));
494 m_glEGLImageTargetTexture2DOES = context->getProcAddress(QByteArrayLiteral("glEGLImageTargetTexture2DOES"));
495 }
496 }
497 m_eglFunctionsResolved = true;
498}
499
500void WindowThumbnail::bindEGLTexture()
501{
502 ((glEGLImageTargetTexture2DOES_func)(m_glEGLImageTargetTexture2DOES))(GL_TEXTURE_2D, (GLeglImageOES)m_image);
503 resetDamaged();
504}
505#endif // HAVE_EGL
506
507#endif // HAVE_XCB_COMPOSITE
508
509void WindowThumbnail::windowToTexture(WindowTextureNode *textureNode)
510{
511 if (!m_damaged && textureNode->texture()) {
512 return;
513 }
514#if HAVE_XCB_COMPOSITE
515 if (!textureNode->texture()) {
516 // the texture got discarded by the scene graph, but our mapping is still valid
517 // let's discard the pixmap to have a clean state again
518 releaseResources();
519 }
520 if (m_pixmap == XCB_PIXMAP_NONE) {
521 m_pixmap = pixmapForWindow();
522 }
523 if (m_pixmap == XCB_PIXMAP_NONE) {
524 // create above failed
525 iconToTexture(textureNode);
526 setThumbnailAvailable(false);
527 return;
528 }
529 bool fallbackToIcon = true;
530#if HAVE_GLX
531 fallbackToIcon = !windowToTextureGLX(textureNode);
532#endif // HAVE_GLX
533#if HAVE_EGL
534 if (fallbackToIcon) {
535 // if glx succeeded fallbackToIcon is false, thus we shouldn't try egl
536 fallbackToIcon = !xcbWindowToTextureEGL(textureNode);
537 }
538#endif // HAVE_EGL
539 if (fallbackToIcon) {
540 // just for safety to not crash
541 iconToTexture(textureNode);
542 }
543 setThumbnailAvailable(!fallbackToIcon);
544 textureNode->markDirty(QSGNode::DirtyForceUpdate);
545#else
546 iconToTexture(textureNode);
547#endif
548}
549
550#if HAVE_XCB_COMPOSITE
551xcb_pixmap_t WindowThumbnail::pixmapForWindow()
552{
553 if (!m_composite) {
554 return XCB_PIXMAP_NONE;
555 }
556
557 xcb_connection_t *c = QX11Info::connection();
558 xcb_pixmap_t pix = xcb_generate_id(c);
559 auto cookie = xcb_composite_name_window_pixmap_checked(c, m_winId, pix);
560 QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> error(xcb_request_check(c, cookie));
561 if (error) {
562 return XCB_PIXMAP_NONE;
563 }
564 return pix;
565}
566
567#if HAVE_GLX
568void WindowThumbnail::resolveGLXFunctions()
569{
570 auto *context = window()->openglContext();
571 QList<QByteArray> extensions = QByteArray(glXQueryExtensionsString(QX11Info::display(), QX11Info::appScreen())).split(' ');
572 if (extensions.contains(QByteArrayLiteral("GLX_EXT_texture_from_pixmap"))) {
573 m_bindTexImage = context->getProcAddress(QByteArrayLiteral("glXBindTexImageEXT"));
574 m_releaseTexImage = context->getProcAddress(QByteArrayLiteral("glXReleaseTexImageEXT"));
575 } else
576 qWarning() << "couldn't resolve GLX_EXT_texture_from_pixmap functions";
577 m_openGLFunctionsResolved = true;
578}
579
580void WindowThumbnail::bindGLXTexture()
581{
582 Display *d = QX11Info::display();
583 ((glXReleaseTexImageEXT_func)(m_releaseTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT);
584 ((glXBindTexImageEXT_func)(m_bindTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT, nullptr);
585 resetDamaged();
586}
587
588struct FbConfigInfo
589{
590 GLXFBConfig fbConfig;
591 int textureFormat;
592};
593
594struct GlxGlobalData
595{
596 GlxGlobalData() {
597 xcb_connection_t * const conn = QX11Info::connection();
598
599 // Fetch the render pict formats
600 reply = xcb_render_query_pict_formats_reply(conn,
601 xcb_render_query_pict_formats_unchecked(conn), nullptr);
602
603 // Init the visual ID -> format ID hash table
604 for (auto screens = xcb_render_query_pict_formats_screens_iterator(reply); screens.rem; xcb_render_pictscreen_next(&screens)) {
605 for (auto depths = xcb_render_pictscreen_depths_iterator(screens.data); depths.rem; xcb_render_pictdepth_next(&depths)) {
606 const xcb_render_pictvisual_t *visuals = xcb_render_pictdepth_visuals(depths.data);
607 const int len = xcb_render_pictdepth_visuals_length(depths.data);
608
609 for (int i = 0; i < len; i++)
610 visualPictFormatHash.insert(visuals[i].visual, visuals[i].format);
611 }
612 }
613
614 // Init the format ID -> xcb_render_directformat_t* hash table
615 const xcb_render_pictforminfo_t *formats = xcb_render_query_pict_formats_formats(reply);
616 const int len = xcb_render_query_pict_formats_formats_length(reply);
617
618 for (int i = 0; i < len; i++) {
619 if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT)
620 formatInfoHash.insert(formats[i].id, &formats[i].direct);
621 }
622
623 // Init the visual ID -> depth hash table
624 const xcb_setup_t *setup = xcb_get_setup(conn);
625
626 for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) {
627 for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) {
628 const int len = xcb_depth_visuals_length(depth.data);
629 const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
630
631 for (int i = 0; i < len; i++)
632 visualDepthHash.insert(visuals[i].visual_id, depth.data->depth);
633 }
634 }
635 }
636
637 ~GlxGlobalData() {
638 qDeleteAll(visualFbConfigHash);
639 std::free(reply);
640 }
641
642 xcb_render_query_pict_formats_reply_t *reply;
643 QHash<xcb_visualid_t, xcb_render_pictformat_t> visualPictFormatHash;
644 QHash<xcb_visualid_t, uint32_t> visualDepthHash;
645 QHash<xcb_visualid_t, FbConfigInfo *> visualFbConfigHash;
646 QHash<xcb_render_pictformat_t, const xcb_render_directformat_t *> formatInfoHash;
647};
648
649Q_GLOBAL_STATIC(GlxGlobalData, g_glxGlobalData)
650
651static xcb_render_pictformat_t findPictFormat(xcb_visualid_t visual)
652{
653 GlxGlobalData *d = g_glxGlobalData;
654 return d->visualPictFormatHash.value(visual);
655}
656
657static const xcb_render_directformat_t *findPictFormatInfo(xcb_render_pictformat_t format)
658{
659 GlxGlobalData *d = g_glxGlobalData;
660 return d->formatInfoHash.value(format);
661}
662
663static int visualDepth(xcb_visualid_t visual)
664{
665 GlxGlobalData *d = g_glxGlobalData;
666 return d->visualDepthHash.value(visual);
667}
668
669FbConfigInfo *getConfig(xcb_visualid_t visual)
670{
671 Display *dpy = QX11Info::display();
672 const xcb_render_pictformat_t format = findPictFormat(visual);
673 const xcb_render_directformat_t *direct = findPictFormatInfo(format);
674
675 if (!direct) {
676 return nullptr;
677 }
678
679 const int red_bits = qPopulationCount(direct->red_mask);
680 const int green_bits = qPopulationCount(direct->green_mask);
681 const int blue_bits = qPopulationCount(direct->blue_mask);
682 const int alpha_bits = qPopulationCount(direct->alpha_mask);
683
684 const int depth = visualDepth(visual);
685
686 const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits);
687
688 const int attribs[] = {
689 GLX_RENDER_TYPE, GLX_RGBA_BIT,
690 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT,
691 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
692 GLX_X_RENDERABLE, True,
693 GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst
694 GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, int(GLX_DONT_CARE),
695 GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits,
696 GLX_RED_SIZE, red_bits,
697 GLX_GREEN_SIZE, green_bits,
698 GLX_BLUE_SIZE, blue_bits,
699 GLX_ALPHA_SIZE, alpha_bits,
700 GLX_STENCIL_SIZE, 0,
701 GLX_DEPTH_SIZE, 0,
702 0
703 };
704
705 if (QByteArray((char *)glGetString(GL_RENDERER)).contains("llvmpipe")) {
706 return nullptr;
707 }
708
709 int count = 0;
710 GLXFBConfig *configs = glXChooseFBConfig(dpy, QX11Info::appScreen(), attribs, &count);
711 if (count < 1) {
712 return nullptr;
713 }
714
715 struct FBConfig {
716 GLXFBConfig config;
717 int depth;
718 int stencil;
719 int format;
720 };
721
722 QList<FBConfig> candidates;
723
724 for (int i = 0; i < count; i++) {
725 int red, green, blue;
726 glXGetFBConfigAttrib(dpy, configs[i], GLX_RED_SIZE, &red);
727 glXGetFBConfigAttrib(dpy, configs[i], GLX_GREEN_SIZE, &green);
728 glXGetFBConfigAttrib(dpy, configs[i], GLX_BLUE_SIZE, &blue);
729
730 if (std::tie(red, green, blue) != rgb_sizes)
731 continue;
732
733 xcb_visualid_t visual;
734 glXGetFBConfigAttrib(dpy, configs[i], GLX_VISUAL_ID, (int *) &visual);
735
736 if (visualDepth(visual) != depth)
737 continue;
738
739 int bind_rgb, bind_rgba;
740 glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba);
741 glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb);
742
743 if (!bind_rgb && !bind_rgba)
744 continue;
745
746 int texture_targets;
747 glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets);
748
749 if ((texture_targets & GLX_TEXTURE_2D_BIT_EXT) == 0)
750 continue;
751
752 int depth, stencil;
753 glXGetFBConfigAttrib(dpy, configs[i], GLX_DEPTH_SIZE, &depth);
754 glXGetFBConfigAttrib(dpy, configs[i], GLX_STENCIL_SIZE, &stencil);
755
756 int texture_format;
757 if (alpha_bits)
758 texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT;
759 else
760 texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT;
761
762 candidates.append(FBConfig{configs[i], depth, stencil, texture_format});
763 }
764
765 if (count > 0)
766 XFree(configs);
767
768 std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) {
769 if (left.depth < right.depth)
770 return true;
771
772 if (left.stencil < right.stencil)
773 return true;
774
775 return false;
776 });
777
778 FbConfigInfo *info = nullptr;
779
780 if (!candidates.isEmpty()) {
781 const FBConfig &candidate = candidates.front();
782
783 info = new FbConfigInfo;
784 info->fbConfig = candidate.config;
785 info->textureFormat = candidate.format;
786 }
787
788
789 return info;
790}
791
792bool WindowThumbnail::loadGLXTexture()
793{
794 GLXContext glxContext = glXGetCurrentContext();
795 if (!glxContext) {
796 return false;
797 }
798
799 FbConfigInfo *info = nullptr;
800
801 auto &hashTable = g_glxGlobalData->visualFbConfigHash;
802 auto it = hashTable.constFind(m_visualid);
803
804 if (it != hashTable.constEnd()) {
805 info = *it;
806 } else {
807 info = getConfig(m_visualid);
808 hashTable.insert(m_visualid, info);
809 }
810
811 if (!info) {
812 return false;
813 }
814
815 glGenTextures(1, &m_texture);
816
817 const int attrs[] = {
818 GLX_TEXTURE_FORMAT_EXT, info->textureFormat,
819 GLX_MIPMAP_TEXTURE_EXT, false,
820 GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
821 XCB_NONE
822 };
823
824 m_glxPixmap = glXCreatePixmap(QX11Info::display(), info->fbConfig, m_pixmap, attrs);
825
826 return true;
827}
828#endif
829
830#endif
831
832void WindowThumbnail::resetDamaged()
833{
834 m_damaged = false;
835#if HAVE_XCB_COMPOSITE
836 if (m_damage == XCB_NONE) {
837 return;
838 }
839 xcb_damage_subtract(QX11Info::connection(), m_damage, XCB_NONE, XCB_NONE);
840#endif
841}
842
843void WindowThumbnail::stopRedirecting()
844{
845 if (!m_xcb || !m_composite) {
846 return;
847 }
848#if HAVE_XCB_COMPOSITE
849 xcb_connection_t *c = QX11Info::connection();
850 if (m_pixmap != XCB_PIXMAP_NONE) {
851 xcb_free_pixmap(c, m_pixmap);
852 m_pixmap = XCB_PIXMAP_NONE;
853 }
854 if (m_winId == XCB_WINDOW_NONE) {
855 return;
856 }
857 if (m_redirecting) {
858 xcb_composite_unredirect_window(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
859 }
860 m_redirecting = false;
861 if (m_damage == XCB_NONE) {
862 return;
863 }
864 xcb_damage_destroy(c, m_damage);
865 m_damage = XCB_NONE;
866#endif
867}
868
869bool WindowThumbnail::startRedirecting()
870{
871 if (!m_xcb || !m_composite || !window() || !window()->isVisible() || window()->winId() == m_winId || !isEnabled() || !isVisible()) {
872 return false;
873 }
874#if HAVE_XCB_COMPOSITE
875 if (m_winId == XCB_WINDOW_NONE) {
876 return false;
877 }
878 xcb_connection_t *c = QX11Info::connection();
879
880 // need to get the window attributes for the existing event mask
881 const auto attribsCookie = xcb_get_window_attributes_unchecked(c, m_winId);
882
883 // redirect the window
884 xcb_composite_redirect_window(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
885 m_redirecting = true;
886
887 // generate the damage handle
888 m_damage = xcb_generate_id(c);
889 xcb_damage_create(c, m_damage, m_winId, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
890
891 QScopedPointer<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> attr(xcb_get_window_attributes_reply(c, attribsCookie, nullptr));
892 uint32_t events = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
893 if (!attr.isNull()) {
894 events = events | attr->your_event_mask;
895 }
896 // the event mask will not be removed again. We cannot track whether another component also needs STRUCTURE_NOTIFY (e.g. KWindowSystem).
897 // if we would remove the event mask again, other areas will break.
898 xcb_change_window_attributes(c, m_winId, XCB_CW_EVENT_MASK, &events);
899 // force to update the texture
900 m_damaged = true;
901 return true;
902#else
903 return false;
904#endif
905}
906
907
908
909void WindowThumbnail::setThumbnailAvailable(bool thumbnailAvailable)
910{
911 if (m_thumbnailAvailable != thumbnailAvailable) {
912 m_thumbnailAvailable = thumbnailAvailable;
913 emit thumbnailAvailableChanged();
914 }
915}
916
917void WindowThumbnail::sceneVisibilityChanged(bool visible)
918{
919 if (visible) {
920 if (startRedirecting()) {
921 update();
922 }
923 } else {
924 stopRedirecting();
925 releaseResources();
926 }
927}
928
929} // namespace
930