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

Arch Linux下自定义Xlib窗口管理器菜单失效问题排查

问题分析

你的核心问题出在X事件循环的处理不完整,以及对X窗口管理器的核心职责理解缺失:

  1. 极简示例中,你只打开了Display但完全不处理X事件,导致X服务器的事件队列堵塞,所有依赖事件交互的组件(比如右键菜单、应用下拉菜单)无法接收必要的事件,自然会一闪即消失。
  2. 窗口管理器版本的代码中,你只处理了MapRequest和ButtonPress两类事件,漏掉了大量维持窗口系统正常运作的关键事件——尤其是ConfigureRequest,这是菜单类窗口正常显示的必要前提。
解决方案

要让菜单正常工作,你的窗口管理器必须完成以下核心工作:

  • 处理所有X事件,不能只筛选特定类型,避免事件队列堵塞
  • 响应ConfigureRequest事件,允许窗口(包括override_redirect标记的菜单)调整位置和大小
  • 修正鼠标按钮抓取的参数错误
  • 处理基础的窗口协议事件(如ClientMessage)

修正后的极简示例

即使是最小窗口管理器,也必须维持事件循环:

#include <X11/Xlib.h>

int main() {
    Display* display = XOpenDisplay(nullptr);
    if (!display) return 1;

    XEvent event;
    while (1) {
        XNextEvent(display, &event);
        // 无需额外处理,但必须接收事件以保证X服务器队列正常流转
    }

    XCloseDisplay(display);
    return 0;
}

修正后的窗口管理器代码

#include <X11/Xlib.h>
#include <X11/Xutil.h>

Display* display;
Window root;

void Frame(Window window, bool created_before_manager) {
    XWindowAttributes attr = {0};
    XGetWindowAttributes(display, window, &attr);

    if (created_before_manager && (attr.override_redirect || attr.map_state != IsViewable)) {
        return;
    }

    Window frame = XCreateSimpleWindow(display, root, attr.x, attr.y, attr.width, attr.height, 5, 0xff0000, 0xffffff);
    XReparentWindow(display, window, frame, 0, 0);
    XMapWindow(display, frame);

    // 修正:用Button1(左键常量)替代Button1Mask(掩码)
    XGrabButton(display, Button1, Mod1Mask, window, None, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
}

int main() {
    display = XOpenDisplay(nullptr);
    if (!display) return 1;
    root = DefaultRootWindow(display);
    XSelectInput(display, root, SubstructureRedirectMask | SubstructureNotifyMask | StructureNotifyMask);

    XGrabServer(display);
    Window returned_root;
    Window returned_parent;
    Window* top_level_windows;
    unsigned int num_top_level_windows;
    XQueryTree(display, root, &returned_root, &returned_parent, &top_level_windows, &num_top_level_windows);

    for(unsigned int i = 0; i < num_top_level_windows; ++i) {
        Frame(top_level_windows[i], true);
    }

    XFree(top_level_windows);
    XUngrabServer(display);

    for(;;) {
        XEvent event;
        XNextEvent(display, &event);

        switch (event.type) {
            case MapRequest:
                Frame(event.xmaprequest.window, false);
                XMapWindow(display, event.xmaprequest.window);
                break;
            case ButtonPress:
                XRaiseWindow(display, event.xbutton.window);
                break;
            // 关键:处理窗口配置请求,菜单类窗口依赖此事件完成定位
            case ConfigureRequest: {
                XConfigureRequestEvent* cre = &event.xconfigurerequest;
                XWindowChanges changes = {0};
                changes.x = cre->x;
                changes.y = cre->y;
                changes.width = cre->width;
                changes.height = cre->height;
                changes.border_width = cre->border_width;
                changes.sibling = cre->above;
                changes.stack_mode = cre->detail;

                XWindowAttributes attr;
                XGetWindowAttributes(display, cre->window, &attr);
                // 对于override_redirect窗口(如菜单),直接允许配置
                if (attr.override_redirect) {
                    XConfigureWindow(display, cre->window, cre->value_mask, &changes);
                } else {
                    // 普通窗口调整对应的框架窗口
                    Window frame;
                    XQueryTree(display, cre->window, nullptr, &frame, nullptr, nullptr);
                    XConfigureWindow(display, frame, cre->value_mask, &changes);
                }
                break;
            }
            // 处理WM协议事件,避免应用无法正常关闭
            case ClientMessage: {
                XClientMessageEvent* cme = &event.xclient;
                Atom wm_delete = XInternAtom(display, "WM_DELETE_WINDOW", False);
                Atom wm_protocols = XInternAtom(display, "WM_PROTOCOLS", False);
                if (cme->message_type == wm_protocols && cme->data.l[0] == wm_delete) {
                    XDestroyWindow(display, cme->window);
                }
                break;
            }
            // 必须接收这些基础事件,防止队列堵塞
            case MapNotify:
            case UnmapNotify:
            case DestroyNotify:
            case Expose:
                break;
            default:
                break;
        }
    }

    XCloseDisplay(display);
    return 0;
}
关键要点说明
  1. 事件循环不能偷懒:X服务器会将所有窗口相关事件发送给窗口管理器,如果WM不接收处理,事件队列会溢出,应用(包括菜单)无法收到自己的交互事件,直接导致菜单显示后立即关闭。
  2. ConfigureRequest是菜单的核心依赖:右键菜单、下拉菜单这类override_redirect窗口,在显示前会发送ConfigureRequest请求调整位置/大小,如果WM不响应,应用会判定操作失败,直接销毁菜单窗口。
  3. 鼠标抓取参数错误:原代码中用Button1Mask作为按钮参数是错误的,应该使用Button1(对应鼠标左键的常量值),否则无法正确抓取鼠标事件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 17:55:31