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 作為底層。