☰
Linux XCB 图形栈解析:异步 cookie、事件循环与窗口编程实战
2026/10/1 1:41:25 网站建设 项目流程

1. 先搞清楚 Linux XCB 到底站在图形栈的哪一层

第一次在依赖列表里看到libxcb.so,很多人的反应是"这又是什么东西"。它不像libgtk、libQt5Widgets那样一眼能看出用途,也不像libc那样天天打交道。但只要你在 Linux 上跑过任何带图形界面的程序,它几乎百分之百已经被加载进内存了。Linux XCB 的全称是 X protocol C-language Binding,直译过来就是"X 协议的 C 语言绑定",它做的事情非常纯粹:把 X 图形协议那套二进制消息,翻译成一组可以在 C 里直接调用的函数。

一句话概括它的定位——XCB 是客户端和 X 服务端之间最薄的那层通信层。你在屏幕上看到的每一个窗口、每一次鼠标点击的坐标、每一帧画面,最终都要经过它变成字节流写进一个 Unix domain socket。它上面才站着 GTK、Qt、SDL、Cairo 这些"应用层"库,下面连接着 Xorg、Xwayland、Xvfb 这些服务端实现。所以当你排查一个虚拟机里装完 Linux 系统后图形界面起不来的问题,或者一个嵌入式设备上 Qt 程序报"xcb plugin failed"的时候,真正的线索往往就藏在这一层。

这个库值得单独拿出来讲,是因为它同时具备两个身份:既是现代 Linux 图形栈的事实底座,又是一个可以直接手写窗口程序的轻量 API。前者决定了你必须懂它才能排查问题,后者决定了它非常适合嵌入式、工具链受限和高性能场景。接下来我会把它从设计动机、核心机制、代码实操到实际踩坑完整拆一遍,无论你是刚写出第一个 X11 窗口的新手,还是已经在处理厂商工具链兼容问题的老手,应该都能拿到点能直接用的东西。

2. Xlib 的历史包袱,与 XCB 的设计取舍

2.1 Xlib 那些让人头疼的隐藏状态

要理解 XCB 为什么存在,得先理解它想干掉谁。Xlib 诞生于 1985 年,比 Linux 本身还老,那时候的内存容量和网络模型跟现在完全不是一回事。它的接口长这样:XOpenDisplay打开连接后,你拿到一个Display*,然后所有操作都基于这个巨大的句柄。问题在于,这个句柄里塞了太多东西——连接状态、屏幕信息、事件队列、错误处理回调、GC 缓存、字体缓存、区域信息,全部揉在一起。

这种"上帝对象"最直接的两个后果,一个是线程安全几乎无从谈起,另一个是状态不可见。你在一个线程里调了XSetForeground,另一个线程的绘制结果可能莫名其妙变化,因为 GC 是共享的;你调用XInternAtom去查一个原子名,它会立刻阻塞等你拿到回复,没法批量发起。更麻烦的是,很多隐藏的同步点会让你在压测时看到莫名其妙的延迟,却不知道卡在哪一行。

还有一层更隐蔽的痛:协议和 API 不再一一对应。X 协议在这三十多年里不断扩展,RandR、Render、Damage、XFixes、Composite、XKB、DRI2 一个接一个。早期的 Xlib 想支持新扩展,要么长出一个奇形怪状的函数名,要么干脆让你自己去啃协议文档手写请求。做协议绑定的人最怕这个,因为你已经无法从 API 反推协议里到底发生了什么。

2.2 XCB 的两条核心设计原则

XCB 从 2001 年立项开始,目标就非常明确:做协议的直接映射,并且保持异步。这两条原则决定了它后来所有的 API 形态。

第一条是协议直映射。XCB 的接口不是人手写的,而是从一份 XML 协议描述文件(xcb-proto)自动生成的。这意味着 X 协议里每一个请求、每一个 reply、每一个事件,在 XCB 里都有一个对应的函数或结构体,名字、参数顺序、字段含义严格对齐。你如果看懂了 X 协议文档里的CreateWindow,那xcb_create_window的参数列表你基本不用查。这一条带来的好处是扩展支持极快——协议 XML 一更新,XCB 的绑定就跟着更新,新扩展不会出现"Xlib 还没实现"的窘境。

