Skip to content

Commit

Permalink
Merge pull request #137 from user-none/tray-icon
Browse files Browse the repository at this point in the history
Fix issues with try icons being completely transparent
  • Loading branch information
user-none authored Sep 25, 2024
2 parents d8aa4d3 + 1172fc6 commit 1975bbc
Showing 1 changed file with 99 additions and 57 deletions.
156 changes: 99 additions & 57 deletions src/xlibutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,17 @@ void XLibUtil::raiseWindow(windowid_t window)
XFlush(display);
}

// We only consider images with at least 10% of pixels opaque. There have
// been issues with completely transparent icons being read.
// Either we'll try another icon location or we'll fallback to the no icon
// found icon.
static bool imageMeetsMinimumOpaque(size_t num_opaque, size_t width, size_t height)
{
if (static_cast<double>(num_opaque) / static_cast<double>(width * height) > 0.1d)
return true;
return false;
}

static QRgb convertToQColor(unsigned long pixel)
{
return qRgba((pixel & 0x00FF0000) >> 16, // Red
Expand All @@ -667,96 +678,127 @@ static QImage imageFromX11IconData(unsigned long *iconData, unsigned long dataLe
if (width == 0 || height == 0 || dataLength < width * height + 2)
return QImage();

size_t num_opaque = 0;
QVector<QRgb> pixels(width * height);
unsigned long *src = iconData + 2;
for (unsigned long i = 0; i < width * height; ++i) {
pixels[i] = convertToQColor(src[i]);
if (qAlpha(pixels[i]) != 0) {
num_opaque++;
}
}

if (!imageMeetsMinimumOpaque(num_opaque, width, height))
return QImage();

QImage iconImage((uchar *)pixels.data(), width, height, QImage::Format_ARGB32);
return iconImage.copy();
}

static QImage imageFromX11Pixmap(Display *display, Pixmap pixmap, int width, int height)
static QImage imageFromX11Pixmap(Display *display, Pixmap pixmap, int x, int y, unsigned int width, unsigned int height)
{
XImage *ximage = XGetImage(display, pixmap, 0, 0, width, height, AllPlanes, ZPixmap);
XImage *ximage = XGetImage(display, pixmap, x, y, width, height, AllPlanes, ZPixmap);
if (!ximage)
return QImage();

QImage image((uchar *)ximage->data, width, height, QImage::Format_ARGB32);
QImage result = image.copy(); // Make a copy of the image data
XDestroyImage(ximage);
size_t num_opaque = 0;
for (size_t i = 0; i < width; i++) {
for (size_t j = 0; j < height; j++) {
if (qAlpha(image.pixel(i, j)) != 0) {
num_opaque++;
}
}
}

QImage result;
if (imageMeetsMinimumOpaque(num_opaque, width, height))
result = image.copy(); // Make a copy of the image data

XDestroyImage(ximage);
return result;
}

QPixmap XLibUtil::getWindowIcon(windowid_t window)
static QPixmap getWindowIconNetWMIcon(windowid_t window)
{
if (!window)
return QPixmap();

QPixmap appIcon;
Display *display = getDisplay();
static Atom netWmIcon = XInternAtom(display, "_NET_WM_ICON", false);
Atom actualType;
int actualFormat;
unsigned long nItems, bytesAfter;
unsigned char *data = nullptr;

if (XGetWindowProperty(display, window, netWmIcon, 0, LONG_MAX, false, XA_CARDINAL, &actualType, &actualFormat,
&nItems, &bytesAfter, &data) != Success || data == NULL)
return appIcon;

if (actualFormat != 32)
return appIcon;

unsigned long *iconData = reinterpret_cast<unsigned long *>(data);
unsigned long dataLength = nItems;

// Extract the largest icon available
QImage largestImage;
unsigned long maxIconSize = 0;

for (unsigned long i = 0; i < dataLength;) {
unsigned long width = iconData[i];
unsigned long height = iconData[i + 1];
unsigned long iconSize = width * height;

if (iconSize > maxIconSize) {
largestImage = imageFromX11IconData(&iconData[i], dataLength - i);
maxIconSize = iconSize;
}

i += (2 + iconSize);
}

if (!largestImage.isNull())
appIcon = QPixmap::fromImage(largestImage);

XFree(data);
return appIcon;
}

static QPixmap getWindowIconWMHints(windowid_t window)
{
QPixmap appIcon;
Display *display = getDisplay();

// First try to get the icon from WM_HINTS
XWMHints *wm_hints = XGetWMHints(display, window);
if (wm_hints != nullptr) {
if (!(wm_hints->flags & IconMaskHint)) {
wm_hints->icon_mask = 0;
}
if (wm_hints == nullptr)
return appIcon;

if ((wm_hints->flags & IconPixmapHint) && (wm_hints->icon_pixmap)) {
Window root;
int x, y;
unsigned int width, height, border_width, depth;
XGetGeometry(display, wm_hints->icon_pixmap, &root, &x, &y, &width, &height, &border_width, &depth);
if (wm_hints->icon_pixmap) {
Window root;
int x = 0, y = 0;
unsigned int width = 0, height = 0, border_width, depth;
XGetGeometry(display, wm_hints->icon_pixmap, &root, &x, &y, &width, &height, &border_width, &depth);

QImage image = imageFromX11Pixmap(display, wm_hints->icon_pixmap, width, height);
QImage image = imageFromX11Pixmap(display, wm_hints->icon_pixmap, x, y, width, height);
if (!image.isNull()) {
appIcon = QPixmap::fromImage(image);
}

XFree(wm_hints);
}

// Fallback to _NET_WM_ICON if WM_HINTS icon is not available
if (appIcon.isNull()) {
static Atom netWmIcon = XInternAtom(display, "_NET_WM_ICON", false);
Atom actualType;
int actualFormat;
unsigned long nItems, bytesAfter;
unsigned char *data = nullptr;

if (XGetWindowProperty(display, window, netWmIcon, 0, LONG_MAX, false, XA_CARDINAL, &actualType, &actualFormat,
&nItems, &bytesAfter, &data) == Success &&
data)
{
unsigned long *iconData = reinterpret_cast<unsigned long *>(data);
unsigned long dataLength = nItems;

// Extract the largest icon available
QImage largestImage;
unsigned long maxIconSize = 0;

for (unsigned long i = 0; i < dataLength;) {
unsigned long width = iconData[i];
unsigned long height = iconData[i + 1];
unsigned long iconSize = width * height;

if (iconSize > maxIconSize) {
largestImage = imageFromX11IconData(&iconData[i], dataLength - i);
maxIconSize = iconSize;
}
XFree(wm_hints);
return appIcon;
}

i += (2 + iconSize);
}
QPixmap XLibUtil::getWindowIcon(windowid_t window)
{
if (!window)
return QPixmap();

if (!largestImage.isNull()) {
appIcon = QPixmap::fromImage(largestImage);
}
// First try _NET_WM_ICON
QPixmap appIcon = getWindowIconNetWMIcon(window);

XFree(data);
}
}
// Fallback to WM_HINTS if _NET_WM_ICON wasn't set
if (appIcon.isNull())
appIcon = getWindowIconWMHints(window);

return appIcon;
}
Expand Down

0 comments on commit 1975bbc

Please sign in to comment.