每一個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