一、X11基础——介绍
1 基本概念
1.1 X11
-
X Window System
是一套标准,而不是具体的程序或者代码。 -
X11
是这套标准X Window System
的第十一个版本,该版本一直使用至今,较为稳定。这套标准采用的是Client和Server模型,XServer
负责输出(显卡)和获取输入(键盘鼠标)。XClient
就是我们写的程序,这个程序使用libX11.so
库跟XServer
通信,获取服务的输入事件和告知服务器我们要显示的图像。
1.2 Xorg
Xorg
是一个 XServer
,是 X11
的一个最广为使用的实现(同时也指代该实现背后的组织/基金会),“广”到貌似没有其他实现。现在大多时候,X11、Xorg、X Server说的都是一个东西就是Linux桌面的X11后端服务器,也就是Xorg。
1.3 Xlib
-
Xlib
是X Window System
协议的客户端Client
,其功能是与X server沟通。这样的功能可以让程序人员撰写程序时,毋须了解其协议的细节就可以编写X-Client
。 -
Xlib
提供了创建图形用户界面的基本工具,还不够方便,于是就有了QT
和GTK
。用GNOME(GTK)、KDE(QT)是一个桌面环境,可以看作是一个X-Client
,说看作是因为他们还包含很多应用等等。
1.4 Display Manager 显示管理器
提供登录X Window系统的环境,让用户输入用户名/密码,加载用户选择的Window Manager以及语系等数据。例如,gdm3。
显示管理器 display manager (DM)是一个为你的Linux 发行版提供图形登录功能的程序。 它控制用户会话并管理用户认证。 显示管理器会在你输入用户名和密码后,立即启动显示服务器并加载桌面环境。 显示管理器通常是登录界面的代名词。
1.5 Window Manager 窗口管理器
1.6 桌面环境
GNOME是一套桌面环境,包括一系列应用程序。比如显示管理器,窗口管理器,firefox浏览器等都是XClient。
GNOME(KDE、Xfce)是基于Xorg基础之上开发的桌面环境,也就是桌面软件(或者是图形软件)的集合。
1. 补充 Wayland
-
Wayland
是一个通信协议,与X Window System
对等的概念,规定了显示服务器与其客户机之间的通信方式,而使用这个协议的显示服务器称为Wayland Compositor
。它由Kristian Høgsberg于2008年发起,目标是用更简单的现代化视窗系统取代X Window System
。Wayland
协议的参考实现称为Weston
,由Wayland
项目组使用C语言开发。 -
Wayland
与X Window System
的最大不同在于,它规定由客户机自身负责窗口边框和装饰的绘制,并且客户机能够通过EGL以及一些Wayland特定的EGL扩展组件直接在显示存储器中算绘自己的缓冲器。窗口管理器简化成显示管理服务,专门负责算绘那些屏幕上的程序。这比X Window System
中的窗口管理器要更简单、高效。 -
通常
Wayland
说的就是显示服务器,指代Wayland Compositor
。
2 The Client And Server Model Of The X Window System
X窗口系统的开发主要目标是灵活性。思路是,事物的外观是一回事,但事物的工作方式又是另一回事。因此,较低级别提供了绘制窗口、处理用户输入、使用颜色(或黑白屏幕)绘制图形等所需的工具。在这一点上,决定将系统分为两部分。一个决定做什么的客户端,和一个真正在屏幕上绘图并为了将其发送给客户端进行处理而读取用户输入的服务器。
这种模型与我们习惯于处理客户端和服务器时完全相反。在我们的情况下,用户坐在由服务器控制的机器旁边,而客户端可能在远程机器上运行。服务器控制屏幕、鼠标和键盘。客户端可以连接到服务器,要求其绘制一个窗口(或多个窗口),并要求服务器将用户发送到这些窗口的任何输入发送给它。因此,多个客户端可能连接到一个单独的X服务器 - 一个可能正在运行电子邮件软件,一个正在运行WWW浏览器,等等。当用户向某个窗口发送输入时,服务器会将消息发送给控制此窗口的客户端进行处理。客户端决定如何处理此输入,并向服务器发送绘制窗口的请求。
整个会话都是使用X消息协议进行的。此协议最初是在TCP/IP协议套件上进行的,允许客户端在与服务器连接到同一网络的任何机器上运行。后来,X服务器扩展了允许在本地机器上运行的客户端更优化地访问服务器(请注意,X协议消息的大小可能为几百KB),例如使用共享内存,或使用Unix域套接字(在Unix系统上创建两个进程之间的逻辑通道的方法)。
3 GUI programming - the Asynchronous Programming Model
与携带某种串行性质的传统计算机程序不同,GUI程序通常使用异步编程模型,也称为“事件驱动编程”。这意味着该程序大部分时间都处于空闲状态,等待X服务器发送的事件,然后根据这些事件采取行动。事件可能表示“用户在x,y处按下了第一个鼠标按钮”,或者“您控制的窗口需要重新绘制”。为了使程序对用户输入做出响应,以及刷新请求,它需要在相对较短的时间内处理每个事件(例如,作为经验法则,少于200毫秒)。
这也意味着该程序在处理事件时可能不执行可能需要很长时间的操作(例如,打开到某个远程服务器的网络连接,或连接到数据库服务器,或甚至执行长时间的文件复制操作)。相反,它需要以异步方式执行所有这些操作。这可以通过使用各种异步模型来执行较长的操作,或者在不同的进程或线程中执行它们来完成。
因此,GUI程序的外观如下:
- 执行初始化例程。
- 连接到X服务器。
- 执行与X相关的初始化。
- 如果没有完成,则:
- 从X服务器接收下一个事件。
- 处理该事件,可能向X服务器发送各种绘图请求。
- 如果事件是退出消息,则退出循环。
- 关闭到X服务器的连接。
- 执行清理操作。
4 Basic Xlib Notions
为了消除程序实际实现X协议层的需求,创建了一个名为“Xlib”的库。这个库为程序提供了对任何X服务器的非常低级别的访问。由于该协议是标准化的,任何实现的Xlib的客户端都可以与任何X服务器通话。这些天看起来可能很简单,但在使用字符模式终端和专有方法在屏幕上绘制图形的日子里,这看起来像是一个重大突破。实际上,你会注意到关于薄客户机、窗口终端服务器等的大炒作。它们正在实现今天X协议在80年代末启用的功能。另一方面,X宇宙正在迎头赶上关于CUA(公共用户访问,IBM为了指代所有程序使用公共外观和感觉以便为用户提供便利而制定的概念)。没有公共的外观和感觉是X窗口系统的创作者的哲学。显然,它有一些明显的缺点。
4.1 The X Display
使用Xlib的主要概念是X显示。这是一个表示我们与给定X服务器打开的连接的结构。它隐藏了来自服务器的消息队列,以及我们的客户端打算发送给服务器的挂起请求的队列。在Xlib中,此结构命名为“显示”。当我们打开到X服务器的连接时,库会返回指向这样一个结构的指针。稍后,我们将此指针提供给应该将消息发送到X服务器或从此服务器接收消息的任何Xlib函数。
4.2 The GC - Graphics Context
当我们执行各种绘图操作(图形,文本等)时,我们可以指定各种选项来控制如何绘制数据 - 使用什么前景和背景颜色,如何连接线条边缘,绘制某些文本时使用什么字体等)。为了避免需要向每个绘图函数提供大量的参数,使用了图形上下文结构,类型为’GC’。我们在此结构中设置各种绘图选项,然后将指向此结构的指针传递给任何绘图例程。这很方便,因为我们经常需要使用相同的选项进行几次绘图请求。因此,我们会初始化一个图形上下文,设置所需的选项,并将此GC结构传递给所有绘图函数。
4.3 Object Handles
当X服务器为我们创建各种对象时,如窗口、绘图区域和光标,相关函数会返回一个句柄。这是一种在X服务器的内存中而不是我们的应用程序的内存中的对象的标识符。我们可以通过向各种Xlib函数提供此句柄来后来操纵此对象。服务器保持这些句柄与它管理的实际对象之间的映射。Xlib为这些对象(窗口,光标,颜色映射等)提供各种类型定义,它们最终都映射到简单的整数。出于可移植性的考虑,我们仍然应该使用这些类型名称来定义保存句柄的变量。
4.4 Memory Allocation For Xlib Structures
Xlib的接口中使用了各种结构类型。其中一些直接由用户分配。其他的是使用特定的Xlib函数分配的。这允许库适当地初始化这些结构。这很方便,因为这些结构往往包含很多变量,使得程序员初始化起来相当繁琐。记住 - Xlib试图尽可能灵活,这意味着它也可以尽可能复杂。有默认值将使初学者X程序员能够使用该库,而不干扰更有经验的程序员使用这些成千上万的选项。
至于释放内存,这是以两种方式之一完成的。在我们分配内存的情况下 - 我们以相同的方式释放它(即使用free()释放使用malloc()分配的内存)。如果我们使用某个Xlib函数分配它,或者我们使用某个返回动态分配内存的Xlib查询方法 - 我们将使用XFree()函数释放此内存块。
4.5 Events
用于传递从X服务器接收的事件的结构的类型为“XEvent”。Xlib支持大量的事件类型。XEvent结构包含收到的事件类型,以及与事件相关的数据(例如生成事件的屏幕位置、与事件相关的鼠标按钮、与“重绘”事件相关的屏幕区域等)。读取事件的数据的方式取决于事件类型。因此,XEvent结构包含所有可能的事件类型的C语言联合(如果你不确定什么是C联合,现在是时候检查你首选的C语言手册了…)。因此,我们可以有XExpose事件、XButton事件、XMotion事件等。
5 Compiling Xlib-Based Programs
编译基于Xlib的程序需要将它们与Xlib库链接。这是使用如下的编译命令完成的:
cc prog.c -o prog -lX11
或者
cc prog.c -o prog `pkg-config --cflags --libs x11`
6 Opening And Closing The Connection To An X Server
一个X程序首先需要打开与X服务器的连接。当我们这样做时,我们需要指定运行X服务器的主机的地址,以及显示编号。X窗口系统可以支持连接到同一台机器的多个显示器。然而,通常只有一个这样的显示器,即显示器编号“0”。如果我们想要连接到本地显示器(即我们的客户端程序运行的机器的显示器),我们可以将显示器指定为“:0”。为了连接到地址为“simey”的机器的第一个显示器,我们可以使用地址“simey:0”。以下是如何打开连接:
#include <X11/Xlib.h> /* defines common Xlib functions and structs. */
.
.
/* this variable will contain the pointer to the Display structure */
/* returned when opening a connection. */
Display* display;
/* open the connection to the display "simey:0". */
display = XOpenDisplay("simey:0");
if (display == NULL) {
fprintf(stderr, "Cannot connect to X server %s\n", "simey:0");
exit (-1);
}
注意,X程序通常会检查环境变量’DISPLAY’是否已定义,如果已定义,则使用其内容作为XOpenDisplay()函数的参数。 当程序完成其业务并需要关闭与X服务器的连接时,它会执行以下操作:
XCloseDisplay(display);
这会导致由程序创建的所有窗口(如果还有的话)被服务器自动关闭,以及存储在服务器上代表客户端的任何资源被释放。请注意,这并不会导致我们的客户端程序终止 - 我们可以使用正常的exit()函数来实现这一点。
7 Checking Basic Information About A Display
在我们打开与X服务器的连接之后,我们应该检查关于它的一些基本信息:它有哪些屏幕,屏幕的尺寸(宽度和高度)是多少,它支持多少颜色(黑白?灰度?256种颜色?更多?)等等。我们将展示一个进行这些检查的代码片段,并在注释中解释每个函数的使用。我们假设’display’是指向’Display’结构的指针,该结构由之前对XOpenDisplay()的调用返回。
/* this variable will be used to store the "default" screen of the */
/* X server. usually an X server has only one screen, so we're only */
/* interested in that screen. */
int screen_num;
/* these variables will store the size of the screen, in pixels. */
int screen_width;
int screen_height;
/* this variable will be used to store the ID of the root window of our */
/* screen. Each screen always has a root window that covers the whole */
/* screen, and always exists. */
Window root_window;
/* these variables will be used to store the IDs of the black and white */
/* colors of the given screen. More on this will be explained later. */
unsigned long white_pixel;
unsigned long black_pixel;
/* check the number of the default screen for our X server. */
screen_num = DefaultScreen(display);
/* find the width of the default screen of our X server, in pixels. */
screen_width = DisplayWidth(display, screen_num);
/* find the height of the default screen of our X server, in pixels. */
screen_height = DisplayHeight(display, screen_num);
/* find the ID of the root window of the screen. */
root_window = RootWindow(display, screen_num);
/* find the value of a white pixel on this screen. */
white_pixel = WhitePixel(display, screen_num);
/* find the value of a black pixel on this screen. */
black_pixel = BlackPixel(display, screen_num);
还有很多其它的宏来帮助我们获取显示的信息,你可以在Xlib里的参考里找到。另外还有很多相当的函数可以完成相同的工作。