如何正确处理 `XCB_RESIZE_REQUEST` 事件?
我正在尝试使用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_REQUEST。XCB_RESIZE_REQUEST 会在请求调整大小时发送,XCB_CONFIGURE_NOTIFY 会在窗口配置改变时发送;也就是说此时 width 和 height 字段应当包含新窗口的内部尺寸。
为什么我现在会收到类型为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 依赖的项目,通常使用 ~0x80 或 0x7f(这是 XCB_EVENT_RESPONSE_TYPE_MASK 的定义方式)。
或者,如果需要更好的可读性,OP也可以自行定义,例如:
#define MY_XCB_EVENT_RESPONSE_TYPE_MASK 0x7f // or (~0x80)