第二条是异步、cookie 分离。XCB 把"发送请求"和"取回结果"拆成了两个动作。所有需要回复的请求,返回的不是结果本身,而是一个cookie(票据)。你可以先发十个请求,拿到十个 cookie,再依次用_reply函数把结果取回来。这样一次网络往返就能处理一批操作,而不是十次往返。在本地 socket 上差距不明显,但在远程 X 转发、虚拟机、容器这种场景里,性能差别是实打实的。Xlib 那种"调用即阻塞"的模型,在这里天然输一截。

2.3 什么时候该用 XCB,什么时候别折腾

说了这么多优点,也得说清楚边界。如果你只是想写一个普通桌面应用,请不要直接上裸 XCB,那是自找苦吃。XCB 不提供控件、不提供布局、不提供输入法、不提供字体渲染,你要自己处理所有窗口管理和绘制细节。真正适合手写 XCB 的场景,通常是这几类:

  • 嵌入式 Linux 设备,内存和存储都很紧张,libxcb 的体积只有几百 KB 级别,比拖一整套工具包划算得多;
  • 写图形工具、诊断程序、截图工具、快捷键守护进程,这种程序窗口逻辑极简,直接用 XCB 反而干净利落;
  • 需要在工具包和 X server 之间做桥接,比如做输入法框架、做屏幕录制、做远程桌面代理;
  • 研究协议本身,想搞明白事件是怎么流转的,用 XCB 是最快的学习路径。

反过来,如果你的需求是"做一个能用的界面",那就老老实实用 GTK 或 Qt,它们底层其实也在用 XCB,你没必要重复造轮子。判断标准很简单:你需要的功能里,超过三成和"界面"有关,就别碰裸 XCB。

3. 把核心机制拆开:连接、请求、回复、事件

3.1 连接建立与屏幕迭代器

XCB 的第一件事是拿连接:xcb_connect(NULL, NULL)。第一个参数是 display 名,传 NULL 就走环境变量DISPLAY;第二个参数是输出参数,用来回传屏幕编号,不需要就传 NULL。拿到xcb_connection_t*之后,第一件事永远是用xcb_connection_has_error检查返回值,别急着往下走。这个检查很容易被跳过,但只要连接失败——比如DISPLAY没设置、X server 没起来、socket 不存在——后面每一个调用都会返回垃圾或者直接崩。

连接成功之后,用xcb_get_setup(conn)拿到 setup 信息,再用xcb_setup_roots_iterator迭代屏幕。这里有个容易踩的点:一个 X server 可以有多个屏幕,虽然现在绝大多数场景只有一个。迭代器的data成员就是xcb_screen_t*,里面藏着后面几乎所有的关键字段——root是根窗口 ID,root_visual是默认视觉,white_pixel和black_pixel是预置颜色,width_in_pixels和height_in_pixels是分辨率。

如果你在做一个需要适配多屏的截图工具或窗口管理器,正确的做法是用xcb_connect的第二个参数指定屏幕号,或者遍历迭代器逐一判断。别偷懒假设只有一个屏幕,我在一台接了扩展显示器的测试机上就因为这个翻过车,程序莫名其妙画到了看不见的地方。

3.2 cookie:XCB 异步模型的关键

XCB 里有一半的请求是"发出去就不管"的类型,比如xcb_map_window、xcb_poly_fill_rectangle、xcb_change_property。这些请求没有返回值,函数名也不带_reply后缀,调用完就代表请求已经排进了发送队列。

另一半是"需要结果"的类型,比如xcb_intern_atom、xcb_get_property、xcb_get_geometry、xcb_query_tree。这些函数返回一个 cookie 结构体,比如xcb_intern_atom_cookie_t。cookie 本身不包含数据,它只是一个占位符,代表"这笔请求还没有取结果"。真正的数据要靠对应的_reply函数去取。

这套机制的价值在于批量。假设你要设置五个窗口属性,需要先查五个原子名,Xlib 的做法是五次阻塞往返;XCB 的做法是循环调五次xcb_intern_atom把 cookie 存进数组,然后循环调五次xcb_intern_atom_reply统一收结果。发送和接收彻底解耦,网络层可以把五个请求打包成一个写操作。远程 X 转发场景下,这个优化带来的体验差异是肉眼可见的。

