2008年3月20日星期四

X windows图形系统——现在和将来

X导航(一些笔记)

这些笔记来自O'Reilly出版的《The Definitive Guides to the X Window Systems, Vol 1 - Xlib Programming Manual, 1992年第三版 (By Laxxuss)

Xserver的职责

  • 允许多个客户端访问显示资源(display = screen + mouse + keyboard)

  • 解释来自客户端的网络消息

  • 把用户的输入通过网络消息传给客户端

  • 二维绘制

  • 维护一些数据结构 (windows, cursors, fonts, 图形相关上下文Graphics Context——客户端通过资源ID来共享前述资源)

一般客户端的职责

响应相应的事件(输入事件、与其他程序的交互事件)

特殊客户端——窗口管理器的职责

管理有限的UI资源(显示(display)、屏幕空间、颜色表)

  • 屏幕上窗口的布局(用户请求(hint) + 窗口布局政策)

  • 移动窗口和改变窗口的大小

  • 控制屏幕上的窗口栈

X协议分类

  • 请求xlib->server (e.g.绘制线、查询窗口大小、改变颜色表中的某个单元的颜色)。大多数Xlib例程产生请求(除了如Regions和资源管理)。

  • 响应server->xlib,响应相关请求。需要获得响应的xlib例程发出的请求被称为round−trip(往返) request(对性能造成影响)。

  • 事件server->xlib,在xlib中事件保存为队列。客户指定服务器送来事件的范围。

  • 错误server->xlib,通知客户端前一请求是无效,类似事件,但是由Xlib指定的句柄处理(默认是打印错误消息)。

Xlib的覆盖面

  • 颜色管理:绘制用的颜色模式(解释)之间的切换

  • 指针管理(Cursors):鼠标图样的改变

  • 数据管理:数据和窗口或者数字联系的机制

  • 显示(Display)的连接:(通过网络)程序连接/断开一个显示(Display)

  • 获取显示(Display)和服务端相关信息:提供服务端的实现和其相连的显示(Display) 的信息

  • 绘图:绘制/填充点、线、矩形、多边形和弧的函数

  • 错误处理只有keyboardmouse事件传播。事件掩码是每个客户端独有:一些挂接错误处理句柄的函数

  • 事件处理:获取来自用户、其他程序、服务端的事件

  • 扩展:获知服务端的扩展以及这些扩展的用法

  • 字体:列出可用的字体、载入字体以及找出其相应的字符

  • 几何:操作和转换几何相关的

  • 图形上下文:设置对绘制请求的解释(绘制的方式,如填充还是勾画、线的形状...)

  • 对主机访问的控制:控制来自网络其他机器对服务端的访问

  • 图像:获取/显示/操纵屏幕图像

  • 客户端之间的通信:

  • 国际化:独立于语言的输入和文本渲染处理函数

  • 键盘:改变键盘输入处理方式(包括键盘映射)

  • 指针设备(鼠标):改变指针输入处理方式

  • 区域(Regions)操作:对多边形区域(Region)作数学操作

  • 资源管理:方便管理用户设置和命令行参数

  • 屏幕保护:设置屏幕保护期间显示的内容

  • 文本:渲染文本和相关文本渲染的几何信息(大小...)

  • 用户设置:设置/获取键盘连击相关设置

  • 窗口属性(Attribute):获取/设置窗口的当前特质(属性)

  • 窗口生命周期:窗口创建和消亡的函数

  • 窗口管理:在屏幕上操作窗口——改变大小、可见性、窗口栈

Xlib清空请求队列(到server端)的时机

  1. 调用需要立即响应的函数(名中含QueryFetchGet的例程)

  2. 调用读取某个事件的例程,而此时队列中没有匹配事件

  3. 调用XFlushXSync ()

属性(property)

  • 一个和窗口相联的信息包(用于客户端之间的交流),含有一个字符串名和数字ID(atom)

  • 窗口管理器(windows manager)和客户端的大部分通信通过窗口属性(其余通过事件)

