X 窗口编程/Xlib
Xlib 是一个用 X 窗口系统 协议编写的 C 编程语言 客户端库。它包含与 X 服务器交互的函数。这些函数允许程序员编写程序,而无需了解协议的细节。很少有应用程序直接使用 Xlib;相反,它们使用其他库,这些库使用 Xlib 函数来提供部件工具包
- Intrinsics (Xt)
- Athena 部件集 (Xaw)
- Motif
- GTK+
- Qt (X11 版本)
- fpGUI (Free Pascal GUI 工具包)
Xlib 诞生于 1985 年左右,目前被许多类 Unix 操作系统的 GUI 所使用。
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。这类库有两种
- 构建在 Intrinsics 库(Xt)之上的库,它提供了对部件的支持,但没有提供任何特定的部件;使用 Xt 的部件集库提供了特定的部件,例如 Xaw 和 Motif;
- 直接使用 Xlib,而没有使用 Xt 库来提供部件集的库,例如 GTK+、Qt (X11 版本) 和 FLTK (X11 版本)。
使用任何这些部件库的应用程序通常在进入主循环之前指定窗口的内容,并且不需要显式处理Expose
事件和重新绘制窗口内容。
XCB 库是 Xlib 的替代方案。它的两个主要目标是:减小库大小和直接访问 X11 协议。Xlib 已经进行了修改以使用 XCB 作为底层。