这里有一条铁律:每个_reply返回的结构体都必须手动free。它不是你传入的缓冲区,而是 XCB 内部 malloc 出来的一块内存。忘了 free 就是内存泄漏,短命程序看不出来,常驻进程跑一天就能看到 RSS 缓慢上涨。我见过一个做桌面通知守护进程的实现,因为漏了几个原子查询的 free,挂机两天涨了 200MB。

3.3 事件循环与内存所有权

事件的处理路径和请求不同。XCB 的事件获取有两个入口:xcb_wait_for_event阻塞等一个事件,xcb_poll_for_event非阻塞看一眼队列。两者都返回xcb_generic_event_t*,同样需要你 free。很多人第一次写 XCB 事件循环,看到free(ev)会觉得莫名其妙——事件不是内核推过来的吗,为什么我负责释放?因为 XCB 已经把事件解析成了堆上的结构体,所有权交给了你。

判断事件类型要用XCB_EVENT_RESPONSE_TYPE(ev)这个宏,或者手写ev->response_type & ~0x80。那个0x80位是 SendEvent 标志,来自其他客户端的合成事件会把它置上,不屏蔽掉的话你可能会匹配到错误的类型分支。

还有一个结构性问题:XCB 的事件是扁平返回的。xcb_wait_for_event给你的是xcb_generic_event_t*,你需要根据response_type自己强转成具体类型,比如xcb_expose_event_t*、xcb_button_press_event_t*、xcb_client_message_event_t*。转错了字段位置就会读到垃圾,调试起来很痛苦。我的习惯是每个 case 分支第一行就做转换,别拖到最后。

4. 从零写一个能跑的 XCB 窗口程序

4.1 编译环境和依赖确认

先确认开发头文件在不在。不同发行版的包名不一样,Debian 系通常是libxcb1-dev,Red Hat 系是libxcb-devel。写程序不需要那些xcb-util-*辅助库,裸的 libxcb 就够了,辅助库等到做 EWMH、图像上传、光标这些高级功能时再装。

编译参数别手写,用 pkg-config 最稳:

# 查看编译和链接参数 pkg-config --cflags --libs xcb # 编译示例 gcc -Wall -Wextra -o xcb_demo xcb_demo.c $(pkg-config --cflags --libs xcb)

这里提醒一句,加上-Wall -Wextra能帮你抓到一类常见错误:XCB 请求函数的所有参数都会在生成的代码里被用到一次,如果你传了未初始化的值,编译器会报警告。很多人写窗口创建时忘了初始化values数组的某个元素,结果窗口属性随机化,排查半天。

4.2 最小窗口:逐行拆解每个参数的含义

下面这段代码是一个能显示窗口、响应曝光事件、按任意键退出、点击关闭按钮也能退出的完整示例。我把它拆开讲,因为每个参数背后都有理由。

