Libplasma

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

KDE's Doxygen guidelines are available online.