天天看點

xeyes的重新實作--x系統及其Xlib以及和windows的異同

每一個X程式都對應一個x client,x server則隻負責輸入輸出事件和請求,鍵盤和滑鼠以及觸摸屏之類的動作會被x server捕獲,作為事件通過x協定傳給x client,然後x client得到這些事件之後會根據它們做一些邏輯計算,然後得到一個請求通過x協定發給x server,此時x server就會根據x client的請求将圖形繪制出來,通過輸出裝置顯示。一個x server可以同時被很多個x client連接配接,是以它必須管理總體的圖形布局,包括哪個視窗屬于哪個x client,哪個視窗此時位于最上方,哪個視窗被遮住了哪個部分等等,這些同樣作為事件--類似鍵盤滑鼠等的輸入事件傳給相應的x client,是以可以想象,每一個可以顯示的元素都要幫定一個或者幾個x client,x client得到這些事件(管理總體布局的事件,而非輸入裝置的事件)之後,同樣要做出一些反應,比如當它的視窗重新顯示的時候要重畫整個它的視窗等等。

     x系統是一個c/s結構的系統,它并不以視窗為核心,而是以程序(x client--xterm/xeyes,x server--xorg...)為核心的,x server程序負責捕獲事件--包括輸入裝置的事件和視窗管理事件,并且将事件傳遞給相應的x client,而x client則根據事件負責邏輯處理,并将處理結果作為顯示請求回傳給x server,x server得到請求後進行顯示。一般的,每一個x server都有一個事件隊列和一個請求隊列,每一個事件元素綁定x client,它的大體結構應該包含下面的邏輯:

1.

while(...) {

    1.從事件隊列取出一個事件;

    2.得到事件隊應的x client;

    3.将事件發給x client;

}

2.

    1.從請求隊列取出一個請求;

    3.根據請求進行顯示;

3.

3.0:根據滑鼠的位置或者(以及)目前視窗得到一個或者多個對之感興趣的x client用于下面3.1到3.x的事件構造;

3.1:滑鼠回調:構造一個事件加入事件隊列;

3.2:鍵盤回調:構造一個事件加入事件隊列;

...

x client則同樣擁有一個事件隊列和一個請求隊列,但是邏輯和x server正好相反。

作為一個例子,首先啟動一個x server(将配置中的tcpip打開,否則别的機器沒法連接配接),然後用xhost增加一個可以連接配接的主機,然後在該主機上設定DISPLAY環境變量,最後運作一個最簡單的xeyes,然後到x server上移動一下滑鼠,在x client上strace一下這個xeyes,希望得到的是當晃動滑鼠的時候,strace會列印出tcpip連接配接的資料,可是結果卻很亂,不停的select,不停的逾時,不停的列印...看了xeyes的源碼才知道,原來它使用的是timer,也就是說不管有沒有事件,timer都會定期的到期,然後去select網絡連接配接,也就是和x server的連接配接。這樣豈不是很浪費資源,于是就想用最底層的xlib重寫一下著名的xeyes,xlib可以說是比較底層的x程式設計庫了,再往下就是直接用socket用x協定寫代碼了。順便說一句,在xeyes的man手冊中,有這麼一句話:Xeyes watches what you do and reports to the Boss.然而它并沒有向任何人報告你的一舉一動,看了xeyes的代碼後,突然覺得這是可以的,因為XQueryPointer函數可以傳回足夠的資訊,它足以讓你的Boss将你fire了,具體的方式就是在XQueryPointer之後以XQueryPointer的第四個參數child_return為參數調用XFetchName(我們簡單一點說,實際上還有更多資訊可以擷取的),然後就可以得到滑鼠目前在哪個視窗上,這樣Boss就知道你在幹什麼了,這個資訊很容易通過syslog發送出去的,不過前提是Boss的xeyes視窗必須隐藏,也就是必須隐藏那雙眼睛。在下面的代碼中,我将不處理報告Boss的邏輯,因為上面已經簡單說過了。

     下面就是事件循環版本的xeyes的源代碼了,它除了xlib沒有使用其餘的庫,也沒有使用輪詢式的timer,而是使用了事件中斷的觸發機制,隻有在有滑鼠移動事件的時候xeyes才會行動,再者,所有和數學相關的代碼完全拷貝timer版的xeyes代碼,下面就是源代碼:

