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"