#include <xcb/xcb.h> #include <xcb/xcb_atom.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static xcb_atom_t intern_atom(xcb_connection_t *conn, const char *name) { xcb_intern_atom_cookie_t ck = xcb_intern_atom(conn, 0, (uint16_t)strlen(name), name); xcb_intern_atom_reply_t *rp = xcb_intern_atom_reply(conn, ck, NULL); if (!rp) return XCB_ATOM_NONE; xcb_atom_t atom = rp->atom; free(rp); /* 必须释放 */ return atom; } int main(void) { int screen_num = 0; xcb_connection_t *conn = xcb_connect(NULL, &screen_num); if (xcb_connection_has_error(conn)) { fprintf(stderr, "无法连接 X server,请检查 DISPLAY\n"); return 1; } /* 取第 screen_num 个屏幕 */ xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(conn)); for (int i = 0; i < screen_num && it.rem; ++i) xcb_screen_next(&it); xcb_screen_t *screen = it.data; if (!screen) { xcb_disconnect(conn); return 1; } /* 创建窗口 */ xcb_window_t win = xcb_generate_id(conn); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; uint32_t values[2]; values[0] = screen->white_pixel; values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS; xcb_create_window(conn, XCB_COPY_FROM_PARENT, /* 深度继承父窗口 */ win, screen->root, 0, 0, 480, 320, /* x y w h */ 0, /* border width */ XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values); /* 告诉窗口管理器:这个窗口想收 WM_DELETE_WINDOW */ xcb_atom_t wm_protocols = intern_atom(conn, "WM_PROTOCOLS"); xcb_atom_t wm_delete_win = intern_atom(conn, "WM_DELETE_WINDOW"); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, wm_protocols, XCB_ATOM_ATOM, 32, 1, &wm_delete_win); /* 设置 UTF-8 标题,避免中文乱码 */ xcb_atom_t net_wm_name = intern_atom(conn, "_NET_WM_NAME"); xcb_atom_t utf8_string = intern_atom(conn, "UTF8_STRING"); const char *title = "Linux XCB demo"; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, net_wm_name, utf8_string, 8, (uint32_t)strlen(title), title); xcb_map_window(conn, win); xcb_flush(conn); /* 一定要 flush,否则请求可能还躺在缓冲区里 */ /* 事件循环 */ xcb_generic_event_t *ev; int running = 1; while (running && (ev = xcb_wait_for_event(conn))) { switch (XCB_EVENT_RESPONSE_TYPE(ev)) { case XCB_EXPOSE: { xcb_expose_event_t *ex = (xcb_expose_event_t *)ev; fprintf(stderr, "曝光区域: %ux%u\n", ex->width, ex->height); break; } case XCB_KEY_PRESS: running = 0; break; case XCB_CLIENT_MESSAGE: { xcb_client_message_event_t *cm = (xcb_client_message_event_t *)ev; if (cm->data.data32[0] == wm_delete_win) running = 0; break; } default: break; } free(ev); } xcb_disconnect(conn); return 0; }

有几个参数需要专门解释。XCB_COPY_FROM_PARENT作为深度值,表示不单独指定,直接继承父窗口,这是绝大多数情况下的正确选择;只有你要做透明合成或者特殊视觉时才需要显式给 24 或 32。视觉参数用screen->root_visual,不要传 NULL。

事件掩码这块,XCB_EVENT_MASK_EXPOSURE必须加,否则窗口首次显示时收不到曝光事件,你会看到一块空白。XCB_EVENT_MASK_KEY_PRESS用来捕获按键。如果你还需要鼠标,加上XCB_EVENT_MASK_BUTTON_PRESS和XCB_EVENT_MASK_POINTER_MOTION。注意掩码是按位或的关系,别写成加法。

xcb_flush那行非常关键。XCB 内部有写缓冲,你的请求不一定立刻发出去。新手最常见的现象就是"程序卡在xcb_wait_for_event不动,窗口也不出现",九成是因为忘了 flush。养成习惯:创建窗口、映射窗口之后立刻 flush 一次。

4.3 加绘制:GC 与矩形填充

窗口出来了,接下来画点东西。XCB 的绘制需要先创建一个图形上下文(GC),它是画笔的集合,包含前景色、背景色、线宽、填充样式等。

xcb_gcontext_t gc = xcb_generate_id(conn); uint32_t gc_mask = XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH; uint32_t gc_values[2] = { screen->black_pixel, 2 }; xcb_create_gc(conn, gc, win, gc_mask, gc_values); /* 在曝光事件里填充一个矩形 */ xcb_rectangle_t rect = { 40, 40, 160, 100 }; xcb_poly_fill_rectangle(conn, win, gc, 1, &rect); /* 画一条线,坐标成对出现 */ xcb_point_t pts[2] = { {40, 200}, {400, 200} }; xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, win, gc, 2, pts); xcb_flush(conn);

xcb_rectangle_t的四个字段是x、y、width、height,注意 X 协议的坐标原点在窗口左上角,y 轴向下增长。坐标类型是 16 位有符号整数,宽高是 16 位无符号整数,所以坐标范围是 -32768 到 32767,理论上能画超大的窗口,但实际用不着。

xcb_poly_line的第二个参数是坐标模式,XCB_COORD_MODE_ORIGIN表示后面点集里的坐标是相对于窗口原点的绝对值,另一个模式XCB_COORD_MODE_PREVIOUS表示相对于上一个点。画折线时用 PREVIOUS 模式可以省不少计算。

