You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现带标题栏和边框的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属性,直接告诉窗口管理器你的窗口仅在指定区域接收鼠标事件,窗口管理器会自动处理框架窗口的事件穿透逻辑,兼容性更好。

实现步骤:

  1. 引入必要头文件:
#include <X11/Xatom.h>
  1. 添加设置输入区域的函数:
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);
}
  1. 在main函数中,设置完ShapeBounding后调用该函数:
XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet);
set_input_region(dpy, win, shape); // 添加此行

完成以上修改后,即可保留窗口标题栏和边框,同时让透明区域正常穿透鼠标事件。

内容的提问来源于stack exchange,提问作者SilverCube

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:45:55