窗口的特性

  • 父窗口——每个窗口在创建时就指定了父窗口(root窗口——无父窗口,Xserver启动时创建,覆盖整个屏幕)

  • 窗口配置(configure)包括:

    1. 不计边框的窗口高度和宽度(以像素计)

    1. 窗口的边框(宽度可变,0代表边框不可见)

    2. 窗口相对的位置(不计边框)

    3. 同一父窗口的窗口间,栈中的顺序

  • 窗口的几何特性——窗口配置的中窗口的高度、宽度和窗口的位置

  • depth & visualdepth——像素值的位数,visual——像素值如何转换成输出颜色

  • 窗口的类别:

    1. 输入输出

    2. 仅输入(透明无边界,不能有输入输出类别的窗口作为子窗口,常用来改变鼠标外形)

  • 窗口的一组属性(Attributes)

    1. 窗口边界和背景使用的颜色或者模式(Pattern)

    2. 改变窗口大小时窗口内其他内容的重定位

    3. 窗口内容何时自动保存?(当窗口被覆盖之后又“曝光”(exposure))

    4. 接收哪些事件,哪些事件不向祖先窗口提交?

    5. 是否允许不通知窗口管理器来显示、移动、改变本窗口大小

    6. 使用哪种颜色表来解释像素值

    7. 鼠标在此窗口中时应该如何显示

窗口的层次

  • 每个屏幕(screen)对应一个root window(撑满整个屏幕)

  • root window的直接子window成为顶级(toplevel)窗口,由窗口管理器所管理

  • toplevel窗口的子窗口可用作实现比如按钮,滚动条之类1

  • 子窗口可以部分或全部置于父窗口之外,但是子窗口只能向其和父窗口交集区域输出/获取输入

  • 子窗口总是在父窗口的上面

窗口的映射

