File indexing completed on 2024-11-24 04:59:27

0001 /*
0002     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "windowthumbnail.h"
0007 // KF5
0008 #include <KWindowSystem>
0009 #include <KX11Extras>
0010 // Qt
0011 #include <QGuiApplication>
0012 #include <QIcon>
0013 #include <QOpenGLContext>
0014 #include <QOpenGLFunctions>
0015 #include <QQuickWindow>
0016 #include <QRunnable>
0017 #include <QSGImageNode>
0018 
0019 // X11
0020 #if HAVE_XCB_COMPOSITE
0021 #include <private/qtx11extras_p.h>
0022 #include <xcb/composite.h>
0023 #if HAVE_GLX
0024 #include <GL/glx.h>
0025 typedef void (*glXBindTexImageEXT_func)(Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list);
0026 typedef void (*glXReleaseTexImageEXT_func)(Display *dpy, GLXDrawable drawable, int buffer);
0027 #include <fixx11h.h> // glx.h could include XLib.h
0028 #endif
0029 #if HAVE_EGL
0030 typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *);
0031 typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay, EGLImageKHR);
0032 typedef GLvoid (*glEGLImageTargetTexture2DOES_func)(GLenum, GLeglImageOES);
0033 #endif // HAVE_EGL
0034 #endif
0035 
0036 #include <cstdlib>
0037 
0038 namespace Plasma
0039 {
0040 class DiscardTextureProviderRunnable : public QRunnable
0041 {
0042 public:
0043     explicit DiscardTextureProviderRunnable(WindowTextureProvider *provider)
0044         : m_provider(provider)
0045     {
0046     }
0047 
0048     void run() override
0049     {
0050         delete m_provider;
0051     }
0052 
0053 private:
0054     WindowTextureProvider *m_provider;
0055 };
0056 
0057 #if HAVE_XCB_COMPOSITE
0058 #if HAVE_GLX
0059 class DiscardGlxPixmapRunnable : public QRunnable
0060 {
0061 public:
0062     DiscardGlxPixmapRunnable(uint, QFunctionPointer, xcb_pixmap_t);
0063     void run() override;
0064 
0065 private:
0066     uint m_texture;
0067     QFunctionPointer m_releaseTexImage;
0068     xcb_pixmap_t m_glxPixmap;
0069 };
0070 
0071 DiscardGlxPixmapRunnable::DiscardGlxPixmapRunnable(uint texture, QFunctionPointer deleteFunction, xcb_pixmap_t pixmap)
0072     : QRunnable()
0073     , m_texture(texture)
0074     , m_releaseTexImage(deleteFunction)
0075     , m_glxPixmap(pixmap)
0076 {
0077 }
0078 
0079 void DiscardGlxPixmapRunnable::run()
0080 {
0081     if (m_glxPixmap != XCB_PIXMAP_NONE) {
0082         Display *d = QX11Info::display();
0083         ((glXReleaseTexImageEXT_func)(m_releaseTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT);
0084         glXDestroyPixmap(d, m_glxPixmap);
0085         glDeleteTextures(1, &m_texture);
0086     }
0087 }
0088 #endif // HAVE_GLX
0089 
0090 #if HAVE_EGL
0091 class DiscardEglPixmapRunnable : public QRunnable
0092 {
0093 public:
0094     DiscardEglPixmapRunnable(uint, QFunctionPointer, EGLImageKHR);
0095     void run() override;
0096 
0097 private:
0098     uint m_texture;
0099     QFunctionPointer m_eglDestroyImageKHR;
0100     EGLImageKHR m_image;
0101 };
0102 
0103 DiscardEglPixmapRunnable::DiscardEglPixmapRunnable(uint texture, QFunctionPointer deleteFunction, EGLImageKHR image)
0104     : QRunnable()
0105     , m_texture(texture)
0106     , m_eglDestroyImageKHR(deleteFunction)
0107     , m_image(image)
0108 {
0109 }
0110 
0111 void DiscardEglPixmapRunnable::run()
0112 {
0113     if (m_image != EGL_NO_IMAGE_KHR) {
0114         ((eglDestroyImageKHR_func)(m_eglDestroyImageKHR))(eglGetCurrentDisplay(), m_image);
0115         glDeleteTextures(1, &m_texture);
0116     }
0117 }
0118 #endif // HAVE_EGL
0119 #endif // HAVE_XCB_COMPOSITE
0120 
0121 QSGTexture *WindowTextureProvider::texture() const
0122 {
0123     return m_texture.get();
0124 }
0125 
0126 void WindowTextureProvider::setTexture(QSGTexture *texture)
0127 {
0128     m_texture.reset(texture);
0129     Q_EMIT textureChanged();
0130 }
0131 
0132 WindowThumbnail::WindowThumbnail(QQuickItem *parent)
0133     : QQuickItem(parent)
0134     , QAbstractNativeEventFilter()
0135 {
0136     setFlag(ItemHasContents);
0137 
0138     if (QGuiApplication *gui = dynamic_cast<QGuiApplication *>(QCoreApplication::instance())) {
0139         m_xcb = (gui->platformName() == QLatin1String("xcb"));
0140         if (m_xcb) {
0141             gui->installNativeEventFilter(this);
0142 #if HAVE_XCB_COMPOSITE
0143             xcb_connection_t *c = QX11Info::connection();
0144             xcb_prefetch_extension_data(c, &xcb_composite_id);
0145             const auto *compositeReply = xcb_get_extension_data(c, &xcb_composite_id);
0146             m_composite = (compositeReply && compositeReply->present);
0147 
0148             xcb_prefetch_extension_data(c, &xcb_damage_id);
0149             const auto *reply = xcb_get_extension_data(c, &xcb_damage_id);
0150             m_damageEventBase = reply->first_event;
0151             if (reply->present) {
0152                 xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
0153             }
0154 #endif
0155         }
0156     }
0157 }
0158 
0159 WindowThumbnail::~WindowThumbnail()
0160 {
0161     if (m_xcb) {
0162         QCoreApplication::instance()->removeNativeEventFilter(this);
0163         stopRedirecting();
0164     }
0165 }
0166 
0167 void WindowThumbnail::itemChange(ItemChange change, const ItemChangeData &data)
0168 {
0169     switch (change) {
0170     case ItemSceneChange:
0171         if (m_scene) {
0172             disconnect(m_scene.data(), &QWindow::visibleChanged, this, &WindowThumbnail::sceneVisibilityChanged);
0173         }
0174         m_scene = data.window;
0175         if (m_scene) {
0176             connect(m_scene.data(), &QWindow::visibleChanged, this, &WindowThumbnail::sceneVisibilityChanged);
0177             // restart the redirection, it might not have been active yet
0178             stopRedirecting();
0179             if (startRedirecting()) {
0180                 update();
0181             }
0182         }
0183         break;
0184 
0185     case ItemEnabledHasChanged:
0186         Q_FALLTHROUGH();
0187     case ItemVisibleHasChanged:
0188         if (data.boolValue) {
0189             if (startRedirecting()) {
0190                 update();
0191             }
0192         } else {
0193             stopRedirecting();
0194             releaseResources();
0195         }
0196         break;
0197 
0198     default:
0199         break;
0200     }
0201 
0202     QQuickItem::itemChange(change, data);
0203 }
0204 
0205 void WindowThumbnail::releaseResources()
0206 {
0207     QQuickWindow::RenderStage m_renderStage = QQuickWindow::NoStage;
0208     if (m_textureProvider) {
0209         window()->scheduleRenderJob(new DiscardTextureProviderRunnable(m_textureProvider), QQuickWindow::AfterSynchronizingStage);
0210         m_textureProvider = nullptr;
0211     }
0212 
0213 #if HAVE_XCB_COMPOSITE
0214 
0215 #if HAVE_GLX && HAVE_EGL
0216     // only one (or none) should be set, but never both
0217     Q_ASSERT(m_glxPixmap == XCB_PIXMAP_NONE || m_image == EGL_NO_IMAGE_KHR);
0218 #endif
0219 
0220     // data is deleted in the render thread (with relevant GLX calls)
0221     // note runnable may be called *after* this is deleted
0222     // but the pointer is held by the WindowThumbnail which is in the main thread
0223 #if HAVE_GLX
0224     if (m_glxPixmap != XCB_PIXMAP_NONE) {
0225         window()->scheduleRenderJob(new DiscardGlxPixmapRunnable(m_texture, m_releaseTexImage, m_glxPixmap), m_renderStage);
0226 
0227         m_glxPixmap = XCB_PIXMAP_NONE;
0228         m_texture = 0;
0229     }
0230 #endif
0231 #if HAVE_EGL
0232     if (m_image != EGL_NO_IMAGE_KHR) {
0233         window()->scheduleRenderJob(new DiscardEglPixmapRunnable(m_texture, m_eglDestroyImageKHR, m_image), m_renderStage);
0234         m_image = EGL_NO_IMAGE_KHR;
0235         m_texture = 0;
0236     }
0237 #endif
0238 #endif
0239 }
0240 
0241 // this method is invoked automagically from the render thread
0242 // but with the GUI thread locked
0243 //
0244 void WindowThumbnail::invalidateSceneGraph()
0245 {
0246     delete m_textureProvider;
0247     m_textureProvider = nullptr;
0248 #if HAVE_GLX
0249     if (m_glxPixmap != XCB_PIXMAP_NONE) {
0250         // runnable used just to share code with releaseResources, we're already in the render thread
0251         // so run directly
0252         auto runnable = new DiscardGlxPixmapRunnable(m_texture, m_releaseTexImage, m_glxPixmap);
0253         runnable->run();
0254         m_glxPixmap = XCB_PIXMAP_NONE;
0255         m_texture = 0;
0256     }
0257 #endif
0258 #if HAVE_EGL
0259     if (m_image != EGL_NO_IMAGE_KHR) {
0260         auto runnable = new DiscardEglPixmapRunnable(m_texture, m_eglDestroyImageKHR, m_image);
0261         runnable->run();
0262         m_image = EGL_NO_IMAGE_KHR;
0263         m_texture = 0;
0264     }
0265 #endif
0266 }
0267 
0268 uint32_t WindowThumbnail::winId() const
0269 {
0270     return m_winId;
0271 }
0272 
0273 void WindowThumbnail::setWinId(uint32_t winId)
0274 {
0275     if (m_winId == winId) {
0276         return;
0277     }
0278     if (KWindowSystem::isPlatformX11() && !KX11Extras::self()->hasWId(winId)) {
0279         // invalid Id, don't updated
0280         return;
0281     }
0282     if (window() && winId == window()->winId()) {
0283         // don't redirect to yourself
0284         return;
0285     }
0286     stopRedirecting();
0287     m_winId = winId;
0288 
0289     if (isEnabled() && isVisible()) {
0290         startRedirecting();
0291     }
0292 
0293     Q_EMIT winIdChanged();
0294 }
0295 
0296 qreal WindowThumbnail::paintedWidth() const
0297 {
0298     return m_paintedSize.width();
0299 }
0300 
0301 qreal WindowThumbnail::paintedHeight() const
0302 {
0303     return m_paintedSize.height();
0304 }
0305 
0306 bool WindowThumbnail::thumbnailAvailable() const
0307 {
0308     return m_thumbnailAvailable;
0309 }
0310 
0311 QSGNode *WindowThumbnail::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
0312 {
0313     Q_UNUSED(updatePaintNodeData)
0314 
0315     if (!m_textureProvider) {
0316         m_textureProvider = new WindowTextureProvider();
0317     }
0318 
0319     if (!m_xcb || m_winId == 0 || (window() && window()->winId() == m_winId)) {
0320         iconToTexture(m_textureProvider);
0321     } else {
0322         windowToTexture(m_textureProvider);
0323     }
0324 
0325     QSGImageNode *node = static_cast<QSGImageNode *>(oldNode);
0326     if (!node) {
0327         node = window()->createImageNode();
0328         qsgnode_set_description(node, QStringLiteral("windowthumbnail"));
0329         node->setFiltering(QSGTexture::Linear);
0330     }
0331 
0332     node->setTexture(m_textureProvider->texture());
0333     const QSizeF size(node->texture()->textureSize().scaled(boundingRect().size().toSize(), Qt::KeepAspectRatio));
0334     if (size != m_paintedSize) {
0335         m_paintedSize = size;
0336         Q_EMIT paintedSizeChanged();
0337     }
0338     const qreal x = boundingRect().x() + (boundingRect().width() - size.width()) / 2;
0339     const qreal y = boundingRect().y() + (boundingRect().height() - size.height()) / 2;
0340     node->setRect(QRectF(QPointF(x, y), size));
0341     return node;
0342 }
0343 
0344 bool WindowThumbnail::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
0345 {
0346     Q_UNUSED(result)
0347     if (!m_xcb || !m_composite || eventType != QByteArrayLiteral("xcb_generic_event_t")) {
0348         // currently we are only interested in XCB events
0349         return false;
0350     }
0351 #if HAVE_XCB_COMPOSITE
0352     xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
0353     const uint8_t responseType = event->response_type & ~0x80;
0354     if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
0355         if (reinterpret_cast<xcb_damage_notify_event_t *>(event)->drawable == m_winId) {
0356             m_damaged = true;
0357             update();
0358         }
0359     } else if (responseType == XCB_CONFIGURE_NOTIFY) {
0360         if (reinterpret_cast<xcb_configure_notify_event_t *>(event)->window == m_winId) {
0361             releaseResources();
0362             m_damaged = true;
0363             update();
0364         }
0365     } else if (responseType == XCB_MAP_NOTIFY) {
0366         if (reinterpret_cast<xcb_configure_notify_event_t *>(event)->window == m_winId) {
0367             releaseResources();
0368             m_damaged = true;
0369             update();
0370         }
0371     }
0372 #else
0373     Q_UNUSED(message)
0374 #endif
0375     // do not filter out any events, there might be further WindowThumbnails for the same window
0376     return false;
0377 }
0378 
0379 void WindowThumbnail::iconToTexture(WindowTextureProvider *textureProvider)
0380 {
0381     QIcon icon;
0382     if (KWindowSystem::isPlatformX11() && KX11Extras::self()->hasWId(m_winId)) {
0383         icon = KX11Extras::self()->icon(m_winId, boundingRect().width(), boundingRect().height());
0384     } else {
0385         // fallback to plasma icon
0386         icon = QIcon::fromTheme(QStringLiteral("plasma"));
0387     }
0388     QImage image = icon.pixmap(boundingRect().size().toSize(), window()->devicePixelRatio()).toImage();
0389     textureProvider->setTexture(window()->createTextureFromImage(image, QQuickWindow::TextureCanUseAtlas));
0390 }
0391 
0392 #if HAVE_XCB_COMPOSITE
0393 #if HAVE_GLX
0394 bool WindowThumbnail::windowToTextureGLX(WindowTextureProvider *textureProvider)
0395 {
0396     const auto openglContext = static_cast<QOpenGLContext *>(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource));
0397     if (openglContext) {
0398         if (!m_openGLFunctionsResolved) {
0399             resolveGLXFunctions();
0400         }
0401         if (!m_bindTexImage || !m_releaseTexImage) {
0402             return false;
0403         }
0404         if (m_glxPixmap == XCB_PIXMAP_NONE) {
0405             xcb_connection_t *c = QX11Info::connection();
0406             auto attrCookie = xcb_get_window_attributes_unchecked(c, m_winId);
0407             auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap);
0408             QScopedPointer<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> attr(xcb_get_window_attributes_reply(c, attrCookie, nullptr));
0409             QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> geo(xcb_get_geometry_reply(c, geometryCookie, nullptr));
0410 
0411             if (attr.isNull()) {
0412                 return false;
0413             }
0414 
0415             if (geo.isNull()) {
0416                 return false;
0417             }
0418 
0419             m_depth = geo->depth;
0420             m_visualid = attr->visual;
0421 
0422             if (!loadGLXTexture()) {
0423                 return false;
0424             }
0425 
0426             textureProvider->setTexture(
0427                 QNativeInterface::QSGOpenGLTexture::fromNative(m_texture, window(), QSize(geo->width, geo->height), QQuickWindow::TextureCanUseAtlas));
0428         }
0429         openglContext->functions()->glBindTexture(GL_TEXTURE_2D, m_texture);
0430         bindGLXTexture();
0431         return true;
0432     }
0433     return false;
0434 }
0435 #endif // HAVE_GLX
0436 
0437 #if HAVE_EGL
0438 bool WindowThumbnail::xcbWindowToTextureEGL(WindowTextureProvider *textureProvider)
0439 {
0440     EGLContext context = eglGetCurrentContext();
0441 
0442     if (context != EGL_NO_CONTEXT) {
0443         if (!m_eglFunctionsResolved) {
0444             resolveEGLFunctions();
0445         }
0446         if (QByteArray((char *)glGetString(GL_RENDERER)).contains("llvmpipe")) {
0447             return false;
0448         }
0449         if (!m_eglCreateImageKHR || !m_eglDestroyImageKHR || !m_glEGLImageTargetTexture2DOES) {
0450             return false;
0451         }
0452         if (m_image == EGL_NO_IMAGE_KHR) {
0453             xcb_connection_t *c = QX11Info::connection();
0454             auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap);
0455 
0456             const EGLint attribs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
0457             m_image = ((eglCreateImageKHR_func)(m_eglCreateImageKHR))(eglGetCurrentDisplay(),
0458                                                                       EGL_NO_CONTEXT,
0459                                                                       EGL_NATIVE_PIXMAP_KHR,
0460                                                                       (EGLClientBuffer)(uintptr_t)m_pixmap,
0461                                                                       attribs);
0462 
0463             if (m_image == EGL_NO_IMAGE_KHR) {
0464                 qDebug() << "failed to create egl image";
0465                 return false;
0466             }
0467 
0468             glGenTextures(1, &m_texture);
0469             QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> geo(xcb_get_geometry_reply(c, geometryCookie, nullptr));
0470             QSize size;
0471             if (!geo.isNull()) {
0472                 size.setWidth(geo->width);
0473                 size.setHeight(geo->height);
0474             }
0475             textureProvider->setTexture(QNativeInterface::QSGOpenGLTexture::fromNative(m_texture, window(), size, QQuickWindow::TextureCanUseAtlas));
0476         }
0477         auto *openglContext = static_cast<QOpenGLContext *>(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource));
0478         openglContext->functions()->glBindTexture(GL_TEXTURE_2D, m_texture);
0479         bindEGLTexture();
0480         return true;
0481     }
0482     return false;
0483 }
0484 
0485 void WindowThumbnail::resolveEGLFunctions()
0486 {
0487     EGLDisplay display = eglGetCurrentDisplay();
0488     if (display == EGL_NO_DISPLAY) {
0489         return;
0490     }
0491     auto *context = static_cast<QOpenGLContext *>(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource));
0492     QList<QByteArray> extensions = QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' ');
0493     if (extensions.contains(QByteArrayLiteral("EGL_KHR_image")) //
0494         || (extensions.contains(QByteArrayLiteral("EGL_KHR_image_base")) //
0495             && extensions.contains(QByteArrayLiteral("EGL_KHR_image_pixmap")))) {
0496         if (context->hasExtension(QByteArrayLiteral("GL_OES_EGL_image"))) {
0497             qDebug() << "Have EGL texture from pixmap";
0498             m_eglCreateImageKHR = context->getProcAddress(QByteArrayLiteral("eglCreateImageKHR"));
0499             m_eglDestroyImageKHR = context->getProcAddress(QByteArrayLiteral("eglDestroyImageKHR"));
0500             m_glEGLImageTargetTexture2DOES = context->getProcAddress(QByteArrayLiteral("glEGLImageTargetTexture2DOES"));
0501         }
0502     }
0503     m_eglFunctionsResolved = true;
0504 }
0505 
0506 void WindowThumbnail::bindEGLTexture()
0507 {
0508     ((glEGLImageTargetTexture2DOES_func)(m_glEGLImageTargetTexture2DOES))(GL_TEXTURE_2D, (GLeglImageOES)m_image);
0509     resetDamaged();
0510 }
0511 #endif // HAVE_EGL
0512 
0513 #endif // HAVE_XCB_COMPOSITE
0514 
0515 void WindowThumbnail::windowToTexture(WindowTextureProvider *textureProvider)
0516 {
0517     if (!m_damaged && textureProvider->texture()) {
0518         return;
0519     }
0520 #if HAVE_XCB_COMPOSITE
0521     if (m_pixmap == XCB_PIXMAP_NONE) {
0522         m_pixmap = pixmapForWindow();
0523     }
0524     if (m_pixmap == XCB_PIXMAP_NONE) {
0525         // create above failed
0526         iconToTexture(textureProvider);
0527         setThumbnailAvailable(false);
0528         return;
0529     }
0530     bool fallbackToIcon = true;
0531 #if HAVE_GLX
0532     fallbackToIcon = !windowToTextureGLX(textureProvider);
0533 #endif // HAVE_GLX
0534 #if HAVE_EGL
0535     if (fallbackToIcon) {
0536         // if glx succeeded fallbackToIcon is false, thus we shouldn't try egl
0537         fallbackToIcon = !xcbWindowToTextureEGL(textureProvider);
0538     }
0539 #endif // HAVE_EGL
0540     if (fallbackToIcon) {
0541         // just for safety to not crash
0542         iconToTexture(textureProvider);
0543     }
0544     setThumbnailAvailable(!fallbackToIcon);
0545 #else
0546     iconToTexture(textureProvider);
0547 #endif
0548 }
0549 
0550 #if HAVE_XCB_COMPOSITE
0551 xcb_pixmap_t WindowThumbnail::pixmapForWindow()
0552 {
0553     if (!m_composite) {
0554         return XCB_PIXMAP_NONE;
0555     }
0556 
0557     xcb_connection_t *c = QX11Info::connection();
0558     xcb_pixmap_t pix = xcb_generate_id(c);
0559     auto cookie = xcb_composite_name_window_pixmap_checked(c, m_winId, pix);
0560     QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> error(xcb_request_check(c, cookie));
0561     if (error) {
0562         return XCB_PIXMAP_NONE;
0563     }
0564     return pix;
0565 }
0566 
0567 #if HAVE_GLX
0568 void WindowThumbnail::resolveGLXFunctions()
0569 {
0570     auto *context = static_cast<QOpenGLContext *>(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource));
0571     QList<QByteArray> extensions = QByteArray(glXQueryExtensionsString(QX11Info::display(), QX11Info::appScreen())).split(' ');
0572     if (extensions.contains(QByteArrayLiteral("GLX_EXT_texture_from_pixmap"))) {
0573         m_bindTexImage = context->getProcAddress(QByteArrayLiteral("glXBindTexImageEXT"));
0574         m_releaseTexImage = context->getProcAddress(QByteArrayLiteral("glXReleaseTexImageEXT"));
0575     } else {
0576         qWarning() << "couldn't resolve GLX_EXT_texture_from_pixmap functions";
0577     }
0578     m_openGLFunctionsResolved = true;
0579 }
0580 
0581 void WindowThumbnail::bindGLXTexture()
0582 {
0583     Display *d = QX11Info::display();
0584     ((glXReleaseTexImageEXT_func)(m_releaseTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT);
0585     ((glXBindTexImageEXT_func)(m_bindTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT, nullptr);
0586     resetDamaged();
0587 }
0588 
0589 struct FbConfigInfo {
0590     GLXFBConfig fbConfig;
0591     int textureFormat;
0592 };
0593 
0594 struct GlxGlobalData {
0595     GlxGlobalData()
0596     {
0597         xcb_connection_t *const conn = QX11Info::connection();
0598 
0599         // Fetch the render pict formats
0600         reply = xcb_render_query_pict_formats_reply(conn, xcb_render_query_pict_formats_unchecked(conn), nullptr);
0601 
0602         // Init the visual ID -> format ID hash table
0603         for (auto screens = xcb_render_query_pict_formats_screens_iterator(reply); screens.rem; xcb_render_pictscreen_next(&screens)) {
0604             for (auto depths = xcb_render_pictscreen_depths_iterator(screens.data); depths.rem; xcb_render_pictdepth_next(&depths)) {
0605                 const xcb_render_pictvisual_t *visuals = xcb_render_pictdepth_visuals(depths.data);
0606                 const int len = xcb_render_pictdepth_visuals_length(depths.data);
0607 
0608                 for (int i = 0; i < len; i++) {
0609                     visualPictFormatHash.insert(visuals[i].visual, visuals[i].format);
0610                 }
0611             }
0612         }
0613 
0614         // Init the format ID -> xcb_render_directformat_t* hash table
0615         const xcb_render_pictforminfo_t *formats = xcb_render_query_pict_formats_formats(reply);
0616         const int len = xcb_render_query_pict_formats_formats_length(reply);
0617 
0618         for (int i = 0; i < len; i++) {
0619             if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT) {
0620                 formatInfoHash.insert(formats[i].id, &formats[i].direct);
0621             }
0622         }
0623 
0624         // Init the visual ID -> depth hash table
0625         const xcb_setup_t *setup = xcb_get_setup(conn);
0626 
0627         for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) {
0628             for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) {
0629                 const int len = xcb_depth_visuals_length(depth.data);
0630                 const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
0631 
0632                 for (int i = 0; i < len; i++) {
0633                     visualDepthHash.insert(visuals[i].visual_id, depth.data->depth);
0634                 }
0635             }
0636         }
0637     }
0638 
0639     ~GlxGlobalData()
0640     {
0641         qDeleteAll(visualFbConfigHash);
0642         std::free(reply);
0643     }
0644 
0645     xcb_render_query_pict_formats_reply_t *reply;
0646     QHash<xcb_visualid_t, xcb_render_pictformat_t> visualPictFormatHash;
0647     QHash<xcb_visualid_t, uint32_t> visualDepthHash;
0648     QHash<xcb_visualid_t, FbConfigInfo *> visualFbConfigHash;
0649     QHash<xcb_render_pictformat_t, const xcb_render_directformat_t *> formatInfoHash;
0650 };
0651 
0652 Q_GLOBAL_STATIC(GlxGlobalData, g_glxGlobalData)
0653 
0654 static xcb_render_pictformat_t findPictFormat(xcb_visualid_t visual)
0655 {
0656     GlxGlobalData *d = g_glxGlobalData;
0657     return d->visualPictFormatHash.value(visual);
0658 }
0659 
0660 static const xcb_render_directformat_t *findPictFormatInfo(xcb_render_pictformat_t format)
0661 {
0662     GlxGlobalData *d = g_glxGlobalData;
0663     return d->formatInfoHash.value(format);
0664 }
0665 
0666 static int visualDepth(xcb_visualid_t visual)
0667 {
0668     GlxGlobalData *d = g_glxGlobalData;
0669     return d->visualDepthHash.value(visual);
0670 }
0671 
0672 FbConfigInfo *getConfig(xcb_visualid_t visual)
0673 {
0674     Display *dpy = QX11Info::display();
0675     const xcb_render_pictformat_t format = findPictFormat(visual);
0676     const xcb_render_directformat_t *direct = findPictFormatInfo(format);
0677 
0678     if (!direct) {
0679         return nullptr;
0680     }
0681 
0682     const int red_bits = qPopulationCount(direct->red_mask);
0683     const int green_bits = qPopulationCount(direct->green_mask);
0684     const int blue_bits = qPopulationCount(direct->blue_mask);
0685     const int alpha_bits = qPopulationCount(direct->alpha_mask);
0686 
0687     const int depth = visualDepth(visual);
0688 
0689     const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits);
0690 
0691     const int attribs[] = {GLX_RENDER_TYPE,
0692                            GLX_RGBA_BIT,
0693                            GLX_DRAWABLE_TYPE,
0694                            GLX_WINDOW_BIT | GLX_PIXMAP_BIT,
0695                            GLX_X_VISUAL_TYPE,
0696                            GLX_TRUE_COLOR,
0697                            GLX_X_RENDERABLE,
0698                            True,
0699                            GLX_CONFIG_CAVEAT,
0700                            int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst
0701                            GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT,
0702                            int(GLX_DONT_CARE),
0703                            GLX_BUFFER_SIZE,
0704                            red_bits + green_bits + blue_bits + alpha_bits,
0705                            GLX_RED_SIZE,
0706                            red_bits,
0707                            GLX_GREEN_SIZE,
0708                            green_bits,
0709                            GLX_BLUE_SIZE,
0710                            blue_bits,
0711                            GLX_ALPHA_SIZE,
0712                            alpha_bits,
0713                            GLX_STENCIL_SIZE,
0714                            0,
0715                            GLX_DEPTH_SIZE,
0716                            0,
0717                            0};
0718 
0719     if (QByteArray((char *)glGetString(GL_RENDERER)).contains("llvmpipe")) {
0720         return nullptr;
0721     }
0722 
0723     int count = 0;
0724     GLXFBConfig *configs = glXChooseFBConfig(dpy, QX11Info::appScreen(), attribs, &count);
0725     if (count < 1) {
0726         return nullptr;
0727     }
0728 
0729     struct FBConfig {
0730         GLXFBConfig config;
0731         int depth;
0732         int stencil;
0733         int format;
0734     };
0735 
0736     QList<FBConfig> candidates;
0737 
0738     for (int i = 0; i < count; i++) {
0739         int red;
0740         int green;
0741         int blue;
0742         glXGetFBConfigAttrib(dpy, configs[i], GLX_RED_SIZE, &red);
0743         glXGetFBConfigAttrib(dpy, configs[i], GLX_GREEN_SIZE, &green);
0744         glXGetFBConfigAttrib(dpy, configs[i], GLX_BLUE_SIZE, &blue);
0745 
0746         if (std::tie(red, green, blue) != rgb_sizes) {
0747             continue;
0748         }
0749 
0750         xcb_visualid_t visual;
0751         glXGetFBConfigAttrib(dpy, configs[i], GLX_VISUAL_ID, (int *)&visual);
0752 
0753         if (visualDepth(visual) != depth) {
0754             continue;
0755         }
0756 
0757         int bind_rgb;
0758         int bind_rgba;
0759         glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba);
0760         glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb);
0761 
0762         if (!bind_rgb && !bind_rgba) {
0763             continue;
0764         }
0765 
0766         int texture_targets;
0767         glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets);
0768 
0769         if ((texture_targets & GLX_TEXTURE_2D_BIT_EXT) == 0) {
0770             continue;
0771         }
0772 
0773         int depth;
0774         int stencil;
0775         glXGetFBConfigAttrib(dpy, configs[i], GLX_DEPTH_SIZE, &depth);
0776         glXGetFBConfigAttrib(dpy, configs[i], GLX_STENCIL_SIZE, &stencil);
0777 
0778         int texture_format;
0779         if (alpha_bits) {
0780             texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT;
0781         } else {
0782             texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT;
0783         }
0784 
0785         candidates.append(FBConfig{configs[i], depth, stencil, texture_format});
0786     }
0787 
0788     XFree(configs);
0789 
0790     std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) {
0791         if (left.depth < right.depth) {
0792             return true;
0793         }
0794 
0795         if (left.stencil < right.stencil) {
0796             return true;
0797         }
0798 
0799         return false;
0800     });
0801 
0802     FbConfigInfo *info = nullptr;
0803 
0804     if (!candidates.isEmpty()) {
0805         const FBConfig &candidate = candidates.front();
0806 
0807         info = new FbConfigInfo;
0808         info->fbConfig = candidate.config;
0809         info->textureFormat = candidate.format;
0810     }
0811 
0812     return info;
0813 }
0814 
0815 bool WindowThumbnail::loadGLXTexture()
0816 {
0817     GLXContext glxContext = glXGetCurrentContext();
0818     if (!glxContext) {
0819         return false;
0820     }
0821 
0822     FbConfigInfo *info = nullptr;
0823 
0824     auto &hashTable = g_glxGlobalData->visualFbConfigHash;
0825     auto it = hashTable.constFind(m_visualid);
0826 
0827     if (it != hashTable.constEnd()) {
0828         info = *it;
0829     } else {
0830         info = getConfig(m_visualid);
0831         hashTable.insert(m_visualid, info);
0832     }
0833 
0834     if (!info) {
0835         return false;
0836     }
0837 
0838     glGenTextures(1, &m_texture);
0839 
0840     /* clang-format off */
0841     const int attrs[] = {
0842         GLX_TEXTURE_FORMAT_EXT,
0843         info->textureFormat,
0844         GLX_MIPMAP_TEXTURE_EXT,
0845         false,
0846         GLX_TEXTURE_TARGET_EXT,
0847         GLX_TEXTURE_2D_EXT,
0848         XCB_NONE};
0849     /* clang-format on */
0850 
0851     m_glxPixmap = glXCreatePixmap(QX11Info::display(), info->fbConfig, m_pixmap, attrs);
0852 
0853     return true;
0854 }
0855 #endif
0856 
0857 #endif
0858 
0859 void WindowThumbnail::resetDamaged()
0860 {
0861     m_damaged = false;
0862 #if HAVE_XCB_COMPOSITE
0863     if (m_damage == XCB_NONE) {
0864         return;
0865     }
0866     xcb_damage_subtract(QX11Info::connection(), m_damage, XCB_NONE, XCB_NONE);
0867 #endif
0868 }
0869 
0870 void WindowThumbnail::stopRedirecting()
0871 {
0872     if (!m_xcb || !m_composite) {
0873         return;
0874     }
0875 #if HAVE_XCB_COMPOSITE
0876     xcb_connection_t *c = QX11Info::connection();
0877     if (m_pixmap != XCB_PIXMAP_NONE) {
0878         xcb_free_pixmap(c, m_pixmap);
0879         m_pixmap = XCB_PIXMAP_NONE;
0880     }
0881     if (m_winId == XCB_WINDOW_NONE) {
0882         return;
0883     }
0884     if (m_redirecting) {
0885         xcb_composite_unredirect_window(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
0886     }
0887     m_redirecting = false;
0888     if (m_damage == XCB_NONE) {
0889         return;
0890     }
0891     xcb_damage_destroy(c, m_damage);
0892     m_damage = XCB_NONE;
0893 #endif
0894 }
0895 
0896 bool WindowThumbnail::startRedirecting()
0897 {
0898     if (!m_xcb || !m_composite || !window() || !window()->isVisible() || window()->winId() == m_winId || !isEnabled() || !isVisible()) {
0899         return false;
0900     }
0901 #if HAVE_XCB_COMPOSITE
0902     if (m_winId == XCB_WINDOW_NONE) {
0903         return false;
0904     }
0905     xcb_connection_t *c = QX11Info::connection();
0906 
0907     // need to get the window attributes for the existing event mask
0908     const auto attribsCookie = xcb_get_window_attributes_unchecked(c, m_winId);
0909 
0910     // redirect the window
0911     xcb_composite_redirect_window(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
0912     m_redirecting = true;
0913 
0914     // generate the damage handle
0915     m_damage = xcb_generate_id(c);
0916     xcb_damage_create(c, m_damage, m_winId, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
0917 
0918     QScopedPointer<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> attr(xcb_get_window_attributes_reply(c, attribsCookie, nullptr));
0919     uint32_t events = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
0920     if (!attr.isNull()) {
0921         events = events | attr->your_event_mask;
0922     }
0923     // the event mask will not be removed again. We cannot track whether another component also needs STRUCTURE_NOTIFY (e.g. KWindowSystem).
0924     // if we would remove the event mask again, other areas will break.
0925     xcb_change_window_attributes(c, m_winId, XCB_CW_EVENT_MASK, &events);
0926     // force to update the texture
0927     m_damaged = true;
0928     return true;
0929 #else
0930     return false;
0931 #endif
0932 }
0933 
0934 void WindowThumbnail::setThumbnailAvailable(bool thumbnailAvailable)
0935 {
0936     if (m_thumbnailAvailable != thumbnailAvailable) {
0937         m_thumbnailAvailable = thumbnailAvailable;
0938         Q_EMIT thumbnailAvailableChanged();
0939     }
0940 }
0941 
0942 void WindowThumbnail::sceneVisibilityChanged(bool visible)
0943 {
0944     if (visible) {
0945         if (startRedirecting()) {
0946             update();
0947         }
0948     } else {
0949         stopRedirecting();
0950         releaseResources();
0951     }
0952 }
0953 
0954 bool WindowThumbnail::isTextureProvider() const
0955 {
0956     return true;
0957 }
0958 
0959 QSGTextureProvider *WindowThumbnail::textureProvider() const
0960 {
0961     // When Item::layer::enabled == true, QQuickItem will be a texture
0962     // provider. In this case we should prefer to return the layer rather
0963     // than our texture
0964     if (QQuickItem::isTextureProvider()) {
0965         return QQuickItem::textureProvider();
0966     }
0967 
0968     if (!m_textureProvider) {
0969         m_textureProvider = new WindowTextureProvider();
0970     }
0971 
0972     return m_textureProvider;
0973 }
0974 
0975 } // namespace
0976 
0977 #include "moc_windowthumbnail.cpp"