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
相关产品推荐
相关产品推荐