#include <X11/Xlib.h>

#include <X11/Xos.h>

#include <stdio.h>

#include <X11/IntrinsicP.h>

#include <X11/StringDefs.h>

#include <X11/extensions/shape.h>

#include <stdio.h> 

#include <math.h>

#include "transform.h"

int main(void) 

{

    Display *disp = XOpenDisplay(NULL);    //得到預設的display

    Window win_root = DefaultRootWindow(disp); //如果不使用這個root,将得不到xeyes視窗之外的事件。

    int screen = DefaultScreen(display);

    int black = BlackPixel(display, screen);

    int white = WhitePixel(display, screen);

    Window win_eyes = XCreateSimpleWindow(disp, win_root,  0, 0, 150, 100, 3, black, white);

    XSelectInput(disp, win_root, PointerMotionMask );

    XSelectInput(disp, win_eyes, ExposureMask|PointerMotionMask );

    XMapWindow(disp, win_eyes);

    XGCValues    xgcv1;

    xgcv1.function = GXorReverse;

    XtGCMask valuemask1 = GCFunction|GCForeground | GCBackground;

    GC gc_eyeballs = XCreateGC(disp, win_root, 0, NULL);

    XGCValues values_return;

    XGetGCValues (disp, gc_eyeballs, valuemask1, &values_return);

    Pixel fg = values_return.foreground;

    Pixel bg = values_return.background;

    xgcv1.foreground = bg;

    xgcv1.background = fg; //注意這裡建立了兩個反轉的畫筆,作用在于畫眼珠子之前先擦除原來的眼珠子

    GC gc_balls = XCreateGC(disp, win_root, valuemask1, &xgcv1);

    XGCValues    xgcv;

    Pixmap pim = XCreatePixmap (disp, win_eyes, 150, 100, 1);

    XtGCMask valuemask = GCForeground | GCBackground;

    GC gc_eyes = XCreateGC (disp, pim, valuemask, &xgcv);

    XSetForeground (disp, gc_eyes, 0);

    XFillRectangle (disp, pim, gc_eyes, 0, 0,150, 100);

    XSetForeground (disp, gc_eyes, 1);

    Window        rep_root, rep_child;

    int        rep_rootx, rep_rooty;

    unsigned int    rep_mask;

    int        dx, dy;

    TPoint        mouse;

    TPoint        newpupil[2]; //記錄當次的眼睛位置

    TPoint        oldpupil[2]; //記錄上次的眼睛位置

    Transform    t; //涉及到數學的計算,全部拷貝原始的xeyes代碼

    SetTransform (&t,0, 150,100, 0, W_MIN_X, W_MAX_X, W_MIN_Y, W_MAX_Y);

    while(1) {

        XEvent report = {0};

        XNextEvent(disp, &report);

        switch (report.type){

            case Expose:

                if (report.xexpose.window == win_root) {

                    //一般不會執行到這裡,因為我們取的是根視窗,除非建立一個全螢幕的視窗使之遮住根視窗,然後移動它。

                } else { //以下的T打頭的函數全部拷貝自原始代碼

                    TFillArc (disp, pim, gc_eyes, &t,

                          EYE_X(0) - EYE_HWIDTH - EYE_THICK,

                           EYE_Y(0) - EYE_HHEIGHT - EYE_THICK,

                          EYE_WIDTH + EYE_THICK * 2.0,

                           EYE_HEIGHT + EYE_THICK * 2.0,

                           90 * 64, 360 * 64); //畫左眼

                          EYE_X(1) - EYE_HWIDTH - EYE_THICK,

                           EYE_Y(1) - EYE_HHEIGHT - EYE_THICK,

                           90 * 64, 360 * 64); //畫右眼

                           //裁剪視窗

                    XShapeCombineMask (disp, win_eyes, ShapeBounding, 0, 0, pim, ShapeSet);

                    TFillArc (disp, win_eyes, gc_eyeballs, &t,

                           90*64, 360 * 64); //塗黑左眼

                            TFillArc (disp, win_eyes, gc_balls, &t,

                          EYE_X(0) - EYE_HWIDTH,

                           EYE_Y(0) - EYE_HHEIGHT,

                          EYE_WIDTH, EYE_HEIGHT,

                          90 * 64, 360 * 64); //描左眼圈

                           90*64, 360 * 64); //塗黑右眼

                          EYE_X(1) - EYE_HWIDTH,

                           EYE_Y(1) - EYE_HHEIGHT,

                           90*64, 360 * 64); //描右眼圈

                } //不要break,繼續畫眼珠子

            case MotionNotify:

                //得到目前滑鼠的位置,還順便得到了它所懸停的視窗資訊

                XQueryPointer (disp, win_root, &rep_root, &rep_child, &rep_rootx, &rep_rooty, &dx, &dy, &rep_mask);

                mouse.x = Tx(dx, dy, &t);

                mouse.y = Ty(dx, dy, &t);

                computePupils (mouse, newpupil); //數學計算

                TFillArc (disp, win_eyes, gc_balls, &t,

                       oldpupil[0].x - BALL_WIDTH / 2.0,

                       oldpupil[0].y - BALL_HEIGHT / 2.0,

                       BALL_WIDTH, BALL_HEIGHT,

                      90 * 64, 360 * 64); //先用反色清除掉原來的左眼珠子

                TFillArc (disp, win_eyes, gc_eyeballs, &t,

                               newpupil[0].x - BALL_WIDTH / 2.0,

                               newpupil[0].y - BALL_HEIGHT / 2.0,

                               BALL_WIDTH, BALL_HEIGHT,

                               90 * 64, 360 * 64); //再用前景色填充左眼珠子

                oldpupil[0].x = newpupil[0].x;

                oldpupil[0].y = newpupil[0].y;

                               oldpupil[1].x - BALL_WIDTH / 2.0,

                               oldpupil[1].y - BALL_HEIGHT / 2.0,

                               90 * 64, 360 * 64); //右眼珠子同左眼珠子

                               newpupil[1].x - BALL_WIDTH / 2.0,

                               newpupil[1].y - BALL_HEIGHT / 2.0,

                               90 * 64, 360 * 64);

                oldpupil[1].x = newpupil[1].x;

                oldpupil[1].y = newpupil[1].y;

                break;

        }

    }

    return 0;