关于颜色,screen->black_pixel和screen->white_pixel是最省事的两个值。要自定义颜色,得走xcb_alloc_color,它返回一个 cookie,_reply里拿到pixel值。注意这个 pixel 值在不同视觉下格式不同,24 位真彩色下就是 0xRRGGBB,索引色下是调色板索引,别硬编码。

4.4 窗口管理协议:让关闭按钮真的关掉窗口

很多新手写完窗口之后发现,点标题栏的关闭按钮没反应。原因不是代码有 bug,而是你没有和窗口管理器协商。X 协议里,窗口管理器不会直接杀你的进程,它会发一个WM_DELETE_WINDOW消息过来,你需要提前声明"我支持这个协议",并在收到消息时自己决定退出。

声明方式就是上面代码里的xcb_change_property,把WM_DELETE_WINDOW原子写进窗口的WM_PROTOCOLS属性。类型是XCB_ATOM_ATOM,格式 32 位,数量 1。这一步做完,窗口管理器才会把关闭动作转成客户端消息发给你。

处理消息时注意判断data.data32[0]是否等于你 intern 出来的WM_DELETE_WINDOW原子。之所以要比较,是因为WM_PROTOCOLS里可能不止一个协议。另外cm->window也要验证一下,如果你一个进程管多个窗口,别误关。

顺手说一个容易忽略的细节:_NET_WM_NAME属性用UTF8_STRING类型,格式写 8,长度按字节算而不是字符数。中文标题如果写成XCB_ATOM_STRING,在部分窗口管理器下会显示成乱码。这和 Linux 上解压文件乱码是同一类问题——编码协商没对齐。

5. 和 Xlib、工具包混用时最容易踩的坑

5.1 事件队列归属:一个必须显式声明的开关

现代版本的 libX11 内部就是基于 XCB 实现的,这个事实带来一个副作用:当你同时用 Xlib 和 XCB 操作同一个连接时,事件到底谁收?答案取决于事件队列的所有者是谁。默认情况下,XOpenDisplay会把所有权交给 Xlib,也就是说你用xcb_wait_for_event会一直等不到东西。

解决办法是在拿到 Display 之后立刻调用:

#include <X11/Xlib-xcb.h> Display *dpy = XOpenDisplay(NULL); xcb_connection_t *conn = XGetXCBConnection(dpy); XSetEventQueueOwner(dpy, XCBOwnsEventQueue);

这三行做完,事件就归 XCB 收了,你可以继续用xcb_wait_for_event。反过来,如果你想用 Xlib 的事件函数,就别调XSetEventQueueOwner。千万不要两边都读,一边读走了另一边就拿不到,表现是事件随机丢失,这类问题非常难查。

XGetXCBConnection拿到的连接是借用的,不要对它调xcb_disconnect,那会破坏 Xlib 的内部状态,程序退出时由XCloseDisplay负责清理。

5.2 在 Qt、GTK、SDL 里定位 XCB 相关问题

装完之后图形界面起不来,报错里出现xcb,这时候排查思路可以固定下来。先看 Qt 这边,报could not load the Qt platform plugin "xcb"通常有三类原因:插件文件缺失、依赖库版本不匹配、DISPLAY没设置。用QT_DEBUG_PLUGINS=1跑一遍,它会打印插件加载的详细过程和失败原因,比盲猜快得多。

GTK 这边,报cannot open display时先确认DISPLAY环境变量是不是指向了正确的显示号,比如:0还是:1。多用户登录或者用了 Xvfb 的场景下,显示号经常对不上。GTK 也支持GDK_BACKEND=x11强制走 X11 后端,用来和 Wayland 问题做隔离。

SDL 这边有个常见现象:程序在嵌入式设备上启动黑屏但进程活着。这往往是 XCB 的连接建立了但没找到合适的视觉,SDL 会尝试多种视觉配置,失败后可能静默降级。开SDL_VIDEODRIVER=x11和调试日志能看出它选了什么视觉和深度。

还有一个更隐蔽的场景:如果你在做 Vulkan 开发,创建 surface 时用VK_KHR_xcb_surface扩展,需要把xcb_connection_t*和xcb_window_t传给驱动。这里的连接必须是事件队列的所有者,否则呈现队列和事件处理会打架。这点在混用工具包时特别容易出问题。

6. 常见问题速查与独家排查手法

