You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
plasma-workspace/0010-Mitigate-failed-icon-g...

225 lines
7.4 KiB

From 41df1bdb8b478eb27fb424d201c075c76ec0ed5a Mon Sep 17 00:00:00 2001
From: Benedikt Gollatz <benedikt@gollatz.net>
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 <QGuiApplication>
#include <QTimer>
-#include <QPainter>
+#include <QBitmap>
#include <KWindowSystem>
#include <netwm.h>
@@ -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<xcb_image_t*>(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<xcb_image_t*>(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<quint32 *>(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 <QPixmap>
#include <xcb/xcb.h>
+#include <xcb/xcb_image.h>
#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