文件系统一般有两种锁,共享锁及排它锁,也可被称为读锁和写锁。
文件系统锁的特点:
一个文件打开的时候只能拥有一把锁,就是说在同时,不能给一个文件同时分配两把以上的锁。
读写已被上锁的文件的用户可以持有这把锁,即持有这把锁的用户可以对该文件进行相应的操作,如读或写。用户可以申请持有某个文件锁,如果文件开始无锁,申请持有锁之前先由系统为该文件创建了一把锁,然后该申请者持有它。
持有锁的规则:如果这个文件已拥有一个读(共享)锁,其它用户不能为该文件分配排它锁或只读锁,但可以持有这把锁,也就是说其它用户可以读文件,但只要该文件被锁住,就没有用户可以对其进行写入。如果该文件已有一把排它锁且已为某用户持有,则没有任何用户可以再持有这把锁,除非持有者解锁。
有一个重要的概念要记住:对文件的操作本身与锁其实没有什么关系,无论文件是否被上锁,用户都可以随意对文件进行正常情况下的任何操作,但操作系统会检查锁,针对不同的情况给予不同的处理。比如说在无锁的情况下,任何人可以同时对某文件进行任意的读写,当然这样很有可能读写的内容会出现错误——注意只是内容出错,操作并不会出错。加锁后,某些操作在某些情况下会被拒绝。文件锁的作用并不是保护文件及数据本身,而是保证数据的同步性,因此文件锁只对持有锁的用户才是真正有效的,也只有所有用户都使用同一种完全相同的方式利用文件锁的限制对文件进行操作,文件锁才能对所有用户有效,否则,只要有一个例外,整个文件锁的功能就会被破坏。比如,所有人都遵循的开文件,加锁,操作读写,解锁,关闭文件的步骤的话,所有的人操作都不会出现问题,因为基于文件锁的分配及持有原则,文件中的数据的更新是作为原子操作存在的,是不可分的,因此也是同步的,安全的。但假如某个人不是采取此步骤,那么他在读写时就会出现问题,不是读不准就是写不进等等。
基于以上原理,对读数据是否锁定这点就值得说说。一般来说,写数据的时候排它锁定是唯一的操作,它这时保证写到文件中的数据是正确的,文件被锁时,其它用户无法得到该锁,因此无权做任何操作。在读的时候,要视具体情况而定,大多数情况下,如果不需要特别精确或是敏感的数据,无需锁定,因为锁定要花时间和资源,一个人申请持有锁花不了时间,人一多就有问题了,最主要的是,如果该文件需要被更新的话,假如被上了只读锁,则写入无法进行,因为那些想写入的用户将得不到排它锁,如果同时申请持有只读锁的人过多的话,排它锁就有可能一直申请不到,这样表现就是文件可能很长时间内无法被写入,显得很慢。一般来说,写文件的机会相对较少,也更重要,因此主要做好排它锁定,只读锁在多数情况下并无必要。那么只读锁用在何处呢?只读锁其实只对用户本身有用,只读锁保证用户读到的数据是确实从文件中读到的真实数据,而不是被称为“dirty”的脏数据。其实,这个还是针对那些不用锁的其它用户对文件的误操作,假如文件上锁,其它用户不一定非要通过锁对文件进行读写,如果他是直接读写的话,对上了锁的文件操作不一定有效,持有读锁的用户可以肯定在他读数据的时候读出来的是从真实的文件中得到的,而不是同时已被覆盖掉的数据。
因此,在写入的时候上排它锁应该是天经地义的,可以保证这时数据的不会出错。如果你不申请共享锁,可能读出的数据有错误,但对文件本身没有任何影响,影响只是对用户的,申请共享锁后读出的数据肯定是当时读的时候文件中的真实数据,如果不是为了保证数据的精确性,共享锁可以不加,充其量就是重新读一次,如果你读它是为了写入,不如直接加排它锁,没有必要用共享锁。
还有一点要强调的是:文件锁只对使用它的用户,而且是按规则使用它的用户才有效,否则,你用你的,我用我的,有的用,有的不用,还是会乱套的,错误还是会出现的,对同一个文件,只有大家用同一个规则用文件锁,才能保证每个用户在对该文件进行共享操作的时候不会出现读写错误。
以上是就本人知识说的一点原理,可能有些错漏,如有需要更正的,还请多指教。
如今在Internet上,传统基于字符界面的应用逐渐被能够浏览图象信息的WWW(World Wide Web)方式所取代。WWW尽管漂亮,但是也带来了一个问题:图象信息的数据量太大了,本来就已经非常紧张的网络带宽变得更加不堪重负,使得World Wide Web变成了World Wide Wait。
总之,大数据量的图象信息会给存储器的存储容量,通信干线信道的带宽,以及计算机的处理速度增加极大的压力。单纯靠增加存储器容量,提高信道带宽以及计算机的处理速度等方法来解决这个问题是不现实的,这时就要考虑压缩。压缩的理论基础是信息论。从信息论的角度来看,压缩就是去掉信息中的冗余,即保留不确定的信息,去掉确定的信息(可推知的),也就是用一种更接近信息本质的描述来代替原有冗余的描述。这个本质的东西就是信息量(即不确定因素)。
压缩可分为两大类,第一类压缩过程是可逆的,也就是说,从压缩后的图象能够完全恢复出原来的图象,信息没有任何丢失,称为无损压缩;第二类压缩过程是不可逆的,无法完全恢复出原图象,信息有一定的丢失,成为有损压缩。选择哪一类压缩,要折中考虑,尽管我们希望能够无损压缩,但是通常有损压缩的压缩比(即原图象占的字节数与压缩后图象占的字节数之比,压缩比越大,说明压缩效率越高)比无损压缩的高。
图象压缩一般是通过改变图象的表示方式来达到,因此压缩和编码是分不开的。图象压缩的主要应用是图象信息的传输和存储,可广泛地应用于广播电视,电视会议,计算机通讯,传真,多媒体系统,医学图象,卫星图象等领域。
压缩编码的方法有很多,主要分成以下4大类:1.像素编码;2.预测编码;3.变换编码;4.其它方法。
所谓像素编码是指,编码时对每个像素单独处理,不考虑像素之间的相关性。在像素编码中常用的几种方法有:1.脉冲编码调制(Pulse Code Modulation,PCM);2.熵编码(Entropy Coding);3.行程编码(Run Length Coding);4.位平面编码(Bit Plane Coding)。这里面,我们要介绍的是熵编码中的哈夫曼(Huffman)编码,行程编码(以读取.PCX文件为例)。
所谓预测编码是指,去掉相邻像素之间的相关性和冗余性,只对新的信息进行编码。举个简单的例子,因为像素的灰度是连续的,所以在一片区域中,相邻像素之间灰度值的差别可能很小。如果我们只记录第一个像素的灰度,其它像素的灰度都用它与前一个像素灰度之差来表示,就能起到压缩的目的。如248,2,1,0,1,3,实际上这6个像素的灰度是248,250,251,251,252,255。表示250需要8个比特,而表示2只需要两个比特,这样就实现了压缩。
常用的预测编码有Δ调制(Delta Modulation,简称DM);微分预测编码(Differential Pulse Code Modulation,DPCM),具体的细节,我们就不详述了。
所谓变换编码是指,将给定的图象变换到另一个数据域(如频域)上,使得大量的信息能用较少的数据来表示,从而达到压缩的目的。变换编码有很多,如1.离散傅立叶变换(Discrete Fourier Transform,DFT);2.离散余弦变换(Discrete Cosine Transform,DCT);3.离散哈达玛变换(Discrete Hadamard Transform,DHT)。
其它的编码方法也有很多,如混合编码(Hybird Coding),矢量量化(Vector Quantize,VQ),LZW算法。在这里,我们只介绍LZW算法的大体思想。
值得注意的是,近些年来出现了很多新的压缩编码方法,如使用人工神经元网络(Artificial Neural Network,ANN)的压缩编码算法;分形(Fractl);小波(Wavelet);基于对象(Object -Based)的压缩编码算法;基于模型(Model -Based)的压缩编码算法(应 用在MPEG4及未来的视频压缩编码标准中)。这些都超出了本讲座的范围。
本讲的最后,我们将以JPEG压缩编码标准为例,看看上面的几种编码方法在实际的压缩编码中是怎样应用的。
1. 哈夫曼(Huffman)编码
Huffman编码是一种常用的压缩编码方法,是Huffman于1952年为压缩文本文件建立的。它的基本原理是频繁使用的数据用较短的代码代替,较少使用的数据用较长的代码代替,每个数据的代码各不相同。这些代码都是二进制码,且码的长度是可变的。举个例子:假设一个文件中出现了8种符号S0,S1,S2,S3,S4,S5,S6,S7,那么每种符号要编码,至少需要3比特,假设编码成000,001,010,011,100,101,110,111(称做码字)。那么符号序列S0S1S7S0S1S6S2S2S3S4S5S0S0S1编码后变成000001111000001110010010011100101000000001,共用了42比特。我们发现S0,S1,S2这三个符号出现的频率比较大,其它符号出现的频率比较小,如果我们采用一种编码方案使得S0,S1,S2的码字短,其它符号的码字长,这样就能够减少占用的比特数。
例如,我们采用这样的编码方案:S0到S7的码字分别01,11,101,0000,0001,0010,0011,100,那么上述符号序列变成011110001110011101101000000010010010111,共用了39比特,尽管有些码字如S3,S4,S5,S6变长了(由3位变成4位),但使用频繁的几个码字如S0,S1变短了,所以实现了压缩。
上述的编码是如何得到的呢?随意乱写是不行的。编码必须保证不能出现一个码字和另一个的前几位相同的情况,比如说,如果S0的码字为01,S2的码字为011,那么当序列中出现011时,你不知道是S0的码字后面跟了个1,还是完整的一个S2的码字。我们给出的编码能够保证这一点。
下面给出具体的Huffman编码算法。
1.首先统计出每个符号出现的频率,上例S0到S7的出现频率分别为4/14,3/14,2/14,1/14,1/14,1/14,1/14,1/14。
2.从左到右把上述频率按从小到大的顺序排列。
3.每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较。
4.重复3,直到最后得到和为1的根节点。
5.将形成的二叉树的左节点标0,右节点标1。把从最上面的根节点到最下面的叶子节点途中遇到的0,1序列串起来,就得到了各个符号的编码。
上面的例子用Huffman编码的过程如下图所示,其中圆圈中的数字是新节点产生的顺序。可见,我们上面给出的编码就是这么得到的。
图1. Huffman编码的示意图
产生霍夫曼编码需要对原始数据扫描两遍,第一遍扫描要精确地统计出原始数据中,每个值出现的频率,第二遍是建立霍夫曼树并进行编码,由于需要建立二叉树并遍历二叉树生成编码,因此数据压缩和还原速度都较慢,但简单有效,因而得到广泛的应用。
2.行程编码(Run Length Coding)
行程编码的原理也很简单:将一行中颜色值相同的相邻像素用一个计数值和该颜色值来代替。例如:aaabccccccddeee可以表示为3a1b6c2d3e。如果一幅图象是由很多块颜色相同的大面积区域组成,那么采用行程编码的压缩效率是惊人的。然而,该算法也导致了一个致命弱点,如果图象中每两个相邻点的颜色都不同,用这种算法不但不能压缩,反而数据量增加一倍。所以现在单纯采用行程编码的压缩算法用得并不多,PCX文件算是其中的一种.
PCX文件最早是PC Paintbrush软件所采用的一种文件格式,由于压缩比不高,现在用的并不是很多了。它也是由头信息,调色板,实际的图象数据三个部分组成。其中头信息的结构为:
typedef struct{
char manufacturer;
char version;
char encoding;
char bits_per_pixel;
WORD xmin,ymin;
WORD xmax,ymax;
WORD hres;
WORD vres;
char palette[48];
char reserved;
char colour_planes;
WORD bytes_per_line;
WORD palette_type;
char filler[58];
} PCXHEAD;
其中值得注意的是以下几个数据:manufacturer为PCX文件的标识,必须为0x0a;xmin为最小的x坐标,xmax最大的x坐标,所以图象的宽度为xmax-xmin+1,同样图象的高度为ymax-yin+1;bytes_per_line为每个编码行所占的字节数,过一会儿详细介绍。
PCX的调色板在文件的最后。以256色PCX文件为例,倒数第769个字节为颜色数的标识,256时该字节必须为12,剩下的768(256*3)为调色板的RGB值。
为了讲起来方便,下面我们针对256色PCX文件,介绍一下它的解码过程。编码是解码的逆过程,有兴趣的读者可以试着自己来完成。
解码是以行为单位的,该行所占的字节数由bytes_per_line给定。为此,我们开一个大小为bytes_per_line的解码缓冲区。一开始,将缓冲区的所有内容清零。从文件中读出一个字节C,若C>0xc0,说明是行程(Run Length)信息,即C的低6位表示后面连续的字节个数(所以最多63个连续颜色相同的像素,若还有颜色相同的像素,将在下一个行程处理),文件的下一个字节就是实际的图象数据(即该颜色在调色板中的索引值);若C<0xc0,则表示C是实际的图象数据。如此反复,直到这bytes_per_line个字节处理完,这一行的解码完成。PCX就是有若干个这样的解码行组成。
实现256色PCX文件解码的源程序
3. LZW算法的大体思想
LZW是一种比较复杂的压缩算法,其压缩效率也比较高。我们在这里只介绍一下它的基本原理:LZW把每一个第一次出现的字符串用一个数值来编码,在还原程序中再将这个数值还成原来的字符串。例如:用数值0x100代替字符串"abccddeee",每当出现该字符串时,都用0x100代替,这样就起到了压缩的作用。
至于0x100与字符串的对应关系则是在压缩过程中动态生成的,而且这种对应关系隐含在压缩数据中,随着解压缩的进行,这张编码表会从压缩数据中逐步得到恢复,后面的压缩数据再根据前面数据产生的对应关系产生更多的对应关系,直到压缩文件结束为止。LZW是无损的。GIF文件采用了这种压缩算法。
要注意的是,LZW算法由Unisys公司在美国申请了专利,要使用它首先要获得该公司的认可。
4.JPEG压缩编码标准
JPEG是联合图象专家组(Joint Picture Expert Group)的英文缩写,是国际标准化组织(ISO)和CCITT联合制定的静态图象的压缩编码标准。和相同图象质量的其它常用文件格式(如GIF,TIFF,PCX)相比,JPEG是目前静态图象中压缩比最高的。我们给出具体的数据来对比一下。例图采用Windows95目录下的Clouds.bmp,原图大小为640*480,256色。用工具SEA(version1.3)将其分别转成24位色BMP,24位色JPEG,GIF(只能转成256色)压缩格式,24位色TIFF压缩格式,24位色TGA压缩格式,得到的文件大小(以字节为单位)分别为:921,654;17,707;177,152;923,044;768,136。可见JPEG比其它几种压缩比要高得多,而图象质量都差不多(JPEG处理的颜色只有真彩和灰度图)。 正是由于其高压缩比,使得JPEG被广泛地应用于多媒体和网络程序中,例如HTML语法中选用的图象格式之一就是JPEG(另一种是GIF),这是显然的,因为网络的带宽是非常宝贵的,选用一种高压缩比的文件格式是十分必要的。
JPEG有几种模式,其中最常用的是基于DCT变换的顺序型模式,又称为基本系统(Baseline),以下都是针对这种格式进行讨论。
JPEG的压缩原理
JPEG的压缩原理其实上面介绍的那些原理的综合,博采众家之长,这也正是JPEG有高压缩比的原因。其编码器的流程为
图3. 编码器流程
解码器基本上为上述过程的逆过程:
图4. 解码器流程
8*8的图象经过DCT变换后,其低频分量都集中在左上角,高频分量分布在右下角(DCT变换实际上是空间域的低通滤波器)。由于该低频分量包含了图象的主要信息(如亮度),而高频与之相比,就不那么重要了,所以我们可以忽略高频分量,从而达到压缩的目的。如何将高频分量去掉,这就要用到量化,它是产生信息损失的根源。这里的量化操作,就是将某一个值除以量化表中对应的值。由于量化表左上角的值较小,右上角的值较大,这样就起到了保持低频分量,抑制高频分量的目的。
JPEG使用的颜色是YUV格式。我们提到过,Y分量代表了亮度信息,UV分量代表了色差信息。相比而言,Y分量更重要一些。我们可以对Y采用细量化,对UV采用粗量化,可进一步提高压缩比。所以上面所说的量化表通常有两张,一张是针对Y的;一张是针对UV的。
上面讲了,经过DCT变换后,低频分量集中在左上角,其中F(0,0)(即第一行第一列元素)代表了直流(DC)系数,即8*8子块的平均值,要对它单独编码。由于两个相邻的8*8子块的DC系数相差很小,所以对它们采用差分编码DPCM,可以提高压缩比,也就是说对相邻的子块DC系数的差值进行编码。8*8的其它63个元素是交流(AC)系数,采用行程编码。这里出现一个问题:这63个系数应该按照怎么样的顺序排列? 为了保证低频分量先出现,高频分量后出现,以增加行程中连续"0"的个数,这63个元素采用了"之"字型(Zig-Zag)的排列方法,如下图所示:
图5. Zig-Zag
这63个AC系数行程编码的码字用两个字节表示,如下图
图6. 行程编码
上面,我们得到了DC码字和 AC行程码字。为了进一步提高压缩比,需要对其再进行熵编码,这里选用Huffman编码,分成两步:
(1) 熵编码的中间格式表示:
对于AC系数,有两个符号。符号1:行程和尺寸,即上面的(RunLength,Size)。(0,0)和(15,0)是两个比较特殊的情况。(0,0)表示块结束标志(EOB),(15,0)表示ZRL,当行程长度超过15时,用增加ZRL的个数来解决,所以最多有三个ZRL(3*16+15=63)。符号2为幅度值(Amplitude)。
对于DC系数,也有两个符号。符号1:尺寸(Size);符号2为幅度值(Amplitude)。
(2) 熵编码:
对于AC系数,符号1和符号2分别进行编码。零行程长度超过15个时,有一个符(15,0),块结束时只有一个符号(0,0)。 对符号1进行Hufffman编码(亮度,色差的Huffman码表不同)。对符号2:进行变长整数VLI编码,举例来说:Size=6时,Amplitude的范围是-63~-32,以及32~63,对绝对值相同,符号相反的码字之间为反码关系。所以AC系数为32的码字为100000,33的码字为100001,-32的码字为011111,-33的码字为011110。符号2的码字紧接于符号1的码字之后。
对于DC系数,Y和UV的Huffman码表也不同。掉了这么半天的书包,你可能已经晕了,呵呵。举个例子来说明上述过程就容易明白了。
下面为8*8的亮度(Y)图象子块经过量化后的系数。
15 0 -1 0 0 0 0 0
-2 -1 0 0 0 0 0 0
-1 -1 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
可见量化后只有左上角的几个点(低频分量)不为零,这样采用行程编码就很有效。
第一步,熵编码的中间格式表示:先看DC系数。假设前一个8*8子块DC系数的量化值为12,则本块DC系数与它的差为3,根据下表
Size Amplitude
0 0
1 -1,1
2 -3,-2,2,3
3 -7~-4,4~7
4 -15~-8,8~15
5 -31~-16,16~31
6 -63~-32,32~63
7 -127~-64,64~127
8 -255~-128,128~255
9 -511~-256,256~511
10 -1023~512,512~1023
11 -2047~-1024,1024~2047
查表得Size=2,Amplitude=3,所以DC中间格式为(2)(3).AC系数接着被编码,经过Zig-Zag扫描后,遇到的第一个非零系数为-2,其中遇到零的个数为1(即RunLength),根据下面这张AC系数表
Size Amplitude
1 -1,1
2 -3,-2,2,3
3 -7~-4,4~7
4 -15~-8,8~15
5 -31~-16,16~31
6 -63~-32,32~63
7 -127~-64,64~127
8 -255~-128,128~255
9 -511~-256,256~511
10 -1023~512,512~1023
查表得Size=2。所以RunLength=1,Size=2,Amplitude=3,所以AC中间格式为(1,2)(-2)。
其余的点类似,可以求得这个8*8子块熵编码的中间格式为
(DC)(2)(3),(1,2)(-2),(0,1)(-1),(0,1)(-1),(0,1)(-1),(2,1)(-1),(EOB)(0,0)
第二步:熵编码
对于(2)(3):2查DC亮度Huffman表得到11,3经过VLI编码为011
对于(1,2)(-2):(1,2)查AC亮度Huffman表得到11011,-2是2的反码,为01
对于(0,1)(-1):(0,1)查AC亮度Huffman表得到00,-1是1的反码,为0
……
最后这一8*8子块亮度信息压缩后的数据流为11011,1101101,000,000,000,111000,1010。总共31比特,其压缩比是64*8/31=16.5,大约每个像素用半个比特。
可以想见,压缩比和图象质量是呈反比的,以下是压缩效率与图象质量之间的大致关系,可以根据你的需要,选择合适 的压缩比。
压缩效率(单位:bits/pixel) 图象质量
0.25~0.50中~好,可满足某些应用
0.50~0.75好~很好,满足多数应用
0.75~1.5 极好,满足大多数应用
1.5~2.0与原始图象几乎一样
以上我们介绍了JPEG压缩的原理,其中DC系数使用了预测编码DPCM,AC系数使用了变换编码DCT,二者都使用了熵编码Huffman,可见几乎所有传统的压缩方法在这里都用到了。这几种方法的结合正是产生JPEG高压缩比的原因。顺便说一下,该标准是JPEG小组从很多种不同中方案中比较测试得到的,并非空穴来风。
上面介绍了JPEG压缩的基本原理,下面介绍一下JPEG的文件格式。
JPEG的文件格式
JPEG文件大体上可以分成以下两个部分:标记码(Tag)加压缩数据。先介绍标记码部分。
标记码部分给出了JPEG图象的所有信息(有点类似于BMP中的头信息,但要复杂的多),如图象的宽,高,Huffman表,量化表等等。标记码有很多,但绝大多数的JPEG文件只包含几种。标记码的结构为:
SOI
DQT
DRI
SOF0
DHT
SOS
…
EOI
标记码由两个字节组成,高字节为0XFF,每个标记码之前可以填上个数不限的填充字节0XFF。
下面介绍一些常用的标记码的结构及其含义。
SOI(Start of Image)
标记结构 字节数
0XFF1
0XD81
可作为JPEG格式的判据(JFIF还需要APP0的配合)
APP0(Application)
标记结构 字节数 意义
0XFF1
0XE01
Lp2 APP0标记码长度,不包括前两个字节0XFF,0XE0
Identifier5JFIF识别码 0X4A,0X46,0X49,0X46,0X00
Version 2JFIF版本号 可为0X0101或者0X0102
Units 1单位,等于零时表示未指定,为1表示英寸,为2表示厘米
Xdensity2水平分辨率
Ydensity2竖直分辨率
Xthumbnail1水平点数
Ythumbnail1竖直点数
RGB03RGB的值
RGB13RGB的值
…
RGBn 3 RGB的值,n=Xthumbnail*Ythumbnail
APP0是JPEG保留给Application所使用的标记码,而JFIF将文件的相关信息定义在此标记中。
DQT(Define Quantization Table)
标记结构 字节数 意义
0XFF1
0XDB1
Lq2DQT标记码长度,不包括前两个字节0XFF,0XDB
(Pq,Tq)1 高四位Pq为量化表的数据精确度,Pq=0时,Q0~Qn的值为8位,Pq=1时,Qt的值为16位,Tq表示量化表的编号,为0~3。在基本系统中,Pq=0,Tq=0~1,也就是说最多有两个量化表。
Q01或2 量化表的值,Pq=0时,为一个字节,Pq=1时,为两个字节
Q11或2 量化表的值,Pq=0时,为一个字节,Pq=1时,为两个字节
…
Qn1或2 量化表的值,Pq=0时,为一个字节,Pq=1时,为两个字节n的值为0~63,表示量化表中64个值(之字形排列)
DRI(Define Restart Interval)
此标记需要用到最小编码单元(MCU,Minimum Coding Unit)的概念。前面提到,Y分量数据重要,UV分量的数据相对不重要,所以可以只取UV的一部分,以增加压缩比。目前支持JPEG格式的软件通常提供两种取样方式YUV411和YUV422,其含义是YUV三个分量的数据取样比例。举例来说,如果Y取四个数据单元,即水平取样因子Hy乘以垂直取样因子Vy的值为4,而U和V各取一个数据单元,即Hu*Vu=1,Hv*Vv=1。那么这种部分取样就称为YUV411。如下图所示:
图7. YUV411的示意图
易知YUV411有50%的压缩比(原来有12个数据单元,现在有6个数据单元),YUV422
有33%的压缩比(原来有12个数据单元,现在有8个数据单元)。 那么你可能会想,YUV911,YUV1611压缩比不是更高嘛?但是要考虑到图象质量的因素。所以JPEG标准规定了最小编码单元MCU,要求Hy*Vy+Hu*Vu+Hv*Vv≤10。
MCU中块的排列方式与H,V的值有密切关系。如以下几幅图所示:
图8. YUV111的排列顺序
图9. YUV211的排列顺序
图10. YUV411的排列顺序
标记结构 字节数 意义
0XFF1
0XDD1
Lr2DRI标记码长度,不包括前两个字节0XFF,0XDD
Ri2重入间隔的MCU个数,Ri必须是一MCU行中MCU个数的整数,最后一个零头不一定刚好是Ri个MCU。每个重入间隔各自独立编码。
SOF(Start of Frame) 在基本系统中,只处理SOF0
标记结构 字节数 意义
0XFF1
0XC01
Lf2SOF标记码长度,不包括前两个字节0XFF,0XC0
P1 基本系统中,为0X08
Y2 图象高度
X2 图象宽度
Nf1Frame中的成分个数,一般为1或3,1代表灰度图,3代表真彩图
C11成分编号1
(H1,V1)1 第一个水平和垂直采样因子
Tq11该量化表编号
C21成分编号2
(H2,V2)1第二个水平和垂直采样因子
Tq21该量化表编号
…
Cn1 成分编号n
(Hn,Vn)1第n个水平和垂直采样因子
Tqn1该量化表编号
DHT(Define Huffman Table)
标记结构 字节数 意义
0XFF1
0XC41
Lh2DHT标记码长度,不包括前两个字节0XFF,0XC4
(Tc,Th)1
L11
L21
…
L161
V11
V21
…
Vt1
Tc为高4位,Th为低4位。在基本系统中,Tc为0或1,为0时,指DC所用的Huffman表,为1时,指AC所用的Huffman表。Th表示Huffman表的编号,在基本系统中,其值为0或1。
所以,在基本系统中,最多有4个Huffman表,如下所示:
Tc Th Huffman表编号(2*Tc+Th)
0 0 0
0 1 1
1 0 2
1 1 3
Ln表示每个n比特的Huffman码字的个数,n=1~16
Vt表示每个Huffman码字所对应的值,也就是我们前面所讲的符号1,对DC来说该值为(Size),对AC来说该值为(RunLength,Size)。 t=L1+L2+…L16
SOS(Start of Scan)
标记结构 字节数 意义
0XFF1
0XDA1
Ls2 DHT标记码长度,不包括前两个字节0XFF,0XDA
Ns1
Cs11
(Td1,Ta1)1
Cs21
(Td2,Ta2)1
…
CsNs1
(TdNs,TaNs) 1
Ss1
Se1
(Ah,Al)1
Ns为Scan中成分的个数,在基本系统中,Ns=Nf(Frame中成分个数)。CSNs为在Scan中成分的编号。TdNs为高4位,TaNs为低4位,分别表示DC和AC编码表的编号。在基本系统中Ss=0,Se=63,Ah=0,Al=0。
EOI(End of Image) 结束标志
标记结构 字节数 意义
0XFF1
0XD91
JPEG基本系统解码器的程序流程图。
图11. JPEG基本系统解码器的程序流程图
由于没有用到什么优化算法,该解码器的速度并不高,在用VC的性能评测工具Profile评测该程序时我发现最耗时的地方是反离散余弦变换(IDCT)那里,其实这是显然的,浮点数的指令条数要比整数的多得多,因此采用一种快速的IDCT算法能很大的提高性能,我这里采用是目前被认为比较好的一种快速IDCT算法,其主要思想是把二维IDCT分解成行和列两个一维IDCT。
用户数据 ($ofstarid.php)
帖子数据 ($tid.php)
帖子索引数据 (list.php)
版块索引数据 (status.php)
版块数据 (forumdata.php)
论坛索引数据 (bbsnew.php)
今日会员数据 (today.php)
最新会员 (userarray.php)
版主数据 (admin.php)
论坛公告 (notice.php)
联盟论坛 (sharebbs.php)
--------------------------------------------------------------------------------
$ofstarid.php
安全接口#0|姓名#1|密码#2|电子邮件#3|是否公开邮箱#4|用户组#5|头像#6|性别#7|注册时间#8|签名#9|简介#10|QQ#11|ICQ#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|
常用变量统为:
$dir_fb,$dir_name,$dir_pwd,$dir_email,$dir_publicmail,$dir_groupid,$dir_icon,$dir_gender,$dir_regdate,$dir_sign,$dir_introduce,$dir_oicq,$dir_icq,$dir_homepage,$dir_from,$dir_honor,$dir_post,$dir_rvrc,$dir_money,$dir_lasttime,$dir_thistime,$dir_birth,$dir_receivemail,$dir_tuiji,$dir_lastpost,$dir_null1,$dir_lastaddrst,$dir_yz,$dir_todaypost,$dir_group,$dir_sx,$dir_star,$dir_xuni,$dir_badman,$dir_onlinetime,$dir_signchange,$dir_main
--------------------------------------------------------------------------------
$tid.php
安全接口#0|标题#1|作者#2|点击,帖子类型,表情 #3|内容#4|时间#5|IP#6|是否使用签名#7|附件下载次数,附件需要威望#8|被评为几分,评分的人#9| 买帖子的人#10|IP来自#11|帖子是否需要转换#12|是否订阅此主题#13|nul#14l|
常用变量为:
$tpc_fb,$tpc_title,$tpc_author,$tpc_covert,$tpc_content,$tpc_date,$tpc_ip,$tpc_sign,$tpc_download,$tpc_rvrc,$tpc_buy,$tpc_from,$tpc_ifconvert,$tpc_email,$tpc_null1
--------------------------------------------------------------------------------
list.php
安全接口#0|预留#1|预留#2|预留#3|帖子来源# 4|文件名#5|回复数#6|最后发贴状态#7|是否置顶#8|null #9|
常用变量:
$lst_fb,$lst_null,$lst_null,$lst_null,$lst_trdfrom,$lst_filename,$lst_reply,$lst_lastpost,$lst_ifinheard,$lst_null2
--------------------------------------------------------------------------------
status.php
安全接口#0|主题#1|作者#2|时间#3|文件名#4|时间戳#5|帖子总数#6|主题总数#7|子版块所有主题数#8|null#9|
--------------------------------------------------------------------------------
forumdata.php
安全接口#0|版块类型#1|名称#2|简介#3|版块id#4|父版块id#5|密码#6|风格#7|是否使用加密帖#8|是否使用隐藏帖#9|是否使用买卖帖#10|版块公告标题#11|版块公告内容#12|子版块数据段#13|是否全部显示子版块#14|版块logo#15|版块主题回贴限制#16|
--------------------------------------------------------------------------------
bbsnew.php
安全接口#0|新注册用户#1|会员总数#2|主题数#3|文章数#4|今日发贴#5|昨日发贴#6|最高日发贴#7|昨日0点时间控制发贴#8|昨日0点时间控制生日会员#9|今日明星#10|今日庄主#11|今日幸运儿#12|今日生日会员列表#13|最高在线人数#14|最高在线人数发生时间#15|
--------------------------------------------------------------------------------
today.php
安全接口#0|用户名#1|登陆时间#2|活动时间#3|IP#4|用户操作系统#5|用户浏览器#6|NULL#7|IP来源#8|
--------------------------------------------------------------------------------
userarray.php
单一包含 为了简便 安全接口在顶处
--------------------------------------------------------------------------------
admin.php
安全接口#0|版块ID#1|版主名称#2|
-------------------------------------------------------------------------------
notice.php
安全接口#0|作者#1|标题#2|内容#3|起始时间#4|终止时间#5|
--------------------------------------------------------------------------------
sharebbs.php
安全接口#0|论坛名称#1|论坛URL#2|论坛说明#3|论坛logo#4|
--------------------------------------------------------------------------------
帖子类型
list----0普通贴,3置顶贴,
帖子----0普通贴,1锁定的普通贴,2精华贴,3锁定的精华贴,4投票贴,5锁定的投票贴
我发现很多的PHP程序员,尤其是学习还不是很久的,都不知道PHP的精华所在。Perl当年如何在商界出名?其强大的正则表达式。而PHP呢?他是一门从Unix下发展起来的语言,当然也就继承了Perl的很多特点,同时C的优点都有。快速、简洁、明了,尤其是C程序员,PHP是至爱,我就是深爱着“PHP”(都忘了女友了)。这里,我想来写一篇PHP的变量、数组应用技巧和PHP的正则表达式、PHP的模板应用,以后有时间再写PHP与COM、PHP与XML的完全结合。
1、变量、数组的应用技巧
(1)很多人用得不多的数组函数。foreach、list、each。分别举几个例子,应该就能知道了。例:
<?php
$data = array('a' => 'data1', 'b' => 'data2', 'c' => 'data3');
while(list($subscript, $value) = each($data))
{
echo "$subscript => $value :: ";
echo "$subscript => $value\n<br>";
}
reset($data);
foreach($data as $subscript => $value)
{
echo "$subscript => $value :: ";
echo "$subscript => $value\n<br>";
}
(2)函数的变量、变量的变量、变量的“指针”:看下例:
<?php
//变量的变量
$var = "this is a var";
$varname = "var";
echo $$varname;
//函数的变量
function fun1($str) {
echo $str;
}
$funname = "fun1";
$funname("This is a function !");
?>
变量的“指针”。这个指针加上了双引号,表明他不是真正的指针。看看下例:
<?php
function($a) {
$a ++;
}
$c = 0;
function($c);
echo $c; //$c仍为0
function(&$a) {
$a ++;
}
$c = 0;
echo $c; //$c为1
?>
之所以称其为“指针”,就是因为他有了和C语言中指针相同的功能。但这又不是真正的指针,只能够是这样的去理解。
2、正则表达式
正则表达式是一个非常大的题目,Perl的正则表达式的强大是闻了名的。而PHP也不弱,他继承了Perl的正则表达式法则,还有自己的一套法则。这里只说PHP自己的正则表达式。
正则表达式是最基本的元素。简单地说就是一套规则,用于去判定其它的元素是不是符合自身的规则,或者说是不是有相同的特征描述。
正则表达式的开始符:^,结尾符$,这两个符号间的是匹配的元素。如检查一个电话号码是不是打往北京的号,用正则表达式表示就是“^010$”。只要前3位区号是010,就是北京的号,后面的电话号码就不用管了。然后,用正则表达式匹配函数ereg来判断,例:
<?php
$pattern = "^010$";
$phone = "01080718828";
if(ereg($pattern, $phone))
echo "打往北京的号";
else
echo "不是打往北京的号";
?>
这就是正则表达式。北京的电话都是8位数字的,那我要知道这个号码是不是正确了?假如他按了9位号呢?如果判断正误?这就要用到正则表达式的字符簇。那么上例的正则表达式就要这样写:^010[0-9]{8}$,就能同时判断号码是不是符合规则。正则表达式有很多的应用,像LBB、VBB论坛在发贴时的所谓VBB代码LBB代码的解析,都是用正则表达式完成的。
3、模板
知道了正则表达式的功能,那么就可以知道模板了。什么是模板?举个例子吧?一般写网页用到了后台程序的时候,都是在网页里面插入程序代码。如PHP。这就是HTML和PHP的混写。这样的优点是读取速度快,缺点是如果大家分工合作做网站,那么非程序员就不会改网了。
而用模板,则可以达到分工的最合理化。美工只做页面,程序只写后台,然后再合起来。优秀的Jsp提供了自定义标签的功能很好地完成了模板功能。而主流的PHP如何做到呢?就是利用正则表达式来做到的。可以去网上下载一个PHPLIB,里面的PHP目录下有一个template.inc的源代码文件,那就是用PHP实现模板套用的类。
由于篇幅有限,这里只是简单地说一说这些内容,如果真的想学的话,还请看专门的教材。如光要讲清楚正则表达式就可以写上一本小书。
3.11. 记录所有的空语句
总是记录下for或者是while的空块语句,以便清楚的知道该段代码是漏掉了,还是故意不写的。
while ($dest++ = $src++)
; // VOID
3.12. 不要采用缺省方法测试非零值
不要采用缺省值测试非零值,也就是使用:
if (FAIL != f())
比下面的方法好:
if (f())
即使 FAIL 可以含有 0 值 ,也就是PHP认为false的表示。在某人决定用-1代替0作为失败返回值的时候,一个显式的测试就可以帮助你了。就算是比较值不会变化也应该使用显式的比较;例如:if (!($bufsize % strlen($str)))应该写成:if (($bufsize % strlen($str)) == 0)以表示测试的数值(不是布尔)型。一个经常出问题的地方就是使用strcmp来测试一个字符等式,结果永远也不会等于缺省值。
非零测试采用基于缺省值的做法,那么其他函数或表达式就会受到以下的限制:
· 只能返回0表示失败,不能为/有其他的值。
· 命名以便让一个真(true)的返回值是绝对显然的,调用函数IsValid()而不是Checkvalid()。
3.13. 布尔逻辑类型
大部分函数在FALSE的时候返回0,但是发挥非0值就代表TRUE,因而不要用1(TRUE,YES,诸如此类)等式检测一个布尔值,应该用0(FALSE,NO,诸如此类)的不等式来代替:
if (TRUE == func()) { ...
应该写成:
if (FALSE != func()) { ...
3.14. 通常避免嵌入式的赋值
有时候在某些地方我们可以看到嵌入式赋值的语句,那些结构不是一个比较好的少冗余,可读**强的方法。
while ($a != ($c = getchar()))
{
process the character
}
++和--操作符类似于赋值语句。因此,出于许多的目的,在使用函数的时候会产生副作用。使用嵌入式赋值提高运行时**能是可能的。无论怎样,程序员在使用嵌入式赋值语句时需要考虑在增长的速度和减少的可维护**两者间加以权衡。例如:
a = b + c;
d = a + r;
不要写成:
d = (a = b + c) + r;
虽然后者可以节省一个周期。但在长远来看,随着程序的维护费用渐渐增长,程序的编写者对代码渐渐遗忘,就会减少在成熟期的最优化所得。
4. 帮助与共享
4.1. 重用您和其他人的艰苦工作
跨工程的重用在没有一个通用结构的情况下几乎是不可能的。对象符合他们现有的服务需求,不同的过程有着不同的服务需求环境,这使对象重用变得很困难。
开发一个通用结构需要预先花费许多的努力来设计。当努力不成功的时候,无论出于什么原因,有几种办法推荐使用:
4.2. 请教!给群组发Email求助
这个简单的方法很少被使用。因为有些程序员们觉得如果他向其他人求助,会显得自己水平低,这多傻啊!做新的有趣的工作,不要一遍又一遍的做别人已经做过的东西。
如果你需要某些事项的源代码,如果已经有某人做过的话,就向群组发email求助。结果会很惊喜哦!
在许多大的群组中,个人往往不知道其他人在干什么。你甚至可以发现某人在找一些东西做,并且自愿为你写代码,如果人们在一起工作,外面就总有一个金矿。
4.3. 告诉!当你在做事的时候,把它告诉所有人
如果你做了什么可重用的东西的话,让其他人知道。别害羞,也不要为了保护自豪感而把你的工作成果藏起来。一旦养成共享工作成果的习惯,每个人都会获得更多。
4.4. 小型代码库
对于代码重用,一个常见的问题就是人们不从他们做过的代码中做库。一个可重用的类可能正隐蔽在一个程序目录并且决不会有被分享的激动,因为程序员不会把类分拆出来加入库中。
这样的其中一个原因就是人们不喜欢做一个小库,对小库有一些不正确感觉。把这样的感觉克服掉吧,电脑才不关心你有多少个库呢。
如果你有一些代码可以重用,而且不能放入一个已经存在的库中,那么就做一个新的库吧。如果人们真的考虑重用的话,库不会在很长的一段时间里保持那么小的。
4.5. 知识库
很多公司不清楚现有什么代码可用,而且大多数程序员仍然没有通过沟通他们已经做过了什么,或者一直在询问现存什么代码可用。解决这个的方法是有一个可用的知识库。
理想的情况是,程序员可以到一个WEB页,浏览或者查询打包的知识库列表,找到他们所要的。建立一个程序员可以自动维护的知识库系统,是一个很不错的做法。如果有一个专门的管理员来负责维护这个知识库,那当然更好。
另一种方法是自动的从代码中产生知识库的做法。把通用的类、方法和标头(subsystem headers)作为手册或者是知识库的一个条目。
5. 书写注释
5.1. 讲一个故事
把你的注释当作描述系统的一个故事。并且使得你的注释能被机器解析后,以固定的格式放到手册中去。类的注释是故事的一部分,方法的名称、方法的注释、方法的实现也是故事一部分。所有的这些部分编织在一起,使得人们在以后的时间里能够准确的知道你干了什么,为什么这么做。
5.2. 归档注释
注释的要归档才有意义,否则,假如在一个地方放一条注释描述你做了什么选择和你为什么这么做,只有考古学家才能发现这是最有用的信息。(如何归档另行规范)
5.3. 注释结构
工程的每部分都有特定的注释结构。 程序中的注释,这里给出示例作为规范,注释中以 * @ 为关键字的开始,以:为注释关键字结尾。
5.3.1. 预定义关键字
关键字 含义
Purpose 表示类、属**、方法要做些什么或者什么含义。
Package Name 类名
Author 作者
Modifications 修改记录(编号规则为“No”+日期+“-”+序号)
See 参考
Method Name 方法名
Parameter 参数名(包括类型)
Return 返回值(包括类型)
Attribute/Variable Name 属**/变量名
Type 属**/变量类型
5.3.2. 类的注释
/**
* @ Purpose:
* 访问数据库的类,以ODBC作为通用访问接口
* @Package Name: Database
* @Author: Forrest Gump gump@crtvu.edu.cn
* @Modifications:
* No20020523-100:
* odbc_fetch_into()参数位置第二和第三个位置调换
* John Johnson John@crtvu.edu.cn
* @See: (参照)
*/
class Database
{
……
}
5.3.3. 方法注释
/**
* @Purpose:
* 执行一次查询
* @Method Name: Query()
* @Parameter: string $queryStr SQL查询字符串
* @Return: mixed 查询返回值(结果集对象)
*/
function($queryStr){……}
5.3.4. 属**或变量注释
/**
* @Purpose:
* 数据库连接用户名
* @Attribute/Variable Name: mDbUserName
* @Type: string
*/
var mDbUserName;
5.3.5. if (0)来注释外部代码块
有时需要注释大段的测试代码,最简单的方法就是使用if (0)块:
function example()
{
great looking code
if (0) {
lots of code
}
more code
}
你不能使用/**/,因为注释内部不能包含注释,而大段的程序中可以包含注释。
5.3.6. 目录文档
所有的目录下都需要具有README文档,其中包括:
· 该目录的功能及其包含内容
· 一个对每一文件的在线说明(带有link),每一个说明通常还应该提取文件标头的一些属**名字。
· 包括设置、使用说明
· 指导人们如何连接相关资源:
o 源文件索引
o 在线文档
o 纸文档
o 设计文档
· 其他对读者有帮助的东西
考虑一下,当每个原有的工程人员走了,在6个月之内来的一个新人,那个孤独受惊吓的探险者通过整个工程的源代码目录树,阅读说明文件,源文件的标头说明等等做为地图,他应该有能力穿越整个工程。
6. 其他
· 采用面向对象的设计方法;
理由
毫无疑问这是最接近人们自然思维的方法,可能前期会觉得没有直接书写来得快,能否试着保留自己的看法?好戏在后头!
· 类的定义采用一个文件一个类,并且类名和文件名相同;
理由
o 越来越多的人接受了这种做法
o 事实证明这种方法使得项目的逻辑结构更清晰
· 类定义文件中,定义体之外不得出现诸如echo、print等输出语句;
理由
出现这样的语句,应该当做出现bug来看。
· 输出网页的页面不出现SQL语句
理由
这是n层结构的编程思想所致,每层的任务不同,虽然可以越权行使,可能这样很快捷,但我们不赞成这么干。
· 进行SQL执行的数据必须进行有效**检测
特殊符号:
对于MS SQL Server,’%_[ ] 这些符号都是在书写SQL语句中的特殊含义字符,在SQL执行前需要对这些字符进行处理。
脚本符号:
对于PHP脚本标记,如<??><%%><?php?><script lang<script language="php"></script>,在进入数据库前需要检测处理。
理由
这是数据库编程的一个约定,很多参考书上也是这么说,这里需要强调一下。
· 在HTML网页中尽量不要穿插PHP代码
循环代码和纯粹变量输出(类似于<?=$UserName?>)除外。
理由
o 需要说明的是我们工作的上游,页面设计者的工作,假如在页面中穿插代码,将破坏结构,这应当是我们需要避免的。
o 在这里的PHP代码只负责显示,多余的代码显然是不应该的。
· 没有含义的数字
一个在源代码中使用了的赤裸裸的数字是不可思议的数字,因为包括作者,在三个月内,没人它的含义。例如:
if (22 == $foo) { start_thermo_nuclear_war(); }
else if (19 == $foo) { refund_lotso_money(); }
else if (16 == $foo) { infinite_loop(); }
else { cry_cause_im_lost(); }
在上例中22和19的含义是什么呢?如果一个数字改变了,或者这些数字只是简单的错误,你会怎么想?
使用不可思议的数字是该程序员是业余运动员的重要标志.
你应该用define()来给你想表示某样东西的数值一个真正的名字,而不是采用赤裸裸的数字,例如:
define("PRESIDENT_WENT_CRAZY", "22");
define("WE_GOOFED", "19");
define("THEY_DIDNT_PAY", "16");
if (PRESIDENT_WENT_CRAZY == $foo) { start_thermo_nuclear_war(); }
else if (WE_GOOFED == $foo) { refund_lotso_money(); }
else if (THEY_DIDNT_PAY == $foo) { infinite_loop(); }
else { happy_days_i_know_why_im_here(); }
现在不是变得更好了么?
7. PHP文件扩展名
常见的PHP文件的扩展名有:html, .php, .php3, .php4, .phtml, .inc, .class...
这里我们约定:
· 所有浏览者可见页面使用.html
· 所有类、函数库文件使用.php
理由
· 扩展名描述的是那种数据是用户将会收到的。PHP是解释为HTML的。
8. PHP代码标记
统一使用<?php ?>,只输出变量时<?=$username?>
Fvwm is a window manager for X11. It is designed to minimize memory
consumption, provide a 3D look to window frames, and a virtual desktop.
--- The FVWM(F? Virtual Window Manager) manual
- FVWM 是什么样子?
这个问题是永远不会有答案的。问这个问题就像在问:“Xwindow
是什么样子的?”也许勉强可以接受的回答是:“你想让它是什么样子,它就会成为什么样子。”
FVWM 是一个完全可定制的窗口管理器。这是FVWM不同于很多WM的一个特点,它的一切行为方式都是由一个配置文件:
.fvwm2rc 决定的。没有了这个配置文件,FVWM 就成了一个废物:
鼠标和键盘几乎不起任何作用,没有菜单,没有窗口边框,没有按钮,甚至你根本不知道它其实正在运行!
但是一旦有了配置文件,FVWM就会变得威力无比,简单的配置文件可以实现基本的功能,复杂的配置文件甚至可以模拟很多其它WM甚至 Windows
XP。下面就是一个模拟 Windows XP 的例子,点击可以放大。很漂亮吧?什么时候 Windows XP 也能这么漂亮就好了 :)
下面是一个模拟 CDE 的 dtwm 的例子:
以后如果有人告诉你:“FVWM是那个样子。”你就可以对他说:“FVWM不一定是那个样子。” :)
你可以在http://www.fvwm.org/screenshots/看到很多漂亮的配置。
- 配置文件和它的位置
配置文件叫做 ~/.fvwm/.fvwm2rc, 在你的用户目录下。
配置文件有点像一个脚本语言。不要怕,这种脚本语言比起 Perl, awk 简单多了。你不需要学会编程。
写配置文件,一个很好的出发点就是随 FVWM 源码发行的 system.fvwm2rc 文件。它的位置现在在源码包的
sample.fvwmrc/system.fvwm2rc。你也可以在这里下载一份 2.5.4 的system.fvwm2rc.
把它拷贝到你的 ~/.fvwm/ 目录下面, 改名为 .fvwm2rc. 作为我们的起始点。
注意sample.fvwmrc/ 这个目录下还有system.fvwm2rc-sample-95这样的配置文件,它们可以模拟 Windows 95
的操作方式,但是这个配置文件太大了,不适合用来修改成为自己的配置文件,你有兴趣可以自己看看,然后把里面某些你觉得很cool的东西贴到你的配置文件里。
现在我们就来分析一下这个简单的配置文件里到底在说些什么。
- 什么是FVWM命令
一个配置文件里基本上是一些命令,与其它WM不同的是,FVWM并不区分样式命令和动作命令,你可以几乎在任何情况下使用任何命令。比如命令:
Mouse 1 A CSM Style gvim TitleAtBottom
让你在任何时候按住 Ctrl-Shift-Alt 再点击鼠标左键(编号1),名叫gvim的窗口的标题栏就会跑到下面去。是不是很好玩?呵呵。这里
"Style" 是一个样式命令。Mouse 2 A CSM All (rxvt) MoveToDesk 0
按住 Ctrl-Shift-Alt 再点击鼠标左键中键(编号2),所有的 rxvt 都会被移动到当前的桌面. 这里的 "All"
是一个可以附加条件和操作的条件动作命令。
- 怎样实验新的FVWM命令
实验一个命令的作用不需要重新启动FVWM,你可以先在 .fvwm2rc 里这样定义:
Key F3 A A Module FvwmConsole
然后启动fvwm, 这样你在任何时候按下 <F3> 键,就会启动一个叫做 FvwmConsole
的模块,你可以在里面输入Fvwm命令,回车它们就会执行,并且立即生效。这是直接与FVWM对话的方法。当然如果你想得到下面这么漂亮的 FvwmConsole,
还需要对 xterm/rxvt 的参数作一些设定。其实我的定义是:Key F3 A A Module FvwmConsole -terminal rxvt -geometry 45x5-0+0 \
-bg gold -fg midnightblue \
-fn "-adobe-courier-medium-r-*-*-14-*-*-*-*-*-*-*"
- DeskTopSize 2x2 以及其它常用的命令
FVWM
可以有很多个虚拟桌面(desk)(几乎无穷多!),每个虚拟桌面可以被分成很多个页("page")。这些page相当于把你的屏幕扩大了很多倍。这一行就是设定每个desk包含多少page.
DeskTopSize 这类语句指定了整个FVWM的某种行为方式,类似的语句还有很多。比如:
- Read file. 插入另外一个文件file的内容。如果你会C语言,你就知道这个语句相当于 #include
.
当你的配置文件在某一方面有很长内容时,比如我的配置文件里有大量stroke,你就可以把这些都写到另一个文件里,这样使主配置文件容易修改。
- Exec app. 启动一个shell,并且在里面执行"app"命令。这时用菜单和按钮来启动程序时经常用到的命令。
如果你要启动一个X程序最好同时使用 exec, 比如
Exec exec rxvt
这样shell会执行"exec rxvt", 用rxvt替代自己的正文段,这样才不会出现很多shell在那里等待X程序返回。
- Module FvwmXxx. 启动一个叫做 FvwmXxx 的模块。一个模块是一个程序,它直接通过管道与Fvwm通信,所以必须从Fvwm
fork() 出来,而不能从一个 xterm 独立启动。模块可以无限制的扩展Fvwm的功能,只要 Xlib 允许。
- ImagePath path. 指定一个路径,在配置文件里要用到的图标等文件就会到这个路径里去寻找。比如:
ImagePath +:/usr/share/icons:/usr/share/pixmaps:
加号是表示以前定义过的那个 ImagePath. FVWM 可以使用 .xbm, .xpm 和 .png 格式的图标。
$HOME/.fvwm/icons
- Move, Raise, Lower, Resize, ... 这些都是常用的操纵窗口的命令。
- Close, Delete, Destroy, Iconify, Maximize, WindowShade.
关闭窗口,图标化窗口,最大化窗口,shade 化窗口. 你会发现Xwindow关闭窗口的方式有很多种,其中 Close
是最文明的一种,这会发给窗口一个消息让它收拾干净然后退出。Destroy 是立即杀死这个窗口,跟 xkill
的功能一样。Delete介于两者之间,先礼后兵,如果窗口不知道怎么收拾干净,那么就强行杀死它。
- OpaqueMoveSize x. 如果一个窗口移动时显示内容,那么它必需占屏幕面积的 x%.
- MoveToDesk, MoveToPage. 可以把任何窗口移动到指定的桌面和页面。
- SnapAttraction. 设定在什么距离以内,满足什么条件的窗口就被吸附在一起。biaji~~~~~
- WarpToWindow x[p] y[p]. 让鼠标移动到窗口范围以内。x,y是在窗口内的坐标,用百分比表示。后面如果有后缀"p",
就用像素来表示。
- IgnoreModifiers. 你可以忽略某些键盘控制键。这将影响到你的鼠标和键盘热键定义。详细情况见鼠标和键盘一节。
- DesktopName desk name. 定义第desk号桌面的名字叫 name.
- Scroll. 移动你在桌面上的 viewport, 这样你可以把桌面当成一个整体来浏览。看到很大的范围。
- Nop. 不操作,在有些时候需要用它来占位,下面我们会遇到这样的例子。
- PipeRead. 从一个外部命令得到输入。这可以用来根据你的系统构造许多非常高级的控制方式。其中一种叫做“菜单式文件管理器”。我们在菜单一节会遇到这个用法。
- SetEnv. 设置FVWM的环境变量。
就举这些吧……我只是举出了我有时会用到的,其实还有很多很多,你看看 FVWM 的manpage就知道了。
- Read file. 插入另外一个文件file的内容。如果你会C语言,你就知道这个语句相当于 #include
- 窗口上下文
Move, Close, WarpToWindow
...这些命令如何知道作用于那个窗口呢?如果你因为点击了窗口上的按钮,边框,……而激发了这些命令,那么这些命令就会作用于这个窗口。或者你也可以用条件选择命令确定一个或者一批窗口进行操作,见条件命令。否则,这些命令不知道应该作用于哪个窗口,比如你在
FvwmConsole 里键入 "Close", Close
命令就没有窗口上下文,它缺省会出现一个“+”状的选择器让你选择一个窗口。如果你不希望命令在没有窗口上下文的时候自动让你选择一个窗口,那么你可以在命令前面加上
"Silent".
- 鼠标和键盘
FVWM几乎可以以无穷的方式组合,来进行鼠标和键盘的操作。你还可以加入窗口上下文来进行更方便的动作。
键盘操作的定义:
Key Keyname Context Modifiers Function
它表示:在名叫 Keyname 的键在 Context 上下文按下时,如果控制键 Modifiers 组合按下,那么执行 Function.
键盘操作后面的部分跟鼠标一样的含义,我们下面只用鼠标操作来一起说明这些命令的用途。
鼠标操作的定义:
Mouse Button Context Modifiers Function
它表示:在鼠标编号为 Button 的键在 Context 上下文按下时,如果键盘控制键 Modifiers 组合按下,那么执行 Function.
鼠标键编号的方法是:1 左键,2 中键,3 右键。如果你的鼠标有轮子,那么一般4表示往上滚动,5表示往下滚动。
Context是鼠标按下的位置,它可以是:
- R(Root Window) 根窗口
- n (n 是0...9 之间的一个数)。第 n 号窗口按钮。按钮是这样编号的:
1 3 5 7 9 0 8 6
4 2
左边是奇数右边是偶数, 外面的大中间的小。
- T(Title)标题栏
- S(Sidebar)也就是边框. 也可以用 '[', ']', '-' , '_' 分别表示左,右,上,下的边框。
- F(Frame)就是用来resize的那四个角落. 也可以用 '<', '^', '>' and 'v'
分别表示左上,右上,右下,左下的角落。
- W(Working Area) 应用程序窗口工作区域
- I(Icon window) 图标化的窗口。
这些上下文可以组合。比如 "FST" 表示在frame, sidebar, 或者 title.
Modifiers 是鼠标操作时同时的键盘控制键。M 表示 "Meta",在PC上就是Alt,S: shift, C: ctrl. 还有 A:
any, N: none. 也可以组合,比如"MS" 表示同时按下Alt-Shift.
Function 就是任意的FVWM操作了,可以是一个直接的命令,也可以是一个 FVWM 函数。
现在我们分析一下下面这个定义:
Mouse 3 W SC CloseOrNot
这个定义是说,在窗口上点击鼠标右键,并且先按下 Shift-Ctrl,那么调用 CloseOrNot
这个FVWM函数。这个函数会作用与当前鼠标所在的上下文,也就是一个窗口。函数是这样定义的:
- R(Root Window) 根窗口
- 函数
DestroyFunc CloseOrNot
AddToFunc CloseOrNot
+ C Silent Close
+ M Nop
你可以把一系列的操作有条件的加入到一个叫做“函数”的结构里,以后这个函数就可以像命令一样被使用了。
AddToFunc 把动作附加到函数,
如果函数不存在就先创建这个函数。除了第一行,后面的行都以一个"+"号开头,这说明以下是上一个命令(AddToFunc)的继续。DestroyFunc
是为了消除以前有可能定义过的函数体。这个函数 CloseOrNot 表示:
- 如果是一个鼠标点击(C), 那么关闭这个窗口(Close),
但是如果现在不是在窗口上下文,也就是说,函数调用的时候没有一个确定的目标,那么不进行操作,而不是出现一个"+"字瞄准器让用户选择窗口。 这就是
"Silent" 的含义。
- 如果鼠标点下去之后移动了,也就是“拖动”(M),那么不进行操作。
- 如果是一个鼠标点击(C), 那么关闭这个窗口(Close),
- 启动函数和退出函数
在 FVWM 启动和重新启动时都会调用 StartFunction, 而且在首次启动时会调用 InitFunction, 在重新启动时会调用
RestartFunction, InitFunction 和 RestartFunction 都是在 StartFunction
之后调用。每次重起和完全退出时都要执行 ExitFunction.
如果你有什么程序需要在FVWM启动时启动,那么就把它加到合适的函数里面去。比如,我的配置文件有这些内容:
DestroyFunc StartFunction
AddToFunc StartFunction
+ I Module FvwmButtons MainPanel
+ I Module FvwmAuto 500 Raise Nop
+ I Module FvwmAnimate
+ I Module FvwmTaskBar
+ I Exec exec xdaliclock
+ I Exec exec xloadimage -onroot -fullscreen ~/pic/cat_20.jpg
+ I Exec exec xsim
DestroyFunc InitFunction
AddToFunc InitFunction
+ I Exec exec xscreensaver -no-splash
DestroyFunc ExitFunction
AddToFunc ExitFunction
+ I All (xdaliclock) Close
+ I All (xscreensaver) Close
+ I All (xsim) Close
可见,我在第一次启动时会启动 xscreensaver 屏幕保护程序。-no-splash 是 xscreensaver
的参数。在每次重新启动和第一次启动时都要运行 FvwmButtons, FvwmAuto, FvwmAnimate, FvwmTaskBar 几个模块和
xdaliclock,一种 morph 数字的时钟,然后用 xloadimage 放一张漂亮的图片作为桌面背景,最后启动 xsim 中文输入法。
退出和重起时,我特意关闭了那几个启动时打开的程序,因为如果不关闭他们,像 xwin32, Exceed 这样的 Windows X server 不会
Reset.
每个命令前的 "I" 表示 Imediately, 立即执行,联想上面提到的 "C" 和 "M", 这个操作不等待任何鼠标动作。
- 窗口样式
Style 语句用于设定窗口的样式。你可以随心所欲的让不同的窗口有不同的样式。语法为:
Style stylename options
其中 stylename
是你的窗口的名字,窗口的class名字,或者窗口的resource名字。如果你不知道这些 X window
的术语,那现在就姑且当作窗口的名字好了,以后多看看 Xlib 的说明书你就会明白这些东西。窗口的名字有可能不同于程序的名字,你不知道它叫什么名字可以用
xwininfo 程序或者 FvwmIdent 模块来查询。再次说明,FvwmIdent 是模块,不能从 xterm 的命令行运行。
stylename 里可以有 "*" 作为通配符。比如你可以说
Style *term TitleAtLeft
让所有以 "term" 结尾的那些窗口的标题拦都在左边。比如
"xterm", "cxterm", "qterm", ... 都会采用这种样式。
options 是你想让满足条件的窗口以什么样的方式存在。options
的种类非常之多。比如:BorderWidth, HandleWidth, FocusFollowsMouse, TileCascadePlacement,
... 它们有的需要参数,比如 BorderWidth 7, 指定边框宽度为7个像素。有些不需要参数,比如 FocusFollowsMouse/
SloppyFocus/ NeverFocus/ ClickToFocus 指明了几种互相排斥的键盘聚焦方式。 先举几个例子,这些都是
system.fvwm2rc 里的内容:Style * FocusFollowsMouse
Style * TileCascadePlacement
Style "Fvwm*" NoTitle, Sticky, WindowListSkip
Style "Fvwm*" BorderWidth 2, CirculateSkipIcon, CirculateSkip
Style "FvwmPager" StaysOnTop
Style "FvwmBanner" StaysOnTop
Style "FvwmButtons" Icon toolbox.xpm, ClickToFocus
开头的两行说明所有窗口,都是鼠标移进去的时候得到键盘聚焦,鼠标移出来就失去聚焦(FocusFollowsMouse),窗口出现的时候,先试图找一个可以放下它而不挡住其它窗口的地方,如果不行再采用层叠放置的方式(TileCascadePlacement)。
下面是说明所有名字以 "Fvwm" 开头的窗口(在这里一般都是 FVWM
内部的模块),它们都没有标题栏(NoTitle),而且是sticky,也就是说即使桌面切换,它们也一直显示在屏幕上,边框宽度为 2,
CirculateSkip 说明当FVWM要求轮询窗口进行批量操作时,这些窗口不被计算在内。第3,4行说明 FvwmPager, FvwmBanner
这两个模块一直显示在最上面。最后一行说明 FvwmButtons 模块使用 toolbox.xpm
的图标,需要鼠标点击才能得到键盘聚焦(ClickToFocus)。
总的说来,options指出了控制窗口的基本样式和政策,而不包括窗口各个部件具体的样式,它包括以下几个方面内容:
- 窗口聚焦方式。是跟随鼠标(FocusFollowsMouse),还是需要点击才聚焦(ClickToFocus)...
- 窗口标题栏。是否给窗口加上标题拦?如果加上,是放在左边,上边,还是下边?注意这里也不是设定具体标题样式的地方,参看 TitleStyle.
- 窗口应该显示哪些按钮。FVWM可以为每个窗口设定最多10个按钮,但是你通常用不到10个,这样你可以设定对于某一个程序那些按钮应该出现。注意这里也不是设定按钮样式的地方,参看
ButtonStyle.
- 窗口边框样式。边框宽度,handle(就是边框角上那个用来resize的东东)的宽度,边框被鼠标按住的时候是否陷下去?...
- 窗口图标。用那个图标作为窗口iconify时候的图标?
- 窗口最大化,移动,改变大小操作时的样式。是显示窗口内容还是只显示一个“橡皮框”?还是让尺寸小于某个值的窗口才在拖动时显示内容?...
- 窗口放置策略。窗口出现的时候,是层叠放置,最小遮挡放置,还是……?
- 是否允许程序自己放置自己?这是一个政策问题,有些窗口程序启动时会自己选择一个位置出现,但是你可能会发现你不喜欢它那样做,你可以设定NoPPosition,
不允许那个程序自作聪明。
- 对瞬时窗口(transient window)的策略。transient window
是指类似弹出菜单,对话框之类的窗口。当它们出现的时候,你是否想给它们也加上标准的边框?
- 高级特性。还有很多很多选项比如是否允许窗口 backing store,这些如果你还不理解现在暂时不用管它。
以上每项都包含许许多多可以设定的东西。具体还是请参考 fvwm 的manpage。
- 窗口聚焦方式。是跟随鼠标(FocusFollowsMouse),还是需要点击才聚焦(ClickToFocus)...
- 菜单
一个窗口管理器怎么能没有菜单?FVWM的菜单是可以随意自己定义的,它在任何时候出现在你想让它出现的任何地方。一个菜单首先有一个定义,然后有一个激发这个菜单的条件,菜单的样式也可以随意定制。如果使用
PipeRead 命令和一些 shell 命令组合,你就可以用菜单的方式遍历你的文件目录树,成为一个“菜单式file manager”。
- 菜单的定义
菜单是由AddToMenu命令定义的,比如这样一个菜单
是这样定义的
DestroyMenu RootMenu
AddToMenu RootMenu "Root Menu" Title
+ "&xterm%mini.display.xpm%" Exec exec xterm
+ "&Rxvt%mini.monitor.xpm%" Exec exec rxvt
+ "&Big Rxvt" Exec exec rxvt -geometry 78x43
+ "" Nop
+ "&Programs" Popup ProgramsMenu
+ "&Utilities" Popup Utilities
+ "" Nop
+ "Re&fresh Screen" Refresh
+ "Re&capture Screen" Recapture
+ "" Nop
+ "&Lock" Exec exec xscreensaver-command -lock
+ "&Exit Fvwm%mini.exit.xpm%" Popup Quit-Verify
除了第一行,后面的行都以一个+号开头,这说明以下是上一个命令的继续。这样我们定义了一个菜单,它的名字叫 "RootMenu",
它有一个标题叫"Root Menu", 里面有一些程序。当我们选中 "XTerm" 时,会使用FVWM 的 Exec
命令启动一个shell,这个shell马上会执行"exec xterm", 也就是启动一个 xterm。空字串"" 表示在菜单里画一条分隔线。Popup
可以弹出子菜单,子菜单也是用同样的方法定义的。"&"后面的那个字母会变成键盘的热键而被加上下划线,"%"括起来的是图标文件的名字,你需要设置
ImagePath 指向图标文件所在的目录。你还可以在菜单里加入侧面图标,等等等等。
- 菜单的消灭
随后的AddToMenu命令会把内容附加到菜单的末尾。所以如果你想重新定义一个菜单,就需要先把它销毁掉。用
DestroyMenu RootMenu
就可以把刚才那个 "RootMenu" 菜单消灭掉。
- 菜单的激活
光是定义了一个菜单你是不能马上使用它的。这个菜单在什么情况下出现?这个问题是需要你自己来决定,这也是显示FVWM的完全可定制性的地方。比如,我们可以这样定义一个激活菜单的方式:
Mouse 1 R A Menu RootMenu Nop
这句话的意思是:“当鼠标(Mouse)左键(1)在根窗口(R)上点击,同时有任何控制键(A)按下,这个时候显示叫做 RootMenu 的菜单。”
但是有时候我们不容易在屏幕上找到一个可以看到根窗口的地方来点击鼠标。我们可以再加一个定义:
Mouse 3 A MC Menu RootMenu Nop
这样,当右键(3) 在任何地方(A)点击, 同时有Alt(M)和Ctrl(C)按下,那么弹出名叫 "RootMenu" 的菜单。
上面的 "Nop" 表示的是鼠标在菜单上进行双击时的操作。我定义为不操作。另外 Menu 还可以随意定义菜单出现的位置,详细请看 fvwm
manpage。
- 菜单的样式
菜单的样式是由 MenuStyle 定义的:
MenuStyle * MWM
MenuStyle * PopupDelayed, PopupDelay 160, Animation, TitleWarp
MenuStyle * Foreground gold, Background gray40
MenuStyle * ActiveFore White
MenuStyle * Font -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*
MenuStyle * MenuFace VGradient 64 darkgray MidnightBlue
这样我规定:所有的菜单,他们使用 mwm 的行为方式,弹出子菜单延时 160
ms,子菜单弹出时如果靠近屏幕边沿放不下,那么菜单整体移动使得子菜单刚好能弹出,前景色gold,背景色 gray40,
活动的项目(就是鼠标正在它上方的时候)前景色变为白色,菜单使用字体 -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*,
背景是垂直的梯度颜色,一共64阶,从 darkgray 变化到 MidnightBlue.
- 动态菜单定义
前面我们说过了,可以用 PipeRead 来构造一个动态菜单。现在举一个简单的例子:
AddToMenu HomeDirMenu
PipeRead 'for i in $HOME/prog/*.c; \
do echo "+ $i Exec xterm -e vi $i"; done'
当你激发这个菜单 HomeDirMenu,
就会出现你主目录/prog下的所有C程序文件的列表,当你点击其中一个就会启动vi来编辑这个C程序。是不是很方便呢?你想一想,可以用怎样无穷无尽的方式来构造一个菜单呢?
- 菜单的定义
- 按钮
窗口的标题栏上都有一些按钮。那不是窗口程序自己的,而是WM给它们加上的。
FVWM 可以给窗口加上最多10个按钮,它们不光可以实现基本的最大化,最小化,关闭,等功能。FVWM的灵活性允许你赋予按钮几乎任意的功能!
- 按钮编号
按钮是这样编号的:
1 3 5 7 9
0 8 6
4 2
左边是奇数右边是偶数,
外面的大中间的小。
- 按钮功能定义
下面看看按钮的功能是怎么定义的,在鼠标和键盘一节我们已经知道怎么定义鼠标了,按钮的功能只不过是把鼠标与按钮号码组合在一起。
比如我的窗口上一般有三个按钮, 都在右上角,注意它们的编号:
6 4 2
他们的功能是这样定义的:
Mouse 1 4 A Iconify
其中 Maximize-Func2 是用了 system.fvwm2rc 里一个函数:
Mouse 1 6 A Close
Mouse 3 2 A Maximize-Func2DestroyFunc Maximize-Func2
AddToFunc Maximize-Func2 "M" Maximize 100 0
+ "C" Maximize 80 0
+ "D" Maximize 100 100
如果鼠标右键在“最大化”按钮上点击(C)那么高度增长为屏幕的 80%, 宽度不变。如果按下鼠标右键后有拖动(M),
那么高度增长为屏幕高度(100%), 宽度不变。如果双击(D), 就是一般的最大化。
为什么是右键?因为我为左键在这个按钮上定义了更高级的 stroke 来改变窗口大小。我们稍后介绍。
- 按钮样式
按钮的样式是用ButtonStyle定义的。比如我的那三个按钮实际上是如下几句话定义的。
ButtonStyle All -- UseTitleStyle
ButtonStyle All ActiveDown VGradient 8 palevioletred black
ButtonStyle 6 16 20x20@1 30x20@1 50x40@1 70x20@1 80x20@1 80x30@0 \
60x50@0 80x80@0 70x80@0 50x60@0 30x80@0 20x80@0 \
20x70@0 40x50@1 20x30@0 20x20@1
那个X形状的关闭按钮实际上是用很简单的语句画出来的。ButtonStyle
之后的数字是按钮编号,后面一个数子表示一共有多少笔画。后面的XxY@C都是笔画的内容,XxY是坐标, 坐标都是用百分比表示的。@C 表示颜色,
C是一个数字,0 表示阴影色,1 是高亮色,2 是背景色,3 是前景色,4 是移动光标而不画线。
你可以画你自己的按钮,也可以去那别人设计好的来用 FVWM 的主页上有很多人提供这种按钮。
第二个语句 "ActiveDown VGradient 8 palevioletred black"
设定了所有按钮按下去还没有松开鼠标时候的样式,是一个颜色梯度。
- 按钮编号
- 其它样式
我们已经知道 Style 可以决定窗口的样式,MenuStyle 可以决定菜单的样式,ButtonStyle 按钮的样式。其实还有
CursorStyle, TitleStyle, BorderStyle. 他们决定了光标,标题栏,边框的样式。他们都有多样的语法,详细的就看
magpage 吧。这里就不照抄了。
- 条件命令
All, Any, Cond, Current, Direction, Next, None, Pick, WindowId, ...
这些命令是条件选择窗口的办法,它们让你可以用非常多样的方法,来确定你的操作需要对哪一个或者哪些窗口进行。比如:All (Iconic) MoveToPage -1 -1
把所有图标化的窗口都移动到桌面右下角的那一页。Key F5 A A Direction North Maximize True 0 growdown
以后按 F5 就可以让当前聚焦窗口上面(North)那个窗口往下长大,直到被当前窗口挡住去路。你有时候想在VIM里抄 Acrobat Reader
里的内容,安排窗口大小的时候就可以用这招。
- 手写操作 (Stroke)
你用过 EDA 软件吗?用过的话,你就可以知道鼠标动作(stroke)是多么的方便!你是否想在你的窗口管理器里也使用鼠标动作?
- 让 FVWM 支持 Stroke
如果你的FVWM窗口管理器编译进了 libstroke, 你就可以使用鼠标动作操纵程序。libstroke 是一个免费使用的 stroke
库,你可以在 http://www.etla.net/libstroke/得到 libstroke. 下载那个为 FVWM
准备的版本,编译后安装,然后再编译 FVWM,它一般就会找到 libstoke,从而加入 stroke 的功能。
比如我在屏幕上按住 ctrl, 用右键
- 画一个 "r" 字就可以启动 rxvt
- 画一个 "V" 就可以启动 vim
- 画出 "D" 右边的弧线就可以启动 IBM 智能辞典
- 画一个 "e" 启动 emacs ...
- 鼠标左右一晃,就可以启动 xkill,再往某个窗口一点,就可以强制杀死不听话的窗口
- 在窗口里右键往下一划,就可以最小化窗口
- 在窗口边框上用右键……
- 向上拖就可以使窗口往上一直长到被别的窗口挡住的地方
- 向左拖就可以使窗口往左一直长到被别的窗口挡住的地方
- 右……下……斜上…… 从边框开始画一个"L"形就可以回复窗口原来大小
当然这些控制方式都是你自己决定的, 这一切只需要在 .fvwm2rc 里加入一些Stroke语句. 因为太多了,写在主配置文件影响编辑,
这些语句被我写到了另一个文件里,然后在主文件用 Read 语句读入。你可以在这里下载我的fvwm.stroke文件作为参考。
你还可以定义非常高级的操作,你甚至可以这样:按住 ctrl, 用鼠标中键画出一条射线箭头指向的那个窗口,
不论它在那个桌面,就会被吸过来,并且随鼠标移动,你点击左键就可以放置它。
- 画一个 "r" 字就可以启动 rxvt
- 轨迹
stroke 的原理很简单,libstroke
可以识别出你在屏幕上画出的轨迹,把它报告给FVWM,这样FVWM根据轨迹的不同采取不同的操作。轨迹是由一个电话拨号盘的方式确定的。也就是说,把你画出的东西分成9个区域,看你的鼠标依次经过那几个区域。
1 2 3
轨迹也可以用你的小键盘上的数字键来确认。看看你的小键盘:
4 5 6
7 8 97 8 9
4 5 6
1 2 3
- Stroke 项目的定义
在你的配置文件里写入一些 Stroke 语句:
Stroke Sequence Button Context Modifiers Function
比如:Stroke N7414789 0 A C Exec exec rxvt
Stroke N7414759 0 A C Exec exec rxvt
Stroke N74147589 0 A C Exec exec rxvt
Stroke N7414756 0 A C Exec exec rxvt
Stroke N74156 0 A C Exec exec rxvt
Stroke N74159 0 A C Exec exec rxvt
Stroke 关键字之后跟上轨迹说明。轨迹是一系列数字,如果数字前面有一个"N",
就表示我们采用小键盘的布局,而不是电话拨号盘。你看我的那几个轨迹,实际上是我们在写 "r" 字母的时候有可能出现的几种情况。
比如,这个轨迹就是符合 "N7414589".
轨迹之后是鼠标按键号码。如果号码不是0,那么一旦识别到这个轨迹,就会马上执行操作。但是如果号码是0,那么说明这个定义不是在任何时候识别到就马上进行的。而是当
StrokeFunc 命令被调用的时候才进行。StrokeFunc 为你提供了更多的灵活性。
号码之后是 Context Modifiers Function. 他们跟 Mouse, Key 的那两个同名参数是一个意思,参看 鼠标和键盘.
- StrokeFunc
如果你的鼠标号码是0. 那么当 StrokeFunc 被调用的时候,这个轨迹如果被识别,就会执行相应的操作。比如:
#Drag mouse 1 on the maxmize button
现在看到了? 这就是我的最大化按钮上对鼠标左键的绑定。DrawMotion 是 StrokeFunc
Mouse 1 2 N StrokeFunc DrawMotion
的一个可选参数,它可以让轨迹在屏幕上被画出来,这样你可以清楚的看到你到底写了什么。
我有如下的一系列 stroke 定义:
#grow horizontal and vertically
Stroke N258 0 TSF2 N Maximize True 0 growup
Stroke N852 0 TSF2 N Maximize True 0 growdown
Stroke N456 0 TSF2 N Maximize True growright 0
Stroke N654 0 TSF2 N Maximize True growleft 0
#grow bidirectional
Stroke N25852 0 TSF2 N Maximize True 0 grow
Stroke N5852 0 TSF2 N Maximize True 0 grow
........
#reverse to unmaximized
Stroke N74123 0 TSF2 N Maximize False
我的鼠标左键按下“最大化”按钮之后可以进行绘画,然后窗口会随着轨迹的不同而采取各种各样的改变大小的行动!
我还有一个定义:
Mouse 3 TSF N StrokeFunc DrawMotion
这样鼠标右键在窗口标题栏,边框,frame 上绘画时也会触发 StrokeFunc
函数,达到跟左键在“最大化”按钮上绘画同样的效果。发现了吧?StrokeFunc
为我省去了重复的轨迹定义,否则我需要为“左键+最大化按钮”和“右键在边框”定义两套 stroke.
- 实例分析
我们来分析一种可能的执行情况:用鼠标左键按下“最大化”按钮(2),然后向右画。就像这个样子:
当鼠标左键在“最大化”按钮(2)上按下之后,如果没有键盘控制键按下(N),而那么根据"Mouse 1 2 N StrokeFunc
DrawMotion", FVWM就会发现应该调用 StrokeFunc.
StrokeFunc 会马上记录鼠标按下的时候有哪些控制键按下了,现在是没有控制键(N).
然后它发现鼠标随即向右画出了一条线,看看你的小键盘,这是N456。StrokeFunc 就会在已经定义的 Stroke
里去找,是否存在这样的一个定义,它的前面部分是Stroke N456 0 2 N ...
它发现有一个Stroke N456 0 TSF2 N Maximize True growright 0
它的 Context: TSF2 包含了标题栏按钮2。鼠标动作开始时没有控制键按下,而这个项目的Modifiers里也是N.
那么这是一个符合的项目。所以进行操作 "Maximize True growright 0":把窗口向右扩大,直到被另一个窗口或者屏幕边沿挡住。
注意控制键都是在动作开始时就已经记录下来了。如果你在绘画的途中放开了或者按下了控制键是不会改变识别的效果的。
- 怎样提高识别率
通常不要定义太复杂的轨迹,因为变化太多了就不容易识别。左右晃一晃,上下摇一摇,转个圈儿,……已经可以完成你很多任务了。
如果是复杂的 stroke, 比如写一个字母,你需要定义很多相似的 stroke,否则有时不能匹配。如果你不能确定会出现那些轨迹,你可以给
StrokeFunc 一个参数,比如:Mouse 1 2 N StrokeFunc EchoSequence
然后你在屏幕上多画几次你的那个字母,无论它是否匹配一个定义,FVWM
会在启动它的那个终端输出你画出的轨迹号码。那些就是你写这个字母时有可能出现的轨迹,你把这些序列都加到你的配置文件,这样就提高了识别率。
注意这个输出号码的终端很有可能是
tty1,在Linux下你需要Ctrl-Alt-F1切换到tty1才能看到输出。如果你不喜欢这么麻烦,你可以在启动X的时候只启动一个 xterm,
然后在这个 xterm 里面启动 fvwm.
- 键盘触发 Stroke
stroke 也可以由键盘来触发。比如:
Key F6 A C StrokeFunc DrawMotion NotStayPressed
按下 Ctrl-F6 之后,FVWM就会调用 StrokeFunc, 由于我们设定了 NotStayPressed
参数,绘画一直会延续到一个鼠标键按下的时候才结束。这时你就可以用鼠标移动画出一个轨迹,然后按一下鼠标。
- 让 FVWM 支持 Stroke
- 模块
模块是FVWM可以扩展的奥秘。模块是通过管道跟FVWM通信的程序,它们必须由FVWM启动(fork). 也就是说,你可以从 FvwmConsole
来启动这些模块,也可以用菜单,鼠标,热键……来启动。但是就是不能从 xterm 或者 rxvt 敲入命令来启动它们。
- 你有没有发现。当你的鼠标移动到窗口后,如果它被别的窗口挡住了,它并不会跑到上面来。如果你想让它自动上来,你可以使用 FvwmAuto
模块来实现一个简单的“自动提升”功能。我的 StartFunction 里有如下内容:AddToFunc StartFunction
其实你还可以用 FvwmAuto 实现非常复杂的自动提升功能。
+ I Module FvwmAuto 500 Raise Nop
- 你想让你的窗口图标化(Iconify) 和取消图标化(Deinconify) 的时候都有漂亮的动画吗?用以下设定来配置你的
FvwmAnimate 模块,然后启动它,就可以有眼花缭乱的效果了 :)*FvwmAnimate: Delay 25
这些行是对 FvwmAnimate 的配置,模块的配置命令都是 "*" 号开始的。
*FvwmAnimate: Effect Random
*FvwmAnimate: Width 3
- 你想要一个 Windows 那样的任务栏吗?启动 FvwmTaskBar 模块就行了。
- 想要一个 Pager? FvwmPager 可以提供你用不完的功能。参看FvwmPager.
- Drag & Drop? 启动 FvwmDragWell, 就可以让支持 XDND 的程序工作。
- 你想这样一种功能:每次当名叫 XXX 的程序出现时,就把它大小变为 400x300, 移动到屏幕右边,然后启动一个 rxvt 跟它作伴?用
FvwmEvent 可以轻松达到你的目的。
- 你想做一个简单的图形界面程序?用 FvwmScript 可以快速的达到你的目的。
- 写配置文件太冗长了?用 FvwmM4 可以让你用 M4 宏处理语言来预处理配置文件。
- 你想有更加超级的操纵方式?FvwmPerl 可以让你使用 Perl 脚本的方式来操纵 FVWM.
- 你有没有发现。当你的鼠标移动到窗口后,如果它被别的窗口挡住了,它并不会跑到上面来。如果你想让它自动上来,你可以使用 FvwmAuto
- FvwmPager
既然 fvwm
有很多工作区。能不能有一个东西可以方便的看到那些工作区上有哪些程序,而且可以方便的切换工作区呢?FvwmPager就是为这个目的设计的。
我的Pager是这个样子:
FvwmPager有很多可以设定的参数,现在你可以试试这个简单的配置,这就是上面这个 pager 的配置。
*FvwmPager: Rows 4
这些行是对 FvwmPager 的配置,模块的配置命令都是 "*" 号开始的。 在 FVWM 里启动它:
*FvwmPager: Columns 1
*FvwmPagerBack #908090
*FvwmPagerFore #484048
*FvwmPager:Font -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*
*FvwmPagerHilight #cab3ca
*FvwmPagerLabel 0 Main
*FvwmPagerLabel 1 Internet
*FvwmPagerLabel 2 Program
*FvwmPagerLabel 3 Amusement
*FvwmPager:SmallFont -*-simsun-medium-r-*-*-12-*-*-*-*-*-*-*
*FvwmPagerBalloons All
*FvwmPagerBalloonBack Yellow
*FvwmPagerBalloonFore Black
*FvwmPager:BalloonFont -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*
*FvwmPagerBalloonYOffset +2
*FvwmPagerBalloonBorderWidth 1
*FvwmPagerBalloonBorderColor BlackModule FvwmPager 0 3
- FvwmButtons
上面的Pager不错吧?不过它总是在屏幕上占那么一块位置,有没有办法让它可以在需要的时候才伸出来呢?你可以用 FvwmButtons
把FvwmPager包装起来实现这个功能。
这里是我的一个简单的配置:
*FvwmButtonsBack bisque3
这个FvwmButtons设置了一个 button 叫做 "MainPanel". 你可以用:
*MainPanel: Geometry 80x18+40+4
*MainPanel: Back SeaGreen
*MainPanel: (Panel(down, indicator, delay 0, steps 1) \
PagerPanel "Module FvwmButtons PagerPanel")
*MainPanel: Font -*-simsun-medium-r-*-*-16-*-*-*-*-*-*-*
*PagerPanel: Geometry 80x352
*PagerPanel: (Swallow FvwmPager "Module FvwmPager 0 3")
*PagerPanel: Font -*-simsun-medium-r-*-*-16-*-*-*-*-*-*-*Module FvwmButtons MainPanel
来启动它. 它启动时是这个样子:
挂在屏幕左上偏右一点的地方,既没有挡住左边的按钮,又不会挡住窗口的下拉菜单。点一下就会展开,展开以后就是这个样子:
再点就会缩回去。
FvwmButtons 可以提供的功能远远不止这些。FvwmButtons 是一个非常强大的模块。你有兴趣可以看看它的 manpage.
- FAQ
这一节来看看我遇到过的一些问题。
- 为什么 FVWM 不能用图片作为背景?
不熟悉Xwindow的人经常问这种问题。答案是FVWM确实不能设置复杂的高清晰图片作为背景,但是你却可以用图片作为背景。原因是:设置背景根本不是WM的职责,你需要用其它程序,比如
xloadimage, xv, ... 在根窗口上放置一幅图片,那就是所谓的“桌面背景”。你可以把它加入你的启动函数,一个 xloadimage
的例子可以在上面看到。
- FVWM怎么锁定屏幕呢?
你又问到一个容易混淆的问题。锁定屏幕也不是WM必须有的功能。几乎所有WM都是调用另外一个程序,比如 xscreensaver
来锁定屏幕和提供屏幕保护,然后在它们的菜单里加入对 xscreensaver 配置程序 xscreensaver-demo
的调用。看起来好像是WM提供了屏幕保护功能,让很多用户模糊了WM的职责。
你可以把 xsreensaver 加入到FVWM的启动函数里。参看启动函数和退出函数.
- 为什么 FVWM 的窗口标题不能显示汉字?
FVWM当然能显示汉字了,它是一个国际化的程序。原因在于你没有设置好汉字字体。你可以在配置文件里加入汉字字体的设定:
Style * Font -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*
simsun
是我机器上一种同时可以支持汉字和英语编码的字体,如果你的字体只有汉字编码,那么你的英文全部都会“乱码”,这时你需要在后面再加一个英文字体。比如:Style * Font -cjacker-magicsong-medium-r-*-*-14-*-*-*-*-*-gb2312.1980-0,*-r-*
类似的,pager,windowlist 都有自己的字体设定,你需要把它们都设置为你喜欢的中文字体。
- FVWM 有工具条吗?
有。启动 FvwmTaskBar 模块就行了。你还可以把它配置的非常漂亮。这里给出一个我的简陋的配置方案。
Style FvwmTaskBar HandleWidth 0, BorderWidth 0
*FvwmTaskBar: UseSkipList
*FvwmTaskBar: AutoStick
*FvwmTaskBar: DeskOnly
*FvwmTaskBar: Action Click1 DeiconifyRaiseAndFocus
*FvwmTaskBar: Action Click2 Iconify On
*FvwmTaskBar: Action Click3 Lower
*FvwmTaskBar: MailCommand Exec exec rxvt -e mutt
*FvwmTaskBar: 3DFvwm
*FvwmTaskBar: StartName FVWM
*FvwmTaskBar: StartMenu RootMenu
*FvwmTaskBar: Font -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*
*FvwmTaskBar: SelFont -*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*
*FvwmTaskBar: ShowTips
*FvwmTaskBar: ClockFormat
*FvwmTaskBar: WindowButtonsRightMargin 20
*FvwmTaskBar: Back seagreen
*FvwmTaskBar: Fore gold2
*FvwmTaskBar: FocusFore cornsilk
*FvwmTaskBar: IconBack darkgreen
*FvwmTaskBar: IconFore white
另外,FvwmButtons
模块提供了更加复杂的功能。你可以把很多小程序(xclock,biff...)和模块(FvwmIconMan)嵌入到它里面。形成一个复杂的工具条。详情请
man FvwmButtons.
- FVWM 能不能像 Windows 那样用 Alt-Tab 切换窗口?
能。把这行加入 .fvwm2rc:
Key Tab A M WindowList Root c c NoDeskSort
这个绑定不知道什么时候好像成了 FVWM 缺省的。如果你不喜欢,那么加入:Key Tab A M -
取消这个定义。
- FVWM 能和KDE, Gnome 一起工作吗?
KDE 和 Gnome 都是完整的桌面系统,包括了WM和其它很多东西。FVWM 只是一个WM。FVWM可以替代 KDE 缺省的 kwin,或者
Gnome 缺省的 sawfish 成为它们的WM。
- 只用KDE和Gnome的工具条
很多时候 Gnome 和 KDE 的程序是跟他们的桌面系统可以分开使用的。其实你有可能只需要它们漂亮的panel。
Gnome 的工具条叫做 gnome-panel, 在 xterm 启动一个就行了。
KDE 的工具条叫做 kicker. 注意 KDE 有些程序需要 dcopserver, 你可以先启动 dcopserver。
KDE 和 gnome 的panel上的pager和fvwm的FvwmPager都是相通的,所以你可以用它们任何一个来切换桌面 :)
如果你在kde的任务条用右键选择“总在最前”可能不起作用,因为现在它们得完全听fvwm的话不过你可以给它们额外的权力,请参考fvwm
manpage 的有关EWMH 的部分
- 完全启动Gnome和KDE与FVWM一起工作
还有些kde程序不知道用了什么通信方式,启动后就dump了。你可以用 startkde 来启动整个 KDE
系统。一般来说它们都可以与fvwm一起很好的工作。
Gnome 的启动命令叫做 gnome-session. 它也可以完全与 fvwm 一起工作。
这样你就可以用 fvwm 的方式来控制所有桌面系统的窗口了。嘿嘿
在一起。嘿嘿。点击可以放大。
你甚至可以让 Gnome 和 KDE 同时出现。不过估计除了耍酷,没人会像这样做:
- 只用KDE和Gnome的工具条
- 为什么 FVWM 不能用图片作为背景?
在看这个文档之前你最好对 Xwindow 的工作机制有一定了解。知道 X server 跟 WM
有什么关系。我以后或许会增加这些内容,但是现在暂时还没有时间写这些。