6.1 问题速查表

现象大概率原因排查动作
窗口不出现,程序卡在等待事件忘了xcb_flush在 map_window 后加 flush,或在循环前 flush
窗口出现后一片空白没处理 EXPOSE,或 GC 未创建在事件循环里处理XCB_EXPOSE
点关闭按钮没反应未声明WM_DELETE_WINDOW用 change_property 写入 WM_PROTOCOLS
中文标题乱码用了XCB_ATOM_STRING而非 UTF8_STRING改用_NET_WM_NAME+UTF8_STRING
常驻程序内存缓慢上涨_reply结构体未 free检查每个*_reply后是否 free
多线程下绘制错乱GC 被多线程共享每个线程独立 GC,或加锁串行化
混用 Xlib 后事件收不到事件队列归属未切换调用XSetEventQueueOwner
远程转发下操作明显变慢大量串行阻塞请求改用 cookie 批量发送再统一收

6.2 用 xcb_request_check 把异步错误变成可捕获的错误

XCB 默认的行为是:请求出错时,服务端会回一个错误事件,但如果你没在事件掩码里打开XCB_EVENT_MASK_STRUCTURE_NOTIFY之类,这个错误会被默默丢掉。结果是你的请求根本没生效,程序却继续往下跑。

调试阶段强烈建议在关键请求之后加检查:

xcb_void_cookie_t ck = xcb_create_window_checked(conn, /* ... */); xcb_generic_error_t *err = xcb_request_check(conn, ck); if (err) { fprintf(stderr, "请求失败,错误码 = %d\n", err->error_code); free(err); }

所有xcb_void_cookie_t类型的请求都有对应的_checked版本,比如xcb_map_window_checked、xcb_change_property_checked。它们返回 cookie,拿去给xcb_request_check就能同步拿到错误。性能上会多一次往返,所以只在调试期开,上线前换成普通版本。另外xcb_request_check返回的错误结构体同样要 free,别漏。

6.3 用 poll 把 XCB 接进自己的事件循环

写守护进程或者需要同时监听多个 fd 的程序时,不能直接阻塞在xcb_wait_for_event上。正确做法是拿到连接的文件描述符:

int fd = xcb_get_file_descriptor(conn); /* 把 fd 加入 poll/epoll 监听 */ struct pollfd pfd = { .fd = fd, .events = POLLIN }; poll(&pfd, 1, timeout_ms); /* 有可读事件后,先把队列排空再读新的 */ xcb_generic_event_t *ev; while ((ev = xcb_poll_for_queued_event(conn))) { /* 处理并 free */ } ev = xcb_poll_for_event(conn);

这里有个陷阱:必须先调xcb_poll_for_queued_event把内部队列排空,再调xcb_poll_for_event去读 socket。因为 XCB 一次读 socket 可能解析出多个事件,剩余的存在队列里。如果你只看 fd 可读与否,队列里积压的事件永远不会被处理,表现是事件延迟越来越高。

7. 我在这几年实际用下来的一些体会

XCB 这个东西,刚上手时会觉得它"什么都不帮你做",写个窗口要四十行,画个矩形要建 GC,关个窗口还要跟窗口管理器协商协议。但用久了会发现,这份啰嗦换来的是完全的确定性:你知道每一个字节什么时候发出去,知道每一个事件从哪里来,知道每一块内存什么时候该释放。排查问题时这种确定性比什么都值钱。

真要说经验,第一条是永远先检查xcb_connection_has_error,跳过这一步省下的三行,往往要用半小时调试去还。第二条是编译期就开-Wall -Wextra,链接时加-fsanitize=address跑一遍,XCB 的泄漏问题 ASan 一抓一个准,比事后翻代码快太多。第三条是别急着上 xcb-util 系列,先把裸 libxcb 的连接、请求、事件、内存释放四条线走通一遍,后面用辅助库时才知道它到底帮你封装了什么。

最后一个算是习惯性的建议:如果你打算长期做 Linux 图形相关的开发,花一个下午把 X 协议里 CreateWindow、MapWindow、ChangeProperty、InternAtom 这几个请求的字段含义读一遍,再对照 XCB 的函数签名看,很多以前靠试错解决的问题会突然变得显而易见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询