如何实现带标题栏和边框的X11透明窗口并穿透鼠标事件?
问题描述
我需要一个支持鼠标事件穿透的X11透明窗口(允许选中后方窗口的文本)。目前已实现窗口右半部分透明的代码,但只有调用移除标题栏和边框的set_no_decoration函数时,透明区域才能正常穿透鼠标事件;如果保留标题栏和边框,透明区域就无法穿透,无法选中后方文本。
现有代码如下:
#include <X11/Xlib.h> #include <X11/Xutil.h> #include <stdio.h> #include <X11/extensions/shape.h> #include <X11/extensions/Xfixes.h> #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 Bool MakeAlwaysOnTop(Display* display, Window mywin) { int screen = DefaultScreen(display); Window root = RootWindow(display, screen); Atom wmStateAbove = XInternAtom( display, "_NET_WM_STATE_ABOVE", 1 ); if( wmStateAbove != None ) { printf( "_NET_WM_STATE_ABOVE has atom of %ld\n", (long)wmStateAbove ); } else { printf( "ERROR: cannot find atom for _NET_WM_STATE_ABOVE !\n" ); return False; } Atom wmNetWmState = XInternAtom( display, "_NET_WM_STATE", 1 ); if( wmNetWmState != None ) { printf( "_NET_WM_STATE has atom of %ld\n", (long)wmNetWmState ); } else { printf( "ERROR: cannot find atom for _NET_WM_STATE !\n" ); return False; } if( wmStateAbove != None ) { XClientMessageEvent xclient; memset( &xclient, 0, sizeof (xclient) ); xclient.type = ClientMessage; xclient.window = mywin; xclient.message_type = wmNetWmState; xclient.format = 32; xclient.data.l[0] = _NET_WM_STATE_ADD; xclient.data.l[1] = wmStateAbove; xclient.data.l[2] = 0; xclient.data.l[3] = 0; xclient.data.l[4] = 0; XSendEvent( display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xclient ); XFlush(display); return True; } return False; } const int width = 400; const int height = 300; int main() { Display *dpy; Window win; GC gc; XEvent event; int screen; dpy = XOpenDisplay(NULL); if (dpy == NULL) { fprintf(stderr, "Cannot open display\n"); return 1; } screen = DefaultScreen(dpy); win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), 10, 10, width, height, 1, BlackPixel(dpy, screen), WhitePixel(dpy, screen)); Pixmap shape = XCreatePixmap(dpy, win, width, height, 1); GC gc2 = XCreateGC(dpy, shape, 0, NULL); XSetFillStyle(dpy, gc2, FillSolid); XSetForeground(dpy, gc2, 0); XFillRectangle(dpy, shape, gc2, 0, 0, width, height); XSetForeground(dpy, gc2, 1); XFillRectangle(dpy, shape, gc2, 0, 0, width / 2, height); XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet); XMapWindow(dpy, win); set_no_decoration(dpy, win); MakeAlwaysOnTop(dpy, win); // make_window_transparent(dpy, win); while (1) { XNextEvent(dpy, &event); if (event.type == KeyPress) { break; } } XFreeGC(dpy, gc); XDestroyWindow(dpy, win); XCloseDisplay(dpy); return 0; } void make_window_transparent(Display* display, Window window) { XRectangle rect; XserverRegion region = XFixesCreateRegion(display, &rect, 1); XFixesSetWindowShapeRegion(display, window, ShapeInput, 0, 0, region); XFixesDestroyRegion(display, region); } struct mwm_hints{ int flags; int functions; int decorations; int input_mode; int status; }; #define MWM_HINTS_DEC (1 << 1) #define MWM_DECOR_NONE 0 void set_no_decoration(Display* dpy, Window win) { Atom wm_hints; if((wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", True)) != None) { struct mwm_hints hints = {MWM_HINTS_DEC, 0, MWM_DECOR_NONE, 0, 0}; XChangeProperty(dpy, win, wm_hints, wm_hints, 32, PropModeReplace, (unsigned char*)&hints, 4); } }
运行效果:移除窗口装饰后,右侧透明区域可正常穿透鼠标并选中后方文本;保留装饰时,该穿透效果失效。
解决方案
问题根源在于:当窗口保留标题栏和边框时,窗口管理器会为你的应用窗口创建一个框架父窗口(Frame Window),你的窗口作为子窗口嵌套其中。此时你仅给子窗口设置了鼠标输入掩码,而框架窗口仍会接收所有鼠标事件,导致透明区域的事件无法穿透到下层窗口。
以下是两种可行的解决方法:
方案1:定位框架窗口并设置输入掩码
通过XQueryTree获取窗口的父窗口(即框架窗口),然后对其应用相同的ShapeInput掩码。注意这种方法依赖窗口管理器的实现,兼容性可能有限。
修改main函数,在窗口映射后添加以下代码:
// 查找框架父窗口 Window root, parent, *children; unsigned int nchildren; if (XQueryTree(dpy, win, &root, &parent, &children, &nchildren)) { if (parent != root) { // 确认存在框架窗口 // 对框架窗口应用相同的输入掩码 XShapeCombineMask(dpy, parent, ShapeInput, 0, 0, shape, ShapeSet); } if (children) XFree(children); }
方案2:使用_NET_WM_INPUT_REGION属性(推荐)
这是符合EWMH标准的方式,通过设置_NET_WM_INPUT_REGION属性,直接告诉窗口管理器你的窗口仅在指定区域接收鼠标事件,窗口管理器会自动处理框架窗口的事件穿透逻辑,兼容性更好。
实现步骤:
- 引入必要头文件:
#include <X11/Xatom.h>
- 添加设置输入区域的函数:
void set_input_region(Display* dpy, Window win, Pixmap shape_mask) { Atom net_wm_input_region = XInternAtom(dpy, "_NET_WM_INPUT_REGION", False); // 将Shape掩码传递给窗口管理器作为输入区域 XChangeProperty(dpy, win, net_wm_input_region, XA_PIXMAP, 32, PropModeReplace, (unsigned char*)&shape_mask, 1); }
- 在
main函数中,设置完ShapeBounding后调用该函数:
XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet); set_input_region(dpy, win, shape); // 添加此行
完成以上修改后,即可保留窗口标题栏和边框,同时让透明区域正常穿透鼠标事件。
内容的提问来源于stack exchange,提问作者SilverCube
相关产品推荐
相关产品推荐

