如何正确处理 `XCB_RESIZE_REQUEST` 事件?

编程语言 2026-07-10

我正在尝试使用libxcb和 cairo在 X11上开发一个GUI应用程序。Cairo需要在每次窗口调整大小时收到通知,因此我为 XCB_RESIZE_REQUEST 事件设置了处理程序。然而,当我现在调整窗口大小时,窗口的客户端区域(绘制区域以及接收鼠标事件的部分)大小保持不变,尽管在我的WM上窗口似乎在围绕它调整大小。

我试着做一个“最小可复现的示例”,但样板代码太多,仍然是87行。无论如何,这里给出:

#include <stdlib.h>
#include <threads.h>
#include <time.h>

#include <xcb/xcb.h>

#include <cairo/cairo.h>
#include <cairo/cairo-xcb.h>

constexpr uint16_t start_width = 200;
constexpr uint16_t start_height = 200;

xcb_connection_t *_Atomic connection;
_Atomic bool have_been_killed = false;

int guiThread(void *const data) {
    cairo_t *const ctx = data;
    cairo_set_source_rgb(ctx, 0.5, 0, 0.5);
    while (!have_been_killed) {
        cairo_paint(ctx);
        xcb_flush(connection);
        thrd_sleep(&(struct timespec){.tv_nsec = 10'000'000}, nullptr);
    }
    return 0;
}

int main() {
    connection = xcb_connect(nullptr, nullptr);
    if (xcb_connection_has_error(connection)) {
        return 1;
    }
    xcb_screen_t *const screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;

    const xcb_window_t main_window = xcb_generate_id(connection);
    xcb_create_window(
        connection, XCB_COPY_FROM_PARENT, main_window, screen->root,
        100, 100, start_width, start_height, 0,
        XCB_WINDOW_CLASS_INPUT_OUTPUT,
        screen->root_visual,
        XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
        (uint32_t []){screen->black_pixel, XCB_EVENT_MASK_RESIZE_REDIRECT}
    );
    xcb_map_window_checked(connection, main_window);
    xcb_flush(connection);

    xcb_visualtype_t *visual;
    for (xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); screen_iter.rem; xcb_screen_next(&screen_iter)) {
        for (xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(screen_iter.data); depth_iter.rem; xcb_depth_next(&depth_iter)) {
            for (
                xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
                visual_iter.rem;
                xcb_visualtype_next(&visual_iter)
            ) {
                if (screen->root_visual == visual_iter.data->visual_id) {
                    visual = visual_iter.data;
                    goto got_visual;
                }
            }
        }
    }
    return 1;

got_visual:
    cairo_surface_t *const main_surface = cairo_xcb_surface_create(connection, main_window, visual, start_width, start_height);
    thrd_t gui_thread;
    if (thrd_create(&gui_thread, guiThread, cairo_create(main_surface)) != thrd_success) {
        return 1;
    }

    while (!have_been_killed) {
        xcb_generic_event_t *const event = xcb_wait_for_event(connection);
        if (!event) {
            have_been_killed = true;
            break;
        }
        switch (event->response_type) {
            case XCB_RESIZE_REQUEST: {
                const xcb_resize_request_event_t e = *(xcb_resize_request_event_t *)event;
                cairo_xcb_surface_set_size(main_surface, e.width, e.height);
            }
            default:
        }
        free(event);
    }
    xcb_disconnect(connection);
    return 0;
}

我显然在调整大小处理函数里忘记做些什么,但到底是什么?

解决方案

你需要将监听切换为 XCB_CONFIGURE_NOTIFY,而不是 XCB_RESIZE_REQUESTXCB_RESIZE_REQUEST 会在请求调整大小时发送,XCB_CONFIGURE_NOTIFY 会在窗口配置改变时发送;也就是说此时 widthheight 字段应当包含新窗口的内部尺寸。

为什么我现在会收到类型为150的事件?

提交的代码没有对事件码的最高位进行屏蔽。X11将最高位用来指示该事件是由 SendEvent 生成的。 为了能够可靠地检测事件,你需要对这个最高位进行屏蔽:

switch (event->response_type & ~0x80) {
 case XCB_CONFIGURE_NOTIFY: {
   const xcb_configure_notify_event_t e = *(xcb_configure_notify_event_t *)event;
   cairo_xcb_surface_set_size(main_surface, e.width, e.height);
 }
 default:
}

这里还有一个 问答,进一步讨论了这个屏蔽问题。

你还需要将对 xcb_create_window() 的调用改为,给窗口设置 StructureNotifyMask 位,以订阅 ConfigureNotify 事件:

xcb_create_window(
                  connection, XCB_COPY_FROM_PARENT, main_window, screen->root,
                  100, 100, start_width, start_height, 0,
                  XCB_WINDOW_CLASS_INPUT_OUTPUT,
                  screen->root_visual,
                  XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
                  (uint32_t []){screen->black_pixel, XCB_EVENT_MASK_STRUCTURE_NOTIFY}
                  );

魔法数字与可读性

请注意,可能可以使用 XCB_EVENT_RESPONSE_TYPE_MASK 来替代魔法数字 ~0x80。这是在 xcb_event.h 头文件中定义的一个宏,但并非总是可用。xcb_event.h 头文件通常是 xcb-utils 库的一部分。对于不想包含 xcb-utils 依赖的项目,通常使用 ~0x800x7f(这是 XCB_EVENT_RESPONSE_TYPE_MASK 的定义方式)。

或者,如果需要更好的可读性,OP也可以自行定义,例如:

#define MY_XCB_EVENT_RESPONSE_TYPE_MASK  0x7f  // or (~0x80)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章