X Window 系统的窗口显示原理

X Window 系统介绍

  X Window 系统是一个基于网络的图形界面系统,它于 1984 年在麻省理工学院开发,有将近 20 年的应用历史。X Window 系统广泛的应用于桌面 Linux(如 Fedora、Debian、Ubuntu 等),嵌入式 Linux(如 Nokia 的 Maemo、Intel 的 Moblin 等)。随着 Nokia 和 Intel 高调的将 Maemo 和 Moblin 合并为 Meego,X Window 系统的应用将被推向一个新的高潮。

  X Window 是 C/S 架构,涵盖 X Server、X 协议、X Client 三部分内容。其 X Client 有三种开发模式:基于 XLib、基于 GTK、基于 Qt。

  本文将以一个基于 XLib 的应用来介绍 X Window 的窗口显示原理。

  示例及运行结果

  示例代码将显示一个 200X200 的白色背景窗口,并在窗口的中间绘制一个 100 个点,连接成一条横线。按任意按键可以退出该程序。

清单 1. hello.c

 

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
 #include < stdio.h > 
 #include < stdlib.h > 
 #include < X11 /Xlib.h> 
 
 #define WINDOW_SIZE 200 
 int main (int argc, char *argv[]) 
 { 
 Display       *dpy; 
 XSetWindowAttributes attributes; 
 Window        win; 
 GC          gc; 
 XKeyEvent event; 
 int  i; 
 
 // 连接到 X Server,创建到 X Server 的套接字连接 
 dpy = XOpenDisplay(NULL); 
 
 // 创建 200X200 的白色背景窗口 
 attributes.background_pixel = XWhitePixel(dpy, 0);  
 win = XCreateWindow(dpy, XRootWindow(dpy, 0), 
   0, 0, WINDOW_SIZE, WINDOW_SIZE, 0, DefaultDepth(dpy, 0), 
   InputOutput, DefaultVisual(dpy, 0), 
   CWBackPixel,&attributes); 
 // 选择输入事件。 
 XSelectInput(dpy, win, ExposureMask | KeyPressMask ); 
 // 创建绘图上下文 
 gc = XCreateGC(dpy, win, 0, NULL); 
 //Map 窗口 
 XMapWindow(dpy, win); 
 // 事件主循环。主要处理 Expose 事件和 KeyPress 事件 
 while(1) 
 { 
  XNextEvent(dpy,(XEvent *)&event); 
  switch(event.type) 
  { 
   // 处理 Expose 事件 
   case Expose: 
    { 
     // 绘制 100 个点 
     for (i=0;i<WINDOW_SIZE/2;i++) 
      XDrawPoint(dpy, win, gc, WINDOW_SIZE/4+i, WINDOW_SIZE/2); 
    }break; 
   // 处理按键事件 
   case KeyPress: 
    { 
     XFreeGC(dpy, gc); 
     XCloseDisplay(dpy); 
     exit(0); 
    }break; 
   default: 
    { 
    }break; 
  } 
 } 
 return(0); 
 } 

 

  编译:gcc -o hello hello.c -lX11

图 1. Ubuntu 9.10 运行结果 图 2. Maemo Fremantle 模拟器运行结果

  查看原图(大图)

  在示例代码 hello.c 中,和窗口显示相关的接口主要有:XCreateWindow,XMapWindow,XDrawPoint。在示例代码之外,X Server 和窗口管理器同时在发挥着各自的关键作用。本文将结合 X Server、窗口管理器、示例 hello.c 代码来解释窗口显示的原理。

  创建窗口:XCreateWindow

  在 X Window 系统中,客户端申请的 GC、Pixmap、Window 等资源位于服务器 X Server 端。而客户端创建的另一些资源如 XImage,不由 X Server 管理,而是由客户端自行管理。

  在客户端调用 XCreateWindow 创建一个窗口时,X Server 会为它建立一个 Window 类型的数据结构。该结构中描述了窗口的大小、坐标等信息。窗口实际上是屏幕的一块区域,子窗口是父窗口的一部分,所有的窗口有一个共同的根即根窗口。

  客户端调用 XCreateWindow 接口时,对应的 X Server 实现是 CreateWindow 函数

 

