如何正确处理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
相关产品推荐
相关产品推荐

