fix: use 32-bit visual to avoid alpha channel lost#469
Conversation
Compability with wine tray so its background won't be black. PMS: BUG-360339 Log:
|
可使用下方的测试程序验证: Details/* tray_demo.c
* 编译: gcc tray_demo.c -o tray_demo -lX11 -lXrender
* 运行: ./tray_demo
*
* 说明: 必须使用 XRender (XRenderComposite) 绘制 ARGB 内容,
* 而不是 XPutImage。在 XWayland rootless 模式下,当 ARGB
* (depth=32) 托盘窗口被 reparent 到 depth=24 的托盘宿主窗口
* 之后,使用 XPutImage 会导致 alpha 通道被丢弃(变为 0xFF),
* 从而显示出黑色背景。XRender 走的是 Picture/PictOp 路径,
* 会正确保留 alpha 通道。
*/
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrender.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SYSTEM_TRAY_REQUEST_DOCK 0
/* 找到当前屏幕的托盘宿主窗口 (_NET_SYSTEM_TRAY_S<n>) */
static Window get_systray_owner(Display *dpy)
{
char name[32];
snprintf(name, sizeof(name), "_NET_SYSTEM_TRAY_S%d", DefaultScreen(dpy));
Atom sel = XInternAtom(dpy, name, False);
return XGetSelectionOwner(dpy, sel);
}
/* 查找 32-bit TrueColor (ARGB) Visual */
static int find_argb_visual(Display *dpy, XVisualInfo *out)
{
XVisualInfo templ;
templ.screen = DefaultScreen(dpy);
templ.depth = 32;
templ.class = TrueColor;
int n = 0;
XVisualInfo *list = XGetVisualInfo(dpy,
VisualScreenMask | VisualDepthMask | VisualClassMask, &templ, &n);
if (!list || n == 0) return 0;
*out = list[0];
XFree(list);
return 1;
}
/* 使用 XRender 将预乘 ARGB 缓冲区绘制到窗口
* 关键点: 必须通过 PictOpSrc 把 ARGB 像素合成到窗口的 Picture 上,
* 而不能使用 XPutImage 直接写窗口(后者在 XWayland rootless
* 下对被 reparent 的子窗口会丢失 alpha)。 */
static void draw_icon(Display *dpy, Window win, XRenderPictFormat *fmt,
Visual *vis, int w, int h)
{
/* 在临时 Pixmap 上构建预乘 ARGB 像素 */
Pixmap pm = XCreatePixmap(dpy, win, w, h, 32);
XRenderPictureAttributes pa;
pa.subwindow_mode = IncludeInferiors;
Picture pm_pic = XRenderCreatePicture(dpy, pm, fmt, CPSubwindowMode, &pa);
/* 清空为完全透明 */
XRenderColor clear = { .red = 0, .green = 0, .blue = 0, .alpha = 0 };
XRenderFillRectangle(dpy, PictOpSrc, pm_pic, &clear, 0, 0, w, h);
unsigned int *buf = calloc(w * h, sizeof(unsigned int));
if (!buf) {
XRenderFreePicture(dpy, pm_pic);
XFreePixmap(dpy, pm);
return;
}
int cx = w / 2, cy = h / 2, r = w / 2 - 2;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int dx = x - cx, dy = y - cy;
if (dx*dx + dy*dy <= r*r) {
/*
* 预乘 Alpha (pre-multiplied alpha):
* A=0xC0 (75% 不透明), R=0xFF, G=0x40, B=0x00
* 存储格式: 0xAARRGGBB (little-endian 下即内存顺序 BB GG RR AA)
*
* 注意: X11 ARGB Visual 要求像素值为预乘 alpha,
* 即 R_stored = R * A / 255,否则颜色会偏暗。
*/
unsigned char a = 0xC0;
unsigned char rv = (unsigned char)(0xFF * a / 255);
unsigned char gv = (unsigned char)(0x40 * a / 255);
unsigned char bv = 0;
buf[y * w + x] = ((unsigned int)a << 24)
| ((unsigned int)rv << 16)
| ((unsigned int)gv << 8)
| (unsigned int)bv;
}
/* else: alpha=0, 完全透明,保持 calloc 的 0 值 */
}
}
/* 把缓冲区写入 Pixmap (这一步是写 Pixmap 不是 Window, 不会丢 alpha) */
GC gc = XCreateGC(dpy, pm, 0, NULL);
XImage *img = XCreateImage(dpy, vis, 32, ZPixmap, 0,
(char *)buf, w, h, 32, w * 4);
if (img) {
XPutImage(dpy, pm, gc, img, 0, 0, 0, 0, w, h);
img->data = NULL; /* 阻止 XDestroyImage 释放我们的 buf */
XDestroyImage(img);
}
XFreeGC(dpy, gc);
free(buf);
/* 使用 XRender 把 Pixmap 合成到 Window 上 */
Picture win_pic = XRenderCreatePicture(dpy, win, fmt, 0, NULL);
XRenderComposite(dpy, PictOpSrc, pm_pic, None, win_pic,
0, 0, 0, 0, 0, 0, w, h);
XRenderFreePicture(dpy, win_pic);
XRenderFreePicture(dpy, pm_pic);
XFreePixmap(dpy, pm);
}
int main(void)
{
Display *dpy = XOpenDisplay(NULL);
if (!dpy) { fprintf(stderr, "Cannot open display\n"); return 1; }
int screen = DefaultScreen(dpy);
Window root = RootWindow(dpy, screen);
/* 1. 找托盘宿主 */
Window tray = get_systray_owner(dpy);
if (!tray) { fprintf(stderr, "No system tray found\n"); return 1; }
printf("Systray owner: 0x%lx\n", tray);
/* 2. 获取 ARGB Visual(透明的关键) */
XVisualInfo vinfo;
if (!find_argb_visual(dpy, &vinfo)) {
fprintf(stderr, "No 32-bit ARGB visual available\n");
return 1;
}
printf("ARGB visual id: 0x%lx\n", vinfo.visualid);
/* 获取该 Visual 对应的 XRenderPictFormat, 用于后续 Picture 创建 */
XRenderPictFormat *fmt = XRenderFindVisualFormat(dpy, vinfo.visual);
if (!fmt) {
fprintf(stderr, "No XRender format for visual 0x%lx\n", vinfo.visualid);
return 1;
}
/* 3. 为 ARGB Visual 创建专用 Colormap
* (深度与 root 不同,不能用 DefaultColormap) */
Colormap cmap = XCreateColormap(dpy, root, vinfo.visual, AllocNone);
/* 4. 创建图标窗口
* - background_pixel=0: 初始全透明
* - border_pixel=0: 必须显式设置,否则 X 会用默认深度的颜色
* - CWColormap: 绑定上面创建的 colormap */
int icon_w = 22, icon_h = 22;
XSetWindowAttributes wa;
wa.colormap = cmap;
wa.border_pixel = 0;
wa.background_pixel = 0;
wa.event_mask = ExposureMask | ButtonPressMask | StructureNotifyMask;
Window icon_win = XCreateWindow(dpy, root,
0, 0, icon_w, icon_h, 0,
vinfo.depth, InputOutput, vinfo.visual,
CWColormap | CWBorderPixel |
CWBackPixel | CWEventMask,
&wa);
XStoreName(dpy, icon_win, "TrayDemo");
/* 5. 监听托盘宿主的销毁事件 */
XSelectInput(dpy, tray, StructureNotifyMask);
/* 6. 发送 SYSTEM_TRAY_REQUEST_DOCK 请求嵌入
* 协议与 Wine 的 X11DRV_SystrayDockInsert 完全一致 */
Atom opcode = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
XEvent ev;
memset(&ev, 0, sizeof(ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = tray;
ev.xclient.message_type = opcode;
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
ev.xclient.data.l[2] = icon_win;
XSendEvent(dpy, tray, False, NoEventMask, &ev);
XFlush(dpy);
printf("Docked, icon window: 0x%lx\n", icon_win);
/* 7. 事件循环 */
while (1) {
XNextEvent(dpy, &ev);
switch (ev.type) {
case Expose:
if (ev.xexpose.count == 0)
draw_icon(dpy, icon_win, fmt, vinfo.visual, icon_w, icon_h);
break;
case ButtonPress:
printf("Tray icon clicked (button %d)\n", ev.xbutton.button);
break;
case DestroyNotify:
if (ev.xdestroywindow.window == tray) {
printf("Systray gone, exiting\n");
goto done;
}
break;
}
}
done:
XDestroyWindow(dpy, icon_win);
XFreeColormap(dpy, cmap);
XCloseDisplay(dpy);
return 0;
}...或者...使用 wine。 另外,nm-applet 没问题是因为它走的 GDK 的方案实际会使用 XRenderComposite。可以用下面的示例“模拟” nm-applet 的行为。 Details/* tray_demo_v3.c
* Same as tray_demo.c but uses XRender (XRenderFillRectangles) instead
* of XPutImage to paint the icon. This is closer to how GTK/cairo draws.
*
* Build: gcc tray_demo_v3.c -o tray_demo_v3 -lX11 -lXrender
*/
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrender.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SYSTEM_TRAY_REQUEST_DOCK 0
static Window get_systray_owner(Display *dpy)
{
char name[32];
snprintf(name, sizeof(name), "_NET_SYSTEM_TRAY_S%d", DefaultScreen(dpy));
Atom sel = XInternAtom(dpy, name, False);
return XGetSelectionOwner(dpy, sel);
}
static int find_argb_visual(Display *dpy, XVisualInfo *out)
{
XVisualInfo templ;
templ.screen = DefaultScreen(dpy);
templ.depth = 32;
templ.class = TrueColor;
int n = 0;
XVisualInfo *list = XGetVisualInfo(dpy,
VisualScreenMask | VisualDepthMask | VisualClassMask, &templ, &n);
if (!list || n == 0) return 0;
*out = list[0];
XFree(list);
return 1;
}
/* Build the same disk icon but into an ARGB XRenderPicture-backed Pixmap,
* then composite onto the window. We pre-multiply alpha as required. */
static void draw_icon_render(Display *dpy, Window win, XRenderPictFormat *fmt,
Visual *vis, int w, int h)
{
Pixmap pm = XCreatePixmap(dpy, win, w, h, 32);
XRenderPictureAttributes pa;
pa.subwindow_mode = IncludeInferiors;
Picture src_pic = XRenderCreatePicture(dpy, pm, fmt, CPSubwindowMode, &pa);
/* Clear pixmap to fully transparent */
XRenderColor clear = { .red = 0, .green = 0, .blue = 0, .alpha = 0 };
XRenderFillRectangle(dpy, PictOpSrc, src_pic, &clear, 0, 0, w, h);
/* Build the disk in ARGB premultiplied form on a temp buffer, push via XImage */
unsigned int *buf = calloc(w * h, sizeof(unsigned int));
if (!buf) { XFreePixmap(dpy, pm); XRenderFreePicture(dpy, src_pic); return; }
int cx = w / 2, cy = h / 2, r = w / 2 - 2;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int dx = x - cx, dy = y - cy;
if (dx*dx + dy*dy <= r*r) {
unsigned char a = 0xC0;
unsigned char rv = (unsigned char)(0xFF * a / 255);
unsigned char gv = (unsigned char)(0x40 * a / 255);
unsigned char bv = 0;
buf[y * w + x] = ((unsigned int)a << 24)
| ((unsigned int)rv << 16)
| ((unsigned int)gv << 8)
| (unsigned int)bv;
}
}
}
GC gc = XCreateGC(dpy, pm, 0, NULL);
XImage *img = XCreateImage(dpy, vis, 32, ZPixmap, 0, (char *)buf, w, h, 32, w * 4);
if (img) {
XPutImage(dpy, pm, gc, img, 0, 0, 0, 0, w, h);
img->data = NULL;
XDestroyImage(img);
}
XFreeGC(dpy, gc);
free(buf);
/* Composite pixmap onto window via RENDER (PictOpSrc to overwrite) */
Picture dst_pic = XRenderCreatePicture(dpy, win, fmt, 0, NULL);
XRenderComposite(dpy, PictOpSrc, src_pic, None, dst_pic, 0, 0, 0, 0, 0, 0, w, h);
XRenderFreePicture(dpy, dst_pic);
XRenderFreePicture(dpy, src_pic);
XFreePixmap(dpy, pm);
}
int main(void)
{
Display *dpy = XOpenDisplay(NULL);
if (!dpy) { fprintf(stderr, "Cannot open display\n"); return 1; }
int screen = DefaultScreen(dpy);
Window root = RootWindow(dpy, screen);
Window tray = get_systray_owner(dpy);
if (!tray) { fprintf(stderr, "No system tray found\n"); return 1; }
fprintf(stderr, "Systray owner: 0x%lx\n", tray);
XVisualInfo vinfo;
if (!find_argb_visual(dpy, &vinfo)) {
fprintf(stderr, "No 32-bit ARGB visual available\n");
return 1;
}
fprintf(stderr, "ARGB visual id: 0x%lx\n", vinfo.visualid);
/* Find the XRenderPictFormat for this visual */
XRenderPictFormat *fmt = XRenderFindVisualFormat(dpy, vinfo.visual);
if (!fmt) {
fprintf(stderr, "No Render format for visual 0x%lx\n", vinfo.visualid);
return 1;
}
fprintf(stderr, "Render PictFormat: type=%d depth=%d direct=(a:%d/%d r:%d/%d g:%d/%d b:%d/%d)\n",
fmt->type, fmt->depth,
fmt->direct.alpha, fmt->direct.alphaMask,
fmt->direct.red, fmt->direct.redMask,
fmt->direct.green, fmt->direct.greenMask,
fmt->direct.blue, fmt->direct.blueMask);
Colormap cmap = XCreateColormap(dpy, root, vinfo.visual, AllocNone);
int icon_w = 22, icon_h = 22;
XSetWindowAttributes wa;
wa.colormap = cmap;
wa.border_pixel = 0;
wa.background_pixel = 0;
wa.event_mask = ExposureMask | ButtonPressMask | StructureNotifyMask;
Window icon_win = XCreateWindow(dpy, root,
0, 0, icon_w, icon_h, 0,
vinfo.depth, InputOutput, vinfo.visual,
CWColormap | CWBorderPixel |
CWBackPixel | CWEventMask,
&wa);
XStoreName(dpy, icon_win, "TrayDemoV3");
XSelectInput(dpy, tray, StructureNotifyMask);
Atom opcode = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
XEvent ev;
memset(&ev, 0, sizeof(ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = tray;
ev.xclient.message_type = opcode;
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
ev.xclient.data.l[2] = icon_win;
XSendEvent(dpy, tray, False, NoEventMask, &ev);
XFlush(dpy);
fprintf(stderr, "Docked, icon window: 0x%lx\n", icon_win);
while (1) {
XNextEvent(dpy, &ev);
switch (ev.type) {
case Expose:
if (ev.xexpose.count == 0)
draw_icon_render(dpy, icon_win, fmt, vinfo.visual, icon_w, icon_h);
break;
case ButtonPress:
printf("Tray icon clicked (button %d)\n", ev.xbutton.button);
break;
case DestroyNotify:
if (ev.xdestroywindow.window == tray) {
printf("Systray gone, exiting\n");
goto done;
}
break;
}
}
done:
XDestroyWindow(dpy, icon_win);
XFreeColormap(dpy, cmap);
XCloseDisplay(dpy);
return 0;
} |
deepin pr auto review★ 总体评分:90分■ 【总体评价】
■ 【详细分析】
■ 【改进建议代码示例】 // xembedprotocolhandler.cpp - initX11resources() 中容器窗口创建优化
// 移除动态索引,使用更安全的条件赋值方式
uint32_t values[5];
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
values[0] = 0; // back_pixel: ARGB 下 alpha=0,完全透明
values[1] = 0; // border_pixel: 必须显式设置,否则用默认深度颜色
values[2] = true; // bypass WM
values[3] = 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;
if (containerDepth == 32) {
mask |= XCB_CW_COLORMAP;
values[4] = cmap;
}
// util.cpp - getX11WindowImageNonComposite() 中移除未使用变量
QImage Util::getX11WindowImageNonComposite(const xcb_window_t& window)
{
QSize size = getX11WindowGeometry(window).size();
if (size.isEmpty()) {
return QImage();
}
xcb_image_t *image = xcb_image_get(m_x11connection, window, 0, 0, size.width(), size.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP);
if (!image) {
return QImage();
}
uint8_t visualDepth = getWindowVisualDepth(window);
// 已移除未使用的 uint8_t imageDepth = image->depth;
if (visualDepth != 32) {
QImage result = convertFromNative(image, visualDepth);
if (isTransparentImage(result)) {
return QImage();
}
return result;
}
// ... 后续逻辑保持不变
} |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: BLumia, tsic404 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Compability with wine tray so its background won't be black.
PMS: BUG-360339