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

Xlib开发:如何让XGrabButton不消耗点击并按需置顶窗口?

解决Arch Linux窗口管理器中仅在窗口非顶层时触发XRaiseWindow的问题

问题背景

在Arch Linux上开发窗口管理器时,为窗口映射前添加了按钮抓取事件,初始代码如下:

XGrabButton(display, Button1, 0, window, false, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);

点击Button1时执行XRaiseWindow(display, frame);,但遇到两个问题:

  • 无法点击窗口内容,仅触发ButtonPressed回调:已通过将pointer_mode改为GrabModeSync,并在回调中添加以下代码实现事件传递:
    XAllowEvents(display, ReplayPointer, event.time);
    XSync(display, 0);
    
  • 仅当窗口非顶层时才执行XRaiseWindow:尝试stack_mode和XQueryTree未得到预期结果,需要进一步解决。

解决方案:判断窗口层级并选择性触发XRaiseWindow

要实现仅在窗口非顶层时执行窗口提升,核心是准确判断目标窗口是否处于当前窗口栈的最顶层,以下是两种可行方案:

方案1:基于EWMH规范的_NET_WM_STACKING_ORDER属性

X11的EWMH规范提供了_NET_WM_STACKING_ORDER属性,可直接获取当前窗口栈的顺序(从底层到顶层排列),通过检查栈的最后一个元素即可判断是否为顶层窗口:

#include <X11/Xatom.h>

Bool isWindowTopLevel(Display *display, Window root, Window window) {
    Atom prop = XInternAtom(display, "_NET_WM_STACKING_ORDER", False);
    Atom type;
    int format;
    unsigned long nitems, bytes_after;
    unsigned char *data = NULL;

    if (XGetWindowProperty(display, root, prop, 0, ~0L, False, AnyPropertyType,
                          &type, &format, &nitems, &bytes_after, &data) != Success || !data) {
        return False;
    }

    Window *stack = (Window *)data;
    Bool is_top = (stack[nitems - 1] == window);
    XFree(data);
    return is_top;
}

在ButtonPress回调中调用该函数判断:

if (!isWindowTopLevel(display, DefaultRootWindow(display), frame)) {
    XRaiseWindow(display, frame);
}

方案2:兼容非EWMH环境的XQueryTree+XTranslateCoordinates方法

如果需要兼容不支持EWMH的环境,可通过XQueryTree获取根窗口的所有子窗口,再逐个检查是否有可见窗口覆盖在目标窗口上方:

Bool isWindowTopLevel(Display *display, Window root, Window window) {
    Window parent, *children;
    unsigned int nchildren;
    if (!XQueryTree(display, root, &root, &parent, &children, &nchildren)) {
        return False;
    }

    // 从后往前遍历子窗口(栈顺序通常从底到顶存储)
    for (int i = nchildren - 1; i >= 0; i--) {
        Window child = children[i];
        XWindowAttributes attr;
        // 跳过未映射的窗口
        if (!XGetWindowAttributes(display, child, &attr) || attr.map_state != IsViewable) {
            continue;
        }
        // 判断当前子窗口是否覆盖目标窗口
        int x, y;
        Window child_return;
        if (XTranslateCoordinates(display, window, child, 0, 0, &x, &y, &child_return)) {
            if (child_return == child) {
                // 存在可见窗口在目标窗口上方,说明目标窗口非顶层
                XFree(children);
                return False;
            }
        }
    }
    XFree(children);
    return True;
}

同样在回调中判断后执行XRaiseWindow即可。

注意事项

  • 使用EWMH方案时,需确保窗口管理器已正确维护_NET_WM_STACKING_ORDER属性,若从零开发WM,需自行实现该属性的更新逻辑。
  • 兼容方案的兼容性更强,但遍历子窗口的效率略低,适合轻量或特殊兼容场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 19:25:29