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

如何正确处理XCB_RESIZE_REQUEST事件?libxcb+Cairo的X11 GUI问题

问题:libxcb + Cairo开发X11 GUI时,调整窗口大小客户端区域未更新

我正尝试使用libxcb和Cairo开发X11 GUI应用。Cairo需要在窗口每次调整大小时收到通知,因此我为XCB_RESIZE_REQUEST事件设置了处理程序。但现在调整窗口大小时,窗口的客户端区域(绘制区域和接收鼠标事件的部分)保持原尺寸不变,尽管窗口管理器中的窗口看起来在其周围调整了大小。

我制作了一个最小可复现示例,代码如下:

#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;
}

显然我在调整大小处理程序中遗漏了某些操作,请问具体是什么?


解决方案

你遗漏了实际修改X11窗口尺寸的关键步骤。当使用XCB_EVENT_MASK_RESIZE_REDIRECT事件掩码时,窗口管理器不会直接修改窗口大小,而是发送XCB_RESIZE_REQUEST事件交由应用自行处理。你只更新了Cairo绘制表面的大小,但没有通知X11服务器调整窗口本身的尺寸,导致客户端区域(实际可绘制、可接收输入的区域)保持原大小不变。

需要在XCB_RESIZE_REQUEST事件的处理代码中添加xcb_configure_window调用,同时补上switch-case的break语句:

case XCB_RESIZE_REQUEST: {
    const xcb_resize_request_event_t e = *(xcb_resize_request_event_t *)event;
    // 更新Cairo表面的尺寸,适配新窗口大小
    cairo_xcb_surface_set_size(main_surface, e.width, e.height);
    // 向X服务器发送请求,实际修改窗口的宽高
    uint32_t values[] = {e.width, e.height};
    xcb_configure_window(
        connection,
        main_window,
        XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
        values
    );
    xcb_flush(connection); // 确保配置请求立即发送到X服务器
}
break; // 终止当前case,避免执行后续分支

另外补充两个细节:

  • 原代码的switch-case中没有break,处理完XCB_RESIZE_REQUEST后会默认执行default分支,虽然当前default无逻辑,但这是不良编码习惯,建议补上。
  • 确保main_window变量在事件处理的作用域内可访问(你的代码中main_window是main函数的局部变量,事件循环在同一函数内,无需额外修改)。

修改后,窗口的客户端区域会跟随窗口管理器的调整同步更新,Cairo也能在新的尺寸范围内正常绘制。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 13:07:42