Skip to content

Commit 3c4092a

Browse files
committed
fix: use 32-bit visual to avoid alpha channel lost
Compability with wine tray so its background won't be black. PMS: BUG-360339 Log:
1 parent 977a976 commit 3c4092a

3 files changed

Lines changed: 120 additions & 23 deletions

File tree

plugins/application-tray/util.cpp

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -282,37 +282,91 @@ void Util::setX11WindowInputShape(const xcb_window_t& window, const QSize& size)
282282
xcb_flush(m_x11connection);
283283
}
284284

285+
uint8_t Util::getWindowVisualDepth(const xcb_window_t& window) const
286+
{
287+
auto attrCookie = xcb_get_window_attributes(m_x11connection, window);
288+
QSharedPointer<xcb_get_window_attributes_reply_t> attr(
289+
xcb_get_window_attributes_reply(m_x11connection, attrCookie, nullptr),
290+
[](xcb_get_window_attributes_reply_t *ptr) { free(ptr); });
291+
if (!attr) {
292+
return 0;
293+
}
294+
295+
xcb_visualid_t visualId = attr->visual;
296+
auto screen = xcb_setup_roots_iterator(xcb_get_setup(m_x11connection)).data;
297+
xcb_depth_iterator_t depthIter = xcb_screen_allowed_depths_iterator(screen);
298+
while (depthIter.rem) {
299+
xcb_visualtype_iterator_t visualIter = xcb_depth_visuals_iterator(depthIter.data);
300+
while (visualIter.rem) {
301+
if (visualIter.data->visual_id == visualId) {
302+
return depthIter.data->depth;
303+
}
304+
xcb_visualtype_next(&visualIter);
305+
}
306+
xcb_depth_next(&depthIter);
307+
}
308+
return 0;
309+
}
310+
285311
QImage Util::getX11WindowImageNonComposite(const xcb_window_t& window)
286312
{
287313
QSize size = getX11WindowGeometry(window).size();
288314
if (size.isEmpty()) {
289315
return QImage();
290316
}
291-
317+
292318
xcb_image_t *image = xcb_image_get(m_x11connection, window, 0, 0, size.width(), size.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP);
293319

294320
if (!image) {
295321
return QImage();
296322
}
297323

298-
QImage naiveConversion(image->data, image->width, image->height, QImage::Format_ARGB32);
324+
uint8_t visualDepth = getWindowVisualDepth(window);
325+
uint8_t imageDepth = image->depth;
326+
327+
if (visualDepth != 32) {
328+
QImage result = convertFromNative(image, visualDepth);
329+
if (isTransparentImage(result)) {
330+
return QImage();
331+
}
332+
return result;
333+
}
334+
335+
// X11 ARGB visual 的像素值是预乘 alpha 形式,使用 Format_ARGB32_Premultiplied
336+
// 才能与数据语义匹配,避免半透明像素显示偏亮。
337+
QImage naiveConversion(image->data, image->width, image->height, QImage::Format_ARGB32_Premultiplied);
299338
if (naiveConversion.isNull()) {
300339
xcb_image_destroy(image);
301340
return QImage();
302341
}
303342

304-
if (isTransparentImage(naiveConversion)) {
305-
QImage elaborateConversion = QImage(convertFromNative(image));
306-
if (isTransparentImage(elaborateConversion)) {
307-
return QImage();
308-
} else {
309-
return elaborateConversion;
343+
QImage result = naiveConversion.copy();
344+
xcb_image_destroy(image);
345+
346+
int w = result.width(), h = result.height();
347+
int minA = 255, maxA = 0;
348+
for (int x = 0; x < w; ++x)
349+
for (int y = 0; y < h; ++y) {
350+
int a = qAlpha(result.pixel(x, y));
351+
minA = qMin(minA, a);
352+
maxA = qMax(maxA, a);
310353
}
311-
} else {
312-
QImage res = naiveConversion.copy();
313-
xcb_image_destroy(image);
314-
return res;
354+
355+
QRgb tl = result.pixel(0, 0);
356+
357+
if (maxA == 0) {
358+
return QImage();
315359
}
360+
361+
if (minA == 255) {
362+
qCWarning(TRAYUTIL) << "applying heuristic mask for all-opaque image";
363+
QImage m = result.createHeuristicMask();
364+
QPixmap p = QPixmap::fromImage(std::move(result));
365+
p.setMask(QBitmap::fromImage(std::move(m)));
366+
result = p.toImage();
367+
}
368+
369+
return result;
316370
}
317371

318372
bool Util::getX11WindowPixmapData(const xcb_window_t& window, QByteArray *data)
@@ -455,7 +509,7 @@ bool Util::isTransparentImage(const QImage &image)
455509
return true;
456510
}
457511

458-
QImage Util::convertFromNative(xcb_image_t *xcbImage)
512+
QImage Util::convertFromNative(xcb_image_t *xcbImage, uint8_t visualDepth)
459513
{
460514
QImage::Format format = QImage::Format_Invalid;
461515

@@ -498,7 +552,7 @@ QImage Util::convertFromNative(xcb_image_t *xcbImage)
498552
QImage deepCopy = image.copy();
499553
xcb_image_destroy(xcbImage);
500554

501-
if (format == QImage::Format_RGB32 && deepCopy.depth() == 32) {
555+
if ((format == QImage::Format_RGB32 || (format == QImage::Format_ARGB32_Premultiplied && visualDepth != 32)) && deepCopy.depth() == 32) {
502556
QImage m = deepCopy.createHeuristicMask();
503557
QPixmap p = QPixmap::fromImage(std::move(deepCopy));
504558
p.setMask(QBitmap::fromImage(std::move(m)));

plugins/application-tray/util.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Util : public QObject
4444
QString getX11WindowName(const xcb_window_t& window);
4545
bool isValidX11Window(const xcb_window_t& window) const;
4646
void setX11WindowInputShape(const xcb_window_t& widnow, const QSize& size);
47+
uint8_t getWindowVisualDepth(const xcb_window_t& window) const;
4748
QImage getX11WindowImageNonComposite(const xcb_window_t& window);
4849
bool getX11WindowPixmapData(const xcb_window_t& window, QByteArray *data);
4950
void setX11WindowOpacity(const xcb_window_t& window, const double& opacity);
@@ -71,7 +72,7 @@ class Util : public QObject
7172

7273
bool isTransparentImage(const QImage &image);
7374

74-
QImage convertFromNative(xcb_image_t* image);
75+
QImage convertFromNative(xcb_image_t* image, uint8_t visualDepth);
7576

7677
private:
7778
xcb_ewmh_connection_t m_ewmh;

plugins/application-tray/xembedprotocolhandler.cpp

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -250,27 +250,69 @@ void XembedProtocolHandler::initX11resources()
250250
{
251251
auto c = Util::instance()->getX11Connection();
252252
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
253+
254+
// 选出 32 位 TrueColor visual,与 _NET_SYSTEM_TRAY_VISUAL 广告保持一致。
255+
// freedesktop system-tray spec 要求:广告了 _NET_SYSTEM_TRAY_VISUAL 的 tray,
256+
// 其 socket 容器也必须用同一 visual 创建,否则被 reparent 进来的 ARGB icon
257+
// 在 XWayland rootless 等 composite 环境下会丢失 alpha 通道(变黑)。
258+
xcb_visualid_t containerVisual = screen->root_visual;
259+
uint8_t containerDepth = 24;
260+
xcb_depth_iterator_t depthIt = xcb_screen_allowed_depths_iterator(screen);
261+
while (depthIt.rem) {
262+
if (depthIt.data->depth == 32) {
263+
xcb_visualtype_iterator_t visIt = xcb_depth_visuals_iterator(depthIt.data);
264+
while (visIt.rem) {
265+
if (visIt.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) {
266+
containerVisual = visIt.data->visual_id;
267+
containerDepth = 32;
268+
break;
269+
}
270+
xcb_visualtype_next(&visIt);
271+
}
272+
break;
273+
}
274+
xcb_depth_next(&depthIt);
275+
}
276+
253277
m_containerWid = xcb_generate_id(c);
254-
uint32_t values[3];
255-
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
256-
values[0] = screen->black_pixel; // draw a solid background so the embedded icon doesn't get garbage in it
257-
values[1] = true; // bypass wM
258-
values[2] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_ENTER_WINDOW;
278+
279+
// 32 位 visual 需要独立的 Colormap(不能复用 root 的)
280+
xcb_colormap_t cmap = XCB_NONE;
281+
if (containerDepth == 32) {
282+
cmap = xcb_generate_id(c);
283+
xcb_create_colormap(c, XCB_COLORMAP_ALLOC_NONE, cmap, screen->root, containerVisual);
284+
}
285+
286+
uint32_t values[5];
287+
int i = 0;
288+
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
289+
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
290+
values[i++] = 0; // back_pixel: ARGB 下 alpha=0,完全透明
291+
values[i++] = 0; // border_pixel: 必须显式设置,否则用默认深度颜色
292+
values[i++] = true; // bypass WM
293+
values[i++] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_ENTER_WINDOW;
294+
if (containerDepth == 32) {
295+
mask |= XCB_CW_COLORMAP;
296+
values[i++] = cmap;
297+
}
298+
259299
xcb_create_window(c,
260-
XCB_COPY_FROM_PARENT,
300+
containerDepth,
261301
m_containerWid,
262302
screen->root,
263303
0,
264304
0,
265305
1, 1,
266306
0,
267307
XCB_WINDOW_CLASS_INPUT_OUTPUT,
268-
screen->root_visual,
308+
containerVisual,
269309
mask,
270310
values);
271311

312+
// 24 位时代 socket 自带不透明黑底,需要 opacity=0 把它整体隐藏;
313+
// 改 32 位后 background_pixel=0 已使其完全透明,不再需要 opacity=0,
314+
// 否则会与 icon 自身的 alpha 叠加产生副作用。
272315
UTIL->setX11WindowInputShape(m_containerWid, QSize());
273-
UTIL->setX11WindowOpacity(m_containerWid, 0);
274316

275317
xcb_map_window(c, m_containerWid);
276318

0 commit comments

Comments
 (0)