清单 2. CreateWindow 实现

 

 WindowPtr 
 CreateWindow(Window wid, WindowPtr pParent, int x, int y, unsigned w, 
       unsigned h, unsigned bw, unsigned class, Mask vmask, XID *vlist, 
       int depth, ClientPtr client, VisualID visual, int *error) 
 { 
 /* 省略非关键代码部分 */ 
  pScreen = pParent->drawable.pScreen; 
  pWin->drawable = pParent->drawable;/* 子窗口是父窗口的一部分 */ 
  pWin->devPrivates = NULL; 
  pWin->drawable.depth = depth; 
 /* 省略非关键代码部分 */ 
  pWin->origin.x = x + (int)bw; 
  pWin->origin.y = y + (int)bw; 
  pWin->drawable.width = w;/* 窗口管理信息 */ 
  pWin->drawable.height = h; 
  pWin->drawable.x = pParent->drawable.x + x + (int)bw; 
  pWin->drawable.y = pParent->drawable.y + y + (int)bw; 
… 
 } 

 

  在 GTK 中,调用 gdk_window_new 会创建一个 X 窗口。GTK 提供了三种类型的顶层窗口:GDK_WINDOW_TOPLEVEL、GDK_WINDOW_DIALOG、GDK_WINDOW_TEMP。这些顶层窗口的父亲是 GDK_SCREEN_XROOTWIN,即根窗口。而 GDK_WINDOW_CHILD 类型的窗口其父亲由用户创建窗口时指定。GDK_WINDOW_CHILD 类型的窗口对应的是 GTK 的控件,如 GtkButton、GtkEntry 等。

  如前述,所有的窗口都是父窗口的一部分。所有窗口的根是根窗口。根窗口由 X Server 在启动时创建,对应整个屏幕区域。

  以 GTK 为例子,GTK 窗口层级视图是下图 3 的样子:

图 3. GTK 窗口层级视图

  查看原图(大图)

  对单个应用而言,部分窗口管理器如 Matchbox Window Manager 只支持一个应用级顶层窗口,譬如 Maemo Fremantle 使用的就是该类型的窗口管理器。这就是为什么 Maemo Fremantle 模拟器运行 hello.c 的显示结果 ( 图 2) 不是设定的尺寸 200X200,而是延伸到整个屏幕宽度。在 Maemo Fremantle 模拟器顶部显示的状态条其实是主界面显示的,该部分是 Dock 类型的窗口,和 hello.c 无关。

  CreateWindow 调用结束的时候给客户端发送 CreateNotify 事件。但是 GTK 没有处理该事件。

  映射窗口:XMapWindow

  XMapWindow 对应的 X Server 实现是 MapWindow 函数。该代码较长,而且涉及到 X Client、X Server、窗口管理器的多次交互。

  MapWindow 的工作原理是:

  客户端调用 MapWindow 请求映射 Client 窗口。如果该窗口的 overrideRedirect 为假,表示该 MapWindow 调用为普通客户端发起。则发送 MapRequest 到窗口管理器。请求窗口管理器进一步处理。

  窗口管理器收到 MapRequest,创建一个 Frame 窗口,并通过 XReparentWindow 调用,将客户端的窗口设置为 Frame 窗口的子窗口。Frame 窗口的 overrideRedirect 为真。

  窗口管理器调用 XMapSubwindows,第二个参数为 Frame 窗口。由于 Frame 窗口的 overrideRedirect 为真,MapSubwindows 会对该 Frame 窗口的子窗口做映射。并发送 MapNotify 事件、Expose 事件给客户端。客户端在 Expose 事件中绘制客户端窗口内容。

  窗口管理器调用 XMapWindow,第二个参数为 Frame 窗口。同样由于 Frame 窗口的 overrideRedirect 为真,这次 MapWindow 也不发射 MapRequest 事件了,从而映射了 Frame 窗口。

  X Server 在映射 Frame 窗口之后,发送 Expose 事件给窗口管理器,以通知窗口管理器绘制窗口的边框等。

  至此,客户端窗口的内容,窗口的边框都被显示在屏幕上了。

  下图 4 是 X Client、X Server、窗口管理器的交互序列图:

图 4. X Client、X Server、窗口管理器交互序列图

  结束语

  X Window 是一个功能非常强大的 C/S 图形显示系统,具有很好的跨网络性能,也易于进行扩展。了解其窗口显示原理对程序员进行 GTK/QT 编程是有帮助的。

你可能感兴趣的:(xWindow)