跳轉到內容

X 視窗程式設計/Xlib

來自華夏公益教科書,開放的書籍,用於開放的世界
  1. 庫約定
  2. 標準標頭檔案
  3. 型別和值

Xlib 是一個用 C 程式語言 編寫的 X 視窗系統 協議客戶端庫。它包含用於與 X 伺服器互動的函式。這些函式允許程式設計師在不知道協議細節的情況下編寫程式。很少有應用程式直接使用 Xlib;相反,它們使用其他庫,這些庫使用 Xlib 函式來提供視窗小部件工具包。

Xlib 和使用它的其他庫

Xlib 出現在 1985 年左右,目前用於許多類 Unix 作業系統的 GUI 中。

The XCB 庫試圖替換 Xlib。

資料型別

[編輯 | 編輯原始碼]

Xlib 中的主要資料型別是 Display 結構體和識別符號的型別。

非正式地說,顯示器是一個物理或虛擬裝置,在其中進行圖形操作。Xlib 庫的 Display 結構體包含有關顯示器的資訊,但更重要的是,它包含與客戶端和伺服器之間通道相關的的資訊。例如,在類 Unix 作業系統中,Display 結構體包含此通道的套接字的檔案控制代碼(可以使用 ConnectionNumber 宏檢索)。大多數 Xlib 函式都有一個 Display 結構體作為引數,因為它們要麼在通道上操作,要麼與特定通道相關。特別地,所有與伺服器互動的 Xlib 函式都需要此結構體來訪問通道。一些其他函式也需要此結構體,即使它們在本地操作,因為它們對與特定通道相關的資料進行操作。這種操作包括例如對事件佇列的操作,如下所述。

視窗、顏色對映等由伺服器管理。因此,它們的所有資料都儲存在伺服器中,客戶端無法直接修改或操作物件。相反,客戶端向伺服器提交請求,每個請求都指定一個操作和物件的識別符號

型別 WindowPixmapFontColormap 等都是識別符號,它們是 32 位整數(就像 X11 協議本身一樣)。客戶端透過要求伺服器建立視窗來“建立”視窗。這是透過呼叫一個 Xlib 函式來完成的,該函式返回視窗的識別符號(一個數字)。然後,客戶端可以使用此識別符號來請求對該視窗的進一步操作。

識別符號對伺服器是唯一的。它們中的大多數可以被不同的應用程式用來引用同一個物件。例如,兩個連線到同一個伺服器的應用程式將使用相同的識別符號來引用同一個視窗。這兩個應用程式使用單獨的通道,因此具有兩個不同的Display 結構體;但是,當它們請求對同一個識別符號的操作時,這些操作將在同一個物件上執行。

協議和事件

[編輯 | 編輯原始碼]

向伺服器傳送請求的 Xlib 函式通常不會立即傳送這些請求,而是將它們儲存在一個緩衝區中,稱為輸出緩衝區。在這種情況下,術語輸出指的是來自客戶端並定向到伺服器的輸出:輸出緩衝區可以包含所有型別的伺服器請求,而不僅僅是那些對螢幕有可見影響的請求。在呼叫函式 XSyncXFlush 之後,在呼叫返回來自伺服器的值的函式之後(這些函式會阻塞直到收到答案),以及在某些其他條件下,輸出緩衝區保證會被重新整理(即,到目前為止完成的所有請求都將被髮送到伺服器)。

Xlib 將接收到的事件儲存在一個佇列中。客戶端應用程式可以檢查並從佇列中檢索事件。雖然 X 伺服器非同步傳送事件,但使用 Xlib 庫的應用程式需要顯式呼叫 Xlib 函式來訪問佇列中的事件。這些函式中的一些可能會阻塞;在這種情況下,它們也會重新整理輸出緩衝區。

錯誤是非同步接收和處理的:應用程式可以提供一個錯誤處理程式,該處理程式將在接收到來自伺服器的錯誤訊息時被呼叫。

如果視窗或其一部分變得不可見,則不能保證視窗的內容會被保留。在這種情況下,當視窗或其一部分再次變得可見時,應用程式會收到一個 Expose 事件。然後,應用程式應該再次繪製視窗內容。

Xlib 庫中的函式可以分為

  1. 對連線的操作 (XOpenDisplayXCloseDisplay、...);
  2. 對伺服器的請求,包括對操作的請求 (XCreateWindowXCreateGC、...) 和對資訊的請求 (XGetWindowProperty、...);以及
  3. 對客戶端本地的操作:對事件佇列的操作 (XNextEventXPeekEvent、...) 和對本地資料的其他操作 (XLookupKeysymXParseGeometryXSetRegionXCreateImageXSaveContext、...)

以下程式建立一個帶有黑色小正方形的視窗,並在按下鍵盤時退出。

#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 不提供對按鈕、選單、捲軸等的支援。這些視窗小部件由其他庫提供,而這些庫又使用 Xlib。這類庫有兩種

  • 建立在 內在函式 庫 (Xt) 之上的庫,它提供對視窗小部件的支援,但不提供任何特定的視窗小部件;使用 Xt 的視窗小部件集庫提供了特定的視窗小部件,例如 XawMotif
  • 使用 Xlib 直接提供視窗小部件集的庫,不使用 Xt 庫,例如 GTK+Qt (X11 版本) 和 FLTK (X11 版本)。

使用任何這些視窗小部件庫的應用程式通常在進入主迴圈之前指定視窗的內容,並且不需要顯式處理 Expose 事件和重新繪製視窗內容。

The XCB 庫是 Xlib 的替代方案。它的兩個主要目標是:減少庫大小和直接訪問 X11 協議。Xlib 的一個修改版本已經被生產出來,以使用 XCB 作為底層。

華夏公益教科書