窗口必须被映射——通过 XmapWindow()(或其他相关例程:XMapSubwindows()),窗口符合以下几点才可见:只有keyboardmouse事件传播。事件掩码是每个客户端独有

  1. 该窗口的所有祖先窗口被映射

  2. 不能被其他可见的兄弟窗口或者祖先的兄弟窗口覆盖(通过XCirculateSubwindows(), XConfigureWindow(), XrestackWindows()调整栈中的顺序)

  3. 相应的请求缓冲被清空(到server

  4. 顶级窗口的初次映射是个特例——因为需要窗口管理器“领养"之。更复杂的情形:客户必须等待一个“曝光”事件窗口才可见

图形上下文的三个方面

  1. 位掩码(plane mask)

  2. 剪切掩码(clip mask)

  3. 逻辑函数

可绘对象(drawable: window & pixmap)

三类错误事件(Error Event)

  1. 检查返回值来侦测错误和进行相应处理(修改请求的参数再试),比如检测XOpenDisplay()成功否

  2. 协议错误——编程失误引起,由XErrorHandler处理(例外对于返回状态代码过程的则通过检测状态代码来处理),通过XSetErrorHandler ()添加一个自定义的句柄

  3. 致命系统错误——(X服务器崩溃、网络失败...),由XIOErrorHandler处理,通过XSetIOErrorHandler ()添加一个自定义的句柄

错误事件结构体 (错误发生时刻的相关上下文环境)

错误事件发给XErrorHandler处理

int type;

Display *display; /* 发生错误的Display */

XID resourceid; /* 资源ID */

unsigned long serial; /* 失败请求的序列号 */

unsigned char error_code; /* 错误代码 */

unsigned char request_code; /* 失败请求的主操作码 */

unsigned char minor_code; /* 指示扩展,若此请求不使用扩展为0 */

其中error_code可由XGetErrorText()XGetErrorDatabaseText()来解读

GUI设计中的三要素

  1. 窗口的层次关系

  2. 选择需要处理的事件

  3. 事件处理(主要是简单的键盘和鼠标事件)

编写一个X程序的四个步骤

  1. 连接到X服务端(即选择显示(display),因为一个X服务端对应一个显示(display))

  2. 客户端之间的通信(和窗口管理器协作)

  3. 处理曝光事件

  4. 处理改变窗口大小事件(改变给定窗口中子窗口及其他图形内容的尺寸和位置)

创建X程序的一般过程

  1. X server创建一个连接,失败退出

  2. 获取物理屏幕的信息,从而计算窗口大小

  3. 创建窗口

  4. 设置合适的属性(properties),从而和窗口管理器通信

  5. 选择关注的事件

  6. 载入字体,用来打印文字

  7. 创建一个图形上下文(GC),来控制绘图请求的行为

  8. 通过映射窗口来显示之

  9. 循环处理事件

  10. 响应曝光(expose)事件,调用相应例程来绘制图形和文字

  11. 获取配置更改通知(ConfigureNotify)事件,其新的大小信息在事件结构体中

  12. 响应其他事件

  13. 退出——释放相关资源,关闭显示并退出

  14. 其他需要的步骤:

    1. 从配置文件/命令行参数获取用户自定义信息

    2. 处理颜色

一个简单的X程序(模板)

//Xlib include files

#include <X11/Xlib.h>

#include <X11/Xutil.h>

#include <X11/Xos.h>

#include <X11/Xatom.h>

#include <stdio.h>

//Bitmap data for icon

#include "bitmaps/icon_bitmap"

#define BITMAPDEPTH 1


//布尔值,window足够大么->否则显示一条合适的信息

#define TOO_SMALL 0

#define BIG_ENOUGH 1


//这两个参数是几乎所有Xlib例程都需要的,故申明为全局比较简单;如果有其他源文件,这两个变量需要申明为'extern'

Display *display; /* 关于服务端及其屏幕的信息的结构体,仅由XOpenDisplay ()填充 */

int screen_num;


static char *progname;

void load_font (XFontStruct **);

/* ... */

void main (int argc, char **argv)

{

Window win; /* Window ID,其值由XCreateWindow ()或者XCreateSimpleWindow ()例程返回 */

unsigned int width, height; /* Window size */

int x = 0, y = 0; /* Window position */

unsigned int border_width = 4; /* Border four pixels wide */

unsigned int display_width, display_height;

Pixmap icon_pixmap; /* 位图资源ID */

char *display_name = NULL; /* Server to connect to,NULL则由环境变量DISPLAY指示,格式:host:server.screen */

Screen *screen_ptr;

progname = argv[0];

//连接到服务器

if ((display = XOpenDisplay(display_name)) == NULL) {

(void) fprintf(stderr, "%s: cannot connect to X server %sn",progname, XDisplayName (display_name));

exit( −1 );

}

screen_num = DefaultScreen (display); /* screen_num允许的值0 ~ ScreenCount (display) - 1 */

screen_ptr = DefaultScreenOfDisplay (display);

//获取窗口信息

unsigned int display_width = DisplayWidth (display, screen_num);

unsigned int display_height = DisplayHeight(display, screen_num);

//创建窗口

width = display_width/3, height = display_height/4;

win = XCreateSimpleWindow (display, RootWindow (display, screen_num),

x, y, width, height, border_width,

BlackPixel (display, screen_num) /* border pixel value */,

WhitePixel (display, screen_num) /* background pixel value */);

//窗口栏上的图标

#define icon_bitmap_width 20

#define icon_bitmap_height 20

static char icon_bitmap_bits[] = {

0x60, 0x00, 0x01, 0xb0, 0x00, 0x07, 0x0c, 0x03, 0x00, 0x04, 0x04, 0x00,

0xc2, 0x18, 0x00, 0x03, 0x30, 0x00, 0x01, 0x60, 0x00, 0xf1, 0xdf, 0x00,

0xc1, 0xf0, 0x01, 0x82, 0x01, 0x00, 0x02, 0x03, 0x00, 0x02, 0x0c, 0x00,

0x02, 0x38, 0x00, 0x04, 0x60, 0x00, 0x04, 0xe0, 0x00, 0x04, 0x38, 0x00,

0x84, 0x06, 0x00, 0x14, 0x14, 0x00, 0x0c, 0x34, 0x00, 0x00, 0x00, 0x00};

icon_pixmap = XCreateBitmapFromData (display, win, icon_bitmap_bits,

icon_bitmap_width, icon_bitmap_height);

//和窗口管理器通信

char *window_name = "Basic Window Program";

char *icon_name = "basicwin";

XWMHints *wm_hints;

XClassHint *class_hints;

XTextProperty windowName, iconName;

XSizeHints *size_hints; /* 结构体:告诉window管理器顶级窗口最佳大小及大小增量 */

if (!(size_hints = XAllocSizeHints ())) {

fprintf (stderr, "%s: failure allocating memory", progname);

exit (0);

}

if (!(wm_hints = XAllocWMHints ())) {

fprintf(stderr, "%s: failure allocating memory", progname);

exit(0);

}

if (!(class_hints = XAllocClassHint ())) {

fprintf(stderr, "%s: failure allocating memory", progname);

exit (0);

}

//优先级:user(USPosition | USSize) > 窗口管理器 > 应用程序

size_hints−>flags = PPosition | PSize | PMinSize;

size_hints−>min_width = 300;

size_hints−>min_height = 200;

//设置XTextProperty结构体

if (XStringListToTextProperty (&window_name, 1, &windowName) == 0) {

fprintf (stderr, "%s: structure allocation for windowName failed.n", progname);

exit (-1);

}

if (XStringListToTextProperty (&icon_name, 1, &iconName) == 0) {

fprintf (stderr, "%s: structure allocation for iconName failed.n", progname);

exit (-1);

}

//程序是否在窗口第一次映射时显示一个图标/或者是正常的显示

wm_hints−>initial_state = NormalState;

//程序需要键盘输入么

wm_hints−>input = True;

wm_hints−>icon_pixmap = icon_pixmap;

wm_hints−>flags = StateHint | IconPixmapHint | InputHint;

//以下提供给窗口管理器关于本程序的信息

class_hints−>res_name = progname;

class_hints−>res_class = "Basicwin";

XSetWMProperties(display, win, &windowName, &iconName,

argv, argc, size_hints, wm_hints, class_hints);

//选择需要接受的事件, StructureNotifyMask相当于选择了CirculateNotifyConfigureNotifyDestroyNotifyGravityNotifyMapNotifyReparentNotifyUnmapNotify

XSelectInput (display, win,

ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask);

//创建服务器资源(至此,已经创建了一个windowicon pixmap两个资源

GC gc; /* ID of graphics context */

XFontStruct *font_info; /* Structure containing font information */

/* load_font:载入一个字体资源 */

load_font (&font_info);

/* get_GC:创建一个图形上下完资源(用来绘制字体和图形) */

//映射窗口

XMapWindow(display, win);

//清空请求缓冲->如果需要周期性绘制则需要调用XFlush ()

//建立起事件处理循环

XEvent report; /* Structure for event information */

int window_size = BIG_ENOUGH;

while (1) {

XNextEvent (display, &report);

switch (report.type) {

case Expose:

//绘制窗口优化:如非连续的Expose事件中最后一个,则不处理之/是否确定最小绘制区

if (report.xexpose.count != 0) break;

if (window_size == TOO_SMALL) TooSmall(win, gc, font_info);

else {

place_text(win, gc, font_info, width, height);

place_graphics(win, gc, width, height);

}

break;

case ConfigureNotify:

//处理窗口大小改变事件

width = report.xconfigure.width;

height = report.xconfigure.height;

if ((width < size_hints−>min_width) || (height < size_hints−>min_height))

window_size = TOO_SMALL;

else

window_size = BIG_ENOUGH;

break;

case ButtonPress:

case KeyPress:

//离开程序

XUnloadFont(display, font_info−>fid);

XFreeGC(display, gc);

XCloseDisplay(display);

exit(1);

default:

break;

}

}

}

void load_font (XFontStruct **font_info)

{

char *fontname = "9x15";

if ((*font_info = XLoadQueryFont(display,fontname)) == NULL) {

(void) fprintf( stderr, "Basic: Cannot open 9x15 fontn");

exit( −1 );

}

}

/* ... */

Some Tips

  • X服务器不会自动维护窗口中可见内容,对一个不可见或者没有映射的窗口进行图形操作没有效果。当窗口被覆盖之后曝光,可见内容就会丢弃。通过指定"后台支持"(backing store,开销较大),或者重绘来维持窗口内容。只有keyboardmouse事件传播。事件掩码是每个客户端独有

  • 只有keyboardmouse相关事件(沿着窗口层次向上)传播。事件掩码是每个客户端独有。

Q & A

  • 为什么GUI的基元是窗口?

    为了提高屏幕的利用虑,把屏幕划分为窗口来使用,故而

  • 窗口属于谁?

    窗口是全局的,通过窗口的ID可以引用窗口,故窗口不是特定地属于某个客户端

  • 事件如何被分发处理?

    服务端X发送相关事件给在窗口的事件掩码中有标记的客户端,对键盘和鼠标相关事件可能还要向其父窗口传递

    客户端:通过某ID窗口的事件掩码选择关心的事件,从事件队列中获取事件分发到合适的代码中来处理

    事件的结构体中的两个最主要成员1)事件发生的窗口 2)事件类型

