Commit 41df1bdb authored by Benedikt Gollatz's avatar Benedikt Gollatz Committed by David Edmundson
Browse files

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
parent 46310dcb
......@@ -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__________
......
......@@ -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;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment