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

基于XCB的窗口管理器如何追踪按钮释放事件并获取指针窗口ID?

解决XCB窗口管理器获取点击窗口ID的问题

问题原因分析

1. 客户端窗口设置EVENT_MASK_BUTTON_RELEASE无效

给客户端窗口单独设置该掩码没用,核心原因有两个:

  • 只有拥有输入焦点的窗口才能收到按钮事件,如果你没把焦点分配给目标窗口,它不会触发ButtonRelease。
  • X11的ButtonRelease事件依赖对应ButtonPress事件被同一窗口接收,你只设了释放事件的掩码,没设按压事件的,自然收不到完整事件流。

2. xcb_grab_button的问题

你当前的调用有两个关键错误:

  • 抓取的是根窗口,所以所有按钮事件都会绑定到根窗口,事件里的窗口ID自然是根窗口。
  • owner_events设为false,会阻止原目标窗口接收事件,导致客户端完全拿不到鼠标事件。

可行解决方案

方案一:修正抓取逻辑+查询指针窗口

这是窗口管理器常用的方式,既不会拦截客户端事件,又能拿到点击的目标窗口ID:

首先修正xcb_grab_button的调用:

xcb::grab_button(
    &self.conn,
    true, // 设为true,让原窗口也能收到事件
    util::get_screen(&self.conn).root(),
    xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE,
    xcb::GRAB_MODE_ASYNC,
    xcb::GRAB_MODE_ASYNC,
    xcb::NONE,
    xcb::NONE,
    xcb::ANY_BUTTON, // 监听所有鼠标按钮,而非仅1号
    xcb::MOD_MASK_ANY,
);

然后在收到按钮事件时,调用xcb_query_pointer获取指针所在的实际窗口:

// 假设你处理事件时拿到了ButtonPress或ButtonRelease事件实例
match event {
    xcb::Event::ButtonPress(evt) => handle_button_event(&self.conn, evt.event()),
    xcb::Event::ButtonRelease(evt) => handle_button_event(&self.conn, evt.event()),
    // 其他事件处理...
}

fn handle_button_event(conn: &xcb::Connection, window: xcb::Window) {
    let query_cookie = xcb::query_pointer(conn, window);
    if let Ok(reply) = query_cookie.get_reply() {
        let target_wid = reply.child();
        if target_wid != xcb::NONE {
            // 这里拿到的就是点击的窗口ID
            println!("点击的窗口: {}", target_wid);
        } else {
            // 点击的是根窗口空白区域
            println!("点击了根窗口");
        }
    }
}

方案二:管理客户端窗口焦点与事件掩码

如果你希望直接从客户端窗口接收按钮事件,需要做两步:

  1. 客户端窗口创建/映射时,同时设置BUTTON_PRESS和BUTTON_RELEASE掩码:
let cookie = xcb::change_window_attributes_checked(
    conn,
    self.wid,
    &[
        (xcb::CW_EVENT_MASK, 
         xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE | xcb::EVENT_MASK_ENTER_WINDOW | xcb::EVENT_MASK_LEAVE_WINDOW)
    ],
);
cookie.request_check().unwrap_or_else(|e| panic!("设置窗口属性失败: {:?}", e));
  1. 在收到EnterNotify事件时,将输入焦点设置到当前窗口:
// 处理EnterNotify事件
if let xcb::Event::EnterWindow(evt) = event {
    xcb::set_input_focus(&self.conn, xcb::INPUT_FOCUS_POINTER_ROOT, evt.event(), xcb::CURRENT_TIME);
    self.conn.flush();
}

这样,当鼠标进入窗口并点击时,该窗口会收到对应的按钮事件,你可以直接从事件中拿到窗口ID。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:23:14