X 窗口编程/Xlib
Xlib 是一个用 C 编程语言 编写的 X 窗口系统 协议客户端库。它包含用于与 X 服务器交互的函数。这些函数允许程序员在不知道协议细节的情况下编写程序。很少有应用程序直接使用 Xlib;相反,它们使用其他库,这些库使用 Xlib 函数来提供窗口小部件工具包。
Xlib 出现在 1985 年左右,目前用于许多类 Unix 操作系统的 GUI 中。
The XCB 库试图替换 Xlib。
Xlib 中的主要数据类型是 Display
结构体和标识符的类型。
非正式地说,显示器是一个物理或虚拟设备,在其中进行图形操作。Xlib 库的 Display
结构体包含有关显示器的信息,但更重要的是,它包含与客户端和服务器之间通道相关的的信息。例如,在类 Unix 操作系统中,Display
结构体包含此通道的套接字的文件句柄(可以使用 ConnectionNumber
宏检索)。大多数 Xlib 函数都有一个 Display
结构体作为参数,因为它们要么在通道上操作,要么与特定通道相关。特别地,所有与服务器交互的 Xlib 函数都需要此结构体来访问通道。一些其他函数也需要此结构体,即使它们在本地操作,因为它们对与特定通道相关的数据进行操作。这种操作包括例如对事件队列的操作,如下所述。
窗口、颜色映射等由服务器管理。因此,它们的所有数据都存储在服务器中,客户端无法直接修改或操作对象。相反,客户端向服务器提交请求,每个请求都指定一个操作和对象的标识符。
类型 Window
、Pixmap
、Font
、Colormap
等都是标识符,它们是 32 位整数(就像 X11 协议本身一样)。客户端通过要求服务器创建窗口来“创建”窗口。这是通过调用一个 Xlib 函数来完成的,该函数返回窗口的标识符(一个数字)。然后,客户端可以使用此标识符来请求对该窗口的进一步操作。
标识符对服务器是唯一的。它们中的大多数可以被不同的应用程序用来引用同一个对象。例如,两个连接到同一个服务器的应用程序将使用相同的标识符来引用同一个窗口。这两个应用程序使用单独的通道,因此具有两个不同的Display
结构体;但是,当它们请求对同一个标识符的操作时,这些操作将在同一个对象上执行。
向服务器发送请求的 Xlib 函数通常不会立即发送这些请求,而是将它们存储在一个缓冲区中,称为输出缓冲区。在这种情况下,术语输出指的是来自客户端并定向到服务器的输出:输出缓冲区可以包含所有类型的服务器请求,而不仅仅是那些对屏幕有可见影响的请求。在调用函数 XSync
或 XFlush
之后,在调用返回来自服务器的值的函数之后(这些函数会阻塞直到收到答案),以及在某些其他条件下,输出缓冲区保证会被刷新(即,到目前为止完成的所有请求都将被发送到服务器)。
Xlib 将接收到的事件存储在一个队列中。客户端应用程序可以检查并从队列中检索事件。虽然 X 服务器异步发送事件,但使用 Xlib 库的应用程序需要显式调用 Xlib 函数来访问队列中的事件。这些函数中的一些可能会阻塞;在这种情况下,它们也会刷新输出缓冲区。
错误是异步接收和处理的:应用程序可以提供一个错误处理程序,该处理程序将在接收到来自服务器的错误消息时被调用。
如果窗口或其一部分变得不可见,则不能保证窗口的内容会被保留。在这种情况下,当窗口或其一部分再次变得可见时,应用程序会收到一个 Expose
事件。然后,应用程序应该再次绘制窗口内容。
Xlib 库中的函数可以分为
- 对连接的操作 (
XOpenDisplay
、XCloseDisplay
、...); - 对服务器的请求,包括对操作的请求 (
XCreateWindow
、XCreateGC
、...) 和对信息的请求 (XGetWindowProperty
、...);以及 - 对客户端本地的操作:对事件队列的操作 (
XNextEvent
、XPeekEvent
、...) 和对本地数据的其他操作 (XLookupKeysym
、XParseGeometry
、XSetRegion
、XCreateImage
、XSaveContext
、...)
以下程序创建一个带有黑色小正方形的窗口,并在按下键盘时退出。
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
enum {
RECT_X = 20,
RECT_Y = 20,
RECT_WIDTH = 10,
RECT_HEIGHT = 10,
WIN_X = 10,
WIN_Y = 10,
WIN_WIDTH = 100,
WIN_HEIGHT = 100,
WIN_BORDER = 1
};
int main() {
Display *display;
Window window;
XEvent event;
int screen;
/* open connection with the server */
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
screen = DefaultScreen(display);
/* create window */
window = XCreateSimpleWindow(display, RootWindow(display, screen), WIN_X, WIN_Y, WIN_WIDTH, WIN_HEIGHT,
WIN_BORDER, BlackPixel(display, screen), WhitePixel(display, screen));
/* process window close event through event handler so XNextEvent does not fail */
Atom del_window = XInternAtom(display, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(display, window, &del_window, 1);
/* select kind of events we are interested in */
XSelectInput(display, window, ExposureMask | KeyPressMask);
/* display the window */
XMapWindow(display, window);
/* event loop */
while (1) {
XNextEvent(display, &event);
switch (event.type) {
case KeyPress:
/* FALLTHROUGH */
case ClientMessage:
goto breakout;
case Expose:
/* draw the window */
XFillRectangle(display, window, DefaultGC(display, screen), RECT_X, RECT_Y, RECT_WIDTH, RECT_HEIGHT);
/* NO DEFAULT */
}
}
breakout:
/* destroy window */
XDestroyWindow(display, window);
/* close connection to server */
XCloseDisplay(display);
return 0;
}
客户端通过调用 XOpenDisplay
与服务器建立连接。然后,它通过 XCreateSimpleWindow
请求创建一个窗口。需要单独调用 XMapWindow
来映射窗口,即使其在屏幕上可见。
正方形是通过调用 XFillRectangle
绘制的。此操作只能在窗口创建后执行。但是,执行一次可能还不够。实际上,不能保证窗口的内容总是被保留。例如,如果窗口被覆盖然后再次揭开,它的内容可能需要重新绘制。程序通过接收 Expose
事件来获知窗口或其一部分需要被绘制。
因此,窗口内容的绘制是在处理事件的循环中进行的。在进入此循环之前,应用程序感兴趣的事件被选择,在本例中使用 XSelectInput
选择。事件循环等待传入的事件:如果此事件是按键,应用程序退出;如果它是暴露事件,窗口内容被绘制。函数 XNextEvent
阻塞并刷新输出缓冲区,如果队列中没有事件。
Xlib 不提供对按钮、菜单、滚动条等的支持。这些窗口小部件由其他库提供,而这些库又使用 Xlib。这类库有两种
- 建立在 内在函数 库 (Xt) 之上的库,它提供对窗口小部件的支持,但不提供任何特定的窗口小部件;使用 Xt 的窗口小部件集库提供了特定的窗口小部件,例如 Xaw 和 Motif;
- 使用 Xlib 直接提供窗口小部件集的库,不使用 Xt 库,例如 GTK+、Qt (X11 版本) 和 FLTK (X11 版本)。
使用任何这些窗口小部件库的应用程序通常在进入主循环之前指定窗口的内容,并且不需要显式处理 Expose
事件和重新绘制窗口内容。
The XCB 库是 Xlib 的替代方案。它的两个主要目标是:减少库大小和直接访问 X11 协议。Xlib 的一个修改版本已经被生产出来,以使用 XCB 作为底层。