Xlib程式設計很簡單,隻要幾個要素齊備了,那麼剩下的就是閉着眼睛敲代碼了,首先是一個display--這是一個環境,然後是一個drawable--這是一張畫布,它可以是一個windows,最後是一個GC--它是一支畫筆,總的情況就是在display中用gc在drawable上作畫,一個main函數架構就是:

main()

    1.得到一個display,作為disp;

    2.得到一個drawable,作為一個win;

    3.得到一支畫筆,作為gc;

    4.注冊自己感興趣的事件告訴x server;

    5.編寫一個事件循環:

    while (1) {

        5.1.得到一個x server發來的事件;

        5.2.處理該事件;

linux版本的無timer的xeyes到此結束,那麼windows的呢?

可以想象,windows的實作更加簡單一些,當用vc建立一個win32工程之後,IDE就已經幫你完成一半工作了,仔細了解這一半的工作,發現它和上述xlib程式設計的main架構是如此的相似。相似之後還有些許不同,這些不同完全是windows的消息機制和x系統的架構機制不同所導緻的,x系統的當然要更簡單一些,x系統基于程序,而windows基于線程,是以windows的消息機制之是以複雜,那是因為它使用一些作業系統本身的機制,比如線程。如果我們将windows的xeyes應用有意簡化,使之向xlib的代碼靠攏,最後我們發現,它們更加一緻。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271163