一些详细的规范(PDF下载)

原书 ( The Definitive Guides to the X Window Systems, Vol 1 - Xlib Programming Manual)第三版

Xlib手册 (X Version 11, Release 6.9/7.0)

ICCCM (Inter-Client Communication Conventions Manual) v2.1

X协议 (X Version 11, Release 6.7 DRAFT)

X会话管理库 (X Session Management Library) v1.0

X各个部分简介(PDF下载)

X的未来

XGL

The State of Linux Graphics (Jon Smirl)(PDF)

XGL (PDF)

EGL(位于窗口系统和OpenGL ES/OpenVG等的一层)

  1. 提供创建可供客户APIs绘制和共享的渲染表面(windows,pbuffers,pixmaps)的机制

  2. 为客户端API提供创建和管理图形上下文(Graphics contexts)的方法

  3. 提供同步客户端API和平台原生渲染API的方式

OpenGL家族(www.khronos.org)

GL 3草案

OpenGL ES

OpenVG

硬件加速矢量绘图接口(Flashsvg...)

新闻:OpenVG加速webkit渲染

开源实现:shivaVG

OpenVG vs cairo

  1. OpenVG不支持字体或者文本渲染

  2. OpenVG可以成为cairo的一个后端

  3. OpenVG更像硬件的抽象状态机(更底层)

