From 41df1bdb8b478eb27fb424d201c075c76ec0ed5a Mon Sep 17 00:00:00 2001 From: Benedikt Gollatz Date: Sun, 13 Dec 2015 20:47:57 +0000 Subject: [PATCH 10/10] Mitigate failed icon grabbing in xembed-sni-proxy If grabbed icons are blank, try to salvage the copied data as well as possible while leaving setups where image grabbing works fine alone. Based on a patch by Rakyn Barker. BUG:355684 REVIEW: 126336 --- xembed-sni-proxy/sniproxy.cpp | 143 ++++++++++++++++++++++++++++++++---------- xembed-sni-proxy/sniproxy.h | 5 +- 2 files changed, 114 insertions(+), 34 deletions(-) diff --git a/xembed-sni-proxy/sniproxy.cpp b/xembed-sni-proxy/sniproxy.cpp index ca2667f..ae6eab7 100644 --- a/xembed-sni-proxy/sniproxy.cpp +++ b/xembed-sni-proxy/sniproxy.cpp @@ -33,7 +33,7 @@ #include #include -#include +#include #include #include @@ -191,48 +191,51 @@ SNIProxy::~SNIProxy() void SNIProxy::update() { const QImage image = getImageNonComposite(); + if (image.isNull()) { + qCDebug(SNIPROXY) << "No xembed icon for" << m_windowId << Title(); + return; + } int w = image.width(); int h = image.height(); + m_pixmap = QPixmap::fromImage(image); + if (w != s_embedSize || h != s_embedSize) { + qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h; + m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + emit NewIcon(); + emit NewToolTip(); +} + +void sni_cleanup_xcb_image(void *data) { + xcb_image_destroy(static_cast(data)); +} + +bool SNIProxy::isTransparentImage(const QImage& image) const +{ + int w = image.width(); + int h = image.height(); + // check for the center and sub-center pixels first and avoid full image scan - bool isTransparentImage = qAlpha(image.pixel(w >> 1, h >> 1)) + qAlpha(image.pixel(w >> 2, h >> 2)) == 0; + if (! (qAlpha(image.pixel(w >> 1, h >> 1)) + qAlpha(image.pixel(w >> 2, h >> 2)) == 0)) + return false; // skip scan altogether if sub-center pixel found to be opaque // and break out from the outer loop too on full scan - for (int x = 0; x < w && isTransparentImage; ++x) { - for (int y = 0; y < h; ++y) { - if (qAlpha(image.pixel(x, y))) { - // Found an opaque pixel. - isTransparentImage = false; - break; - } - } + for (int x = 0; x < w; ++x) { + for (int y = 0; y < h; ++y) { + if (qAlpha(image.pixel(x, y))) { + // Found an opaque pixel. + return false; + } + } } - // Update icon only if it is at least partially opaque. - // This is just a workaround for X11 bug: xembed icon may suddenly - // become transparent for a one or few frames. Reproducible at least - // with WINE applications. - if (!isTransparentImage) { - m_pixmap = QPixmap::fromImage(image); - if (w != s_embedSize || h != s_embedSize) { - qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h; - m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - emit NewIcon(); - emit NewToolTip(); - } - else { - qCDebug(SNIPROXY) << "Skip transparent xembed icon for" << m_windowId << Title(); - } + return true; } -void sni_cleanup_xcb_image(void *data) { - xcb_image_destroy(static_cast(data)); -} - -QImage SNIProxy::getImageNonComposite() +QImage SNIProxy::getImageNonComposite() const { auto c = QX11Info::connection(); auto cookie = xcb_get_geometry(c, m_windowId); @@ -240,9 +243,83 @@ QImage SNIProxy::getImageNonComposite() xcb_image_t *image = xcb_image_get(c, m_windowId, 0, 0, geom->width, geom->height, 0xFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP); - QImage qimage(image->data, image->width, image->height, image->stride, QImage::Format_ARGB32, sni_cleanup_xcb_image, image); + // Don't hook up cleanup yet, we may use a different QImage after all + QImage naiveConversion = QImage(image->data, image->width, image->height, QImage::Format_ARGB32); + + if (isTransparentImage(naiveConversion)) { + QImage elaborateConversion = QImage(convertFromNative(image)); + + // Update icon only if it is at least partially opaque. + // This is just a workaround for X11 bug: xembed icon may suddenly + // become transparent for a one or few frames. Reproducible at least + // with WINE applications. + if (isTransparentImage(elaborateConversion)) { + qCDebug(SNIPROXY) << "Skip transparent xembed icon for" << m_windowId << Title(); + return QImage(); + } else + return elaborateConversion; + } else { + // Now we are sure we can eventually delete the xcb_image_t with this version + return QImage(image->data, image->width, image->height, image->stride, QImage::Format_ARGB32, sni_cleanup_xcb_image, image); + } +} + +QImage SNIProxy::convertFromNative(xcb_image_t *xcbImage) const +{ + QImage::Format format = QImage::Format_Invalid; + + switch (xcbImage->depth) { + case 1: + format = QImage::Format_MonoLSB; + break; + case 16: + format = QImage::Format_RGB16; + break; + case 24: + format = QImage::Format_RGB32; + break; + case 30: { + // Qt doesn't have a matching image format. We need to convert manually + quint32 *pixels = reinterpret_cast(xcbImage->data); + for (uint i = 0; i < (xcbImage->size / 4); i++) { + int r = (pixels[i] >> 22) & 0xff; + int g = (pixels[i] >> 12) & 0xff; + int b = (pixels[i] >> 2) & 0xff; + + pixels[i] = qRgba(r, g, b, 0xff); + } + // fall through, Qt format is still Format_ARGB32_Premultiplied + } + case 32: + format = QImage::Format_ARGB32_Premultiplied; + break; + default: + return QImage(); // we don't know + } + + QImage image(xcbImage->data, xcbImage->width, xcbImage->height, xcbImage->stride, format, sni_cleanup_xcb_image, xcbImage); + + if (image.isNull()) { + return QImage(); + } + + if (format == QImage::Format_RGB32 && xcbImage->bpp == 32) + { + QImage m = image.createHeuristicMask(); + QBitmap mask(QPixmap::fromImage(m)); + QPixmap p = QPixmap::fromImage(image); + p.setMask(mask); + image = p.toImage(); + } + + // work around an abort in QImage::color + if (image.format() == QImage::Format_MonoLSB) { + image.setColorCount(2); + image.setColor(0, QColor(Qt::white).rgb()); + image.setColor(1, QColor(Qt::black).rgb()); + } - return qimage; + return image; } //____________properties__________ diff --git a/xembed-sni-proxy/sniproxy.h b/xembed-sni-proxy/sniproxy.h index 29aa56e..6ab5b7d 100644 --- a/xembed-sni-proxy/sniproxy.h +++ b/xembed-sni-proxy/sniproxy.h @@ -28,6 +28,7 @@ #include #include +#include #include "snidbus.h" @@ -140,7 +141,9 @@ Q_SIGNALS: private: void sendClick(uint8_t mouseButton, int x, int y); - QImage getImageNonComposite(); + QImage getImageNonComposite() const; + bool isTransparentImage(const QImage &image) const; + QImage convertFromNative(xcb_image_t *xcbImage) const; QDBusConnection m_dbus; xcb_window_t m_windowId; -- 2.5.0