OpenMAX

  1. 硬件加速的全面的流媒体编码器

  2. 程序的可移植性

开源实现:Bellagio(整合层)

OpenMAX vs gstreamer:

  1. 通过OpenMAX IL(整合)层可以被gstreamer整合

  2. OpenMAXAL(应用)层和gstreamer竞争。

glFX

运行时刻的特效框架。(特效由COLLADA FX格式所描述)

COLLADA

基于XML3D内容交换规范

新的驱动

当前DRM的内存管理 (PDF)

新的内存管理模型

Gallium(镓)3D驱动模型 odp演示文件)

新闻:mesa的采用Gallium驱动模型的cell平台驱动(The Mesa Cell driver is part of the Gallium3D architecture. Tungsten Graphics is leading the project. Two phases are planned. First, to implement the framework for parallel rasterization using the Cell SPEs, including texture mapping. Second, to implement a full-featured OpenGL driver with support for GLSL, etc.

讨论:X真的优秀么?

X作太多工作(人机外设的管理(DISPLAY)、绘制及相应数据管理、事件分发)?

主要是现代的绘制工作已经太复杂了,X继续支持绘制工作和已有的OpenGL之流冗余,而且也做不好。

X并没有很好划分Client-Server

将获取的事件发给客户端处理,有以下几个缺点(看上去有点像AJAX于传统动态网页技术的PK):

  • 受到网络延时的影响,使得程序响应度下降

  • 事件(服务端)和事件处理的句柄(客户端)在X所处的层次上属于紧耦合,当前的划分对性能(包括网络传输)和模块化不利

  • 界面逻辑分布在服务端和客户端之间(某种意义上,不符合界面和程序核分离的原则)

YY(无责任灌水)

X不受欢迎?

当前Linux下的GUI除了编写上的困难(包括跨环境),其他还有一些缺点:

  • 安全性,例如

    • 焦点安全(依赖窗口管理器)(例如在用户输入敏感数据(如密码)时,一个突然弹出的窗口可能获取当前用户的焦点而导致用户敏感数据外泄(如果用户不十分小心)

    • 对事件源和路径的认证和控制(比如区分用户触发的事件和程序制造的事件,从而确保用户界面用户的意图)

    • ...

  • 使用性,例如用户在顶级窗口之间的操作(回想GIMP...

思路:

  • 类基于富功能web浏览器的桌面环境(提供界面服务器而非XServer

  • UI操作的模式(类比启发)

交互系统由硬件——软件——用户构成

硬件(CPU)

用户

执行线(CPU的执行入口,i.e. CPU的核2)

鼠标指针数3

进程

窗口4(工作流程)

当前进程

获得焦点的窗口

进程间的通信

窗口间操作

进程调度

任务栏(或者火狐之类的标签栏)

进程抢占

焦点抢占

缓存机制

历史记录(最近文档)

...

...



2这个核不支持类超线程的技术

4这里的窗口都指通常称的窗口(不严格的说,就是顶级窗口),下同

0 评论: