用VC6和Install Shield6.2进行自动化构建

文:Charry

如果一个VC的项目中,有10个或者更多的工程(Project),当我们需要进行一次完整的构建(Build)时,如果你分别用VC 打开每个工程然后Build,相信大家都会觉得很是麻烦。其实VC就自带了一个强大的自动构建的工具: nmake 。它可以帮助你完成日常的构建工作。

nmake的详细用法,你可以通过在控制台下输入:nmake /help来查看。下面我举个实际的例子。

该项目有大约十多个工程,目录结构如下:

src/
+TSHA_LIB
+TSHP_CFG
+TSHA_BROW
+…
+TSHC_OPT

所有的项目文件源码位于src目录下,每个工程占用一个独立的目录。这时,我们可以编写Makefile文件如下:

VC=MSDEV.EXE
CFLAG_ALL=/make /rebuild
RMDIR=rmdir /S /Q
BIN_DLL_DIR=E:\Project\dvlp\client\Release\bin
DEST_DLL_DIR=E:\Project\dist\dll
DEST_BIN_DIR=E:\Project\dist\bin

all:
@echo Now building 共通
$(VC) TSHA_LIB/TSHA_LIB.dsp $(CFLAG_ALL)

@echo Now building 故障管理
$(VC) TSHP_MON_MAPX/TSHP_MON.dsp $(CFLAG_ALL)

@echo Now building 安全管理
$(VC) TSHI_SEC/TSHI_SEC.dsp $(CFLAG_ALL)

@echo Now building 资源管理
$(VC) TSHP_CFG/TSHP_CFG.dsp $(CFLAG_ALL)

@echo Now building 浏览器
$(VC) TSHA_BROW/TSHA_BROW.dsp $(CFLAG_ALL)

@echo Now building 主控画面
$(VC) MAIN_MENU/MAIN_MNU.dsp $(CFLAG_ALL)

@echo Now building TSData
$(VC) TSHA_DATA/TSHA_DATA.dsp $(CFLAG_ALL)

@echo Now building 配置管理
$(VC) TSHB_CFG/TSHB_CFG.dsp $(CFLAG_ALL)

@echo Now building 系统管理
$(VC) TSHE_SMT/TSHE_SMT.dsp $(CFLAG_ALL)

@echo Now building 综合查询
$(VC) TSHF_QRY/TSHF_QRY.dsp $(CFLAG_ALL)

@echo Now building 系能分析
$(VC) TSHP_NSO/TSHP_NSO.dsp $(CFLAG_ALL)

@echo Now building 工单管理
$(VC) TSHL_TER/TSHL_TER.dsp $(CFLAG_ALL)

@echo Now building 操作维护
$(VC) TSHC_OPT/TSHC_OPT.dsp $(CFLAG_ALL)

@echo Now removing trash
$(RMDIR) TSHA_LIB\Release
$(RMDIR) TSHP_NSO\Release
$(RMDIR) TSHP_CFG\Release
$(RMDIR) TSHF_QRY\Release
$(RMDIR) TSHE_SMT\Release
$(RMDIR) TSHB_CFG\Release
$(RMDIR) TSHA_DATA\Release
$(RMDIR) MAIN_MENU\Release
$(RMDIR) TSHA_BROW\Release
$(RMDIR) TSHI_SEC\Release
$(RMDIR) TSHP_MON_MAPX\Release
$(RMDIR) TSHL_TER\Release
$(RMDIR) TSHC_OPT\Release

copy $(BIN_DLL_DIR)\TSHA_LIB.dll $(DEST_DLL_DIR)
copy $(BIN_DLL_DIR)\TSHA_Data.dll $(DEST_DLL_DIR)
copy $(BIN_DLL_DIR)\TSHP_NSO.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHP_CFG.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHF_QRY.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHE_SMT.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHB_CFG.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\MAIN_MNU.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHA_BROW.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHI_SEC.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHP_MON.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHL_TER.exe $(DEST_BIN_DIR)
copy $(BIN_DLL_DIR)\TSHC_OPT.exe $(DEST_BIN_DIR)

对比上面的文件和你的项目结构,把它稍作修改,最好保存为‘Makefile’文件名。熟悉 UNIX的朋友都知道make使用Makefile作为配置文件,nmake亦然。把保存好的Makefile放在src目录下,在src目录下运行nmake就可以自动构建你的项目了。

很简单吧,Makefile的编写方法几乎UNIX下的Makefile相同。文件的开头是一些变量定义,比如:CFLAG_ALL是编译的参数,BIN_DLL_DIR是输出文件的位置,它和每个Project中的设置是一样的。构建后,我们可以自动调用rmdir删除目标文件,最后,把所有的.exe文件和.dll文件拷贝到Install Shield的工程目录下,在这里就是DEST_DLL_DIR和DEST_BIN_DIR 指定的目录。

Bingo,到此为止,用nmake自动构建就已经基本完成了。下面我们开始用Install Shield打包项目文件了,这个过程更简单,打开Install Shield,在菜单[Build] 中找到[Export (Build) Batch File...],输出一个.bat文件。以后你就不需要打开Install Shield,而只要执行这个.bat文件就可以给你的项目打包了。

如果你想把编译和打包一起做,你可在Makefile的最后调用打包的.bat文件。这样只要在控制台下输入nmake,1/10柱香的工夫你的项目就自动构建好了,岂不快哉。

Procmail使用教程

文:Charry [部分参考Ian Soboroff -- ian@umbc.edu的文章]

2004年Bill Gates先生预言微软要在2年内消灭垃圾邮件,现在看来这个预言已经落空了。垃圾邮件还是一如既往的猖狂,甚至随着WEB2.0的到来,比以往显得更为猛烈。那么既然Bill不能为我们解决这个难题,我们就只能靠自己了,下面我要介绍的这个小程序可以帮我们抵挡住大部分的垃圾邮件。这就是Procmail。

Procmail用于过滤电子邮件。它可以预整理或者预处理您的大量的来信。您可以用它从邮件列表中挑选中想要的邮件、剔除垃圾邮件、自动回复等等等等。

Procmail一般用来和fetchmail、mutt、sendmail搭配使用。Procmail只是个工具而且,对于它,我们只要大致的明白的它的配置规则即可。下面我贴上我的配置文件,并加以说明。

我的Procmail配置文件:.procmailrc

#################################
#
# charry[at]charry.org
#
#################################

LOGFILE=$HOME/.pmlog
UMASK=000

# 变量定义,这里只是一部分,其他的请参考mannual
NULL=/dev/null
SPAM=/tmp/spam
JUNK=$HOME/Mail/junk
FOO=$HOME/Mail/foo

##### General filter ######

# 标题中带有Ultimate的邮件,保存到$SPAM中
:0b
* ^Subject:.*Ultimate.*
$SPAM

# 标题中带有xxx movie的邮件保存到$SPAM中
# 这里我为什么没有写Subject,因为缺省查找的就是邮件头
# 以下同
:0b
* .*xxx movie.*
$SPAM

# 把标题中带有xxx girl的邮件,拷贝到$FOO中,并且删除(即放到$NULL中)
# 之所以这样做,是因为这样备份的邮件才能被mutt打开,注意,下面的规
# 则只针对”标题”中带有xxx girl的邮件
:0
* .*xxx girl.*
{
:0 c
$FOO

:0
$NULL
}

# 如果标题中包含sex,则保存到$JUNK中
:0b
* ^Subject:.*sex.*
$JUNK

# 如果正文中包含”合肥山舟”,则保存到$JUNK中
:0Bb
* .*合肥山舟.*
$SPAM

# 如果标题中包含software,则保存到$JUNK中
:0b
* ^Subject:.*software.*
$JUNK

# 如果标题中包含foo.com,则保存到$JUNK中
# 经过测试foo.com和foo\.com都可以过滤
# 因为.即表示任意字符,而\.只是表示一个点,前者的范围其实更大
:0b
* ^Subject:.*foo\.com.*
$JUNK

# 正文中带有如下字样的,保存到$SPAM中
:0Bb
* ^Enlarge your penis up.*
$SPAM

# 正文中带有http://reg.sms.ac字样的,保存到$SPAM中
:0Bb
* .*http:\/\/reg\.sms\.ac.*
$SPAM

# 正文中带有http://www.23idc.com字样的,保存到$SPAM中
:0Bb
* .*http:\/\/www\.23idc\.com.*
$SPAM

# 标题中含有Nude Pictures的
:0b
* ^Subject:.*Nude Pictures.*
$SPAM

# 正文中包含Nude Pictures的
:0Bb
* .*Nude Pictures.*
$SPAM

# 正文中包含increase your size的
:0Bb
* .*increase your size.*
$SPAM

# 正文中包括”赚钱”的保存到$JUNK中
:0B:
* .*赚钱.*
$JUNK

# 如果邮件的正文中包含”Visit our site:”则保存到$JUNK中
:0B:
* .*our site:.*
$JUNK

# 如果邮件的正文中包含”Visit our website:”则保存到$JUNK中
:0B:
* .*our website:.*
$JUNK

# 拒收hotmail的邮件
#:0
#* $ ^From:.*\@hotmail\.com
#$NULL

# 拒收 yahoo.com 但收取 yahoo.com.tw
#:0
#* $ ^From:.*\@yahoo\.com
#* $ !^From:.*\@yahoo\.com\.tw
#$NULL

# 把Cron Daemon的错误挡掉
:0
* ^From:.*Cron Daemon.*
$NULL

# 记录所有邮件地址
:0 c:
| formail -zxFrom: -zxTo: -zxCc: >>tmp/capture

概要
看了上面的例子,估计你已经大概的了解了Procmail的配置方法了,下面我少许的说明一下。标准的格式如下:

:0 [参数] [: [锁定文件]
零或者多个条件(每个一行)
动作

下面是个例子

:0:
* ^(From|Cc|To).*sohu
sohu

第二行是它的条件行,它以星号开头,后面是一个正则表达式(regular expression)。在本例中,它表示所有以From或者Cc或者To打头的,并且后面跟着一些字符,然后再跟着’sohu’的字样。如果某个邮件的一部分匹配了这样的条件,则执行下面的动作。在这里表示把它放到一个名为’sohu’的文件中。

正则表达式
正则表达式区分大小写,比如Charry和charry不同。
点’.'匹配任何任何字符,除了新行符(newline)。比如:.harry匹配charry, xharry, pharry等等。
字符后面跟一个星号(*),表示它将出现0或者多次。比如char*y匹配chay,chary,charry,charrrrrrrrrrrrrrrry。可以想象:.*将匹配任何长度的字符串。
字符后面跟一个加号(+),表示它将出现1或者多次。字符后面跟一个问号(?)表示它将出现0或者1次。你可以用括号把字符括起来作为一个整体,比如:B(ob)+表示:Bot, Bobobob等等。
Part [abcd]匹配:Part a, Part b, Part c, Part d
[^aeiou]+匹配所有非元音单词。这里的^表示‘非’
Bob|Joe表示Bob或者Joe
^表示匹配开头,$表示匹配结尾。注意这里^和上面的那个^意义不同。
更多信息请参考专门的正则表达式(regular expression)文档
规则(意译)
我们来看个例子:

# israeline是个邮件列表的名字
:0: # 最后一个冒号表示锁定文件
* ^To:.*israeline
israel # 把所有的邮件放入’israel’文件中

有一件事情必须知道,不要在匹配条件行中加注释,否则这些注释也会被当成匹配条件

下面我们看一下什么是‘锁文件’(lockfile)。假设几乎在同时,有两份israeline的邮件发来。有很大的可能性,系统会启动两个Procmail,而它们都会尝试着去写‘israel’文件。不用我说,你也会知道后果如何,为了避免错误,我们可以用lockfile。这样,第一个Procmail会锁定israel,这样只有它才能有写的权限。其他的Procmail只能等待第一个完成它的操作。

假设你的大学同学Bob喜欢给你发一些网上找的有趣的东西,这些信件通常以“joke”,“funny”作主题。而你却不想把这些邮件和你的工作相关的信件混在一起,你希望把这些信件转发到一个不同的邮箱中,同时你不想把Bob发来的工作相关的信件也被转发。你可以用下面的规则,它用了两个匹配条件。

:0 # 转发邮件到nishizhu@gmail.com
* ^From.*bob
* ^Subject:.*(joke|funny)
! nishizhu@gmail.com

在动作行的开头加一个“!”表示转发邮件。你可能注意到这里没有在From后面加一个冒号(:),因为有的邮件头中,From后面没有冒号(:),所以为了安全起见,你最好不加那个冒号(:)。既然这里是转发邮件,所以我们就不需要锁定文件了。

我在测试中发现,对于标题中带有中文的,类似的匹配并不起作用,不知道是我的写法有误还是Procmail的bug。对于邮件正文的匹配正常,可以处理中文。
下面我们来看一个复杂的例子:

:0: # 转发
* ^From.*bob
* ^Subject:.*(joke|funny)
{
:0 c
! nishizhu@gmail.com

:0
| lpr -Pacsps
}

在动作行,我们用了一个嵌套的块。它可以被看作另一个.procmailrc文件,在这个块中,我们又可定义任意个匹配条件,不过,必须要求其父条件也成立。

第一个条件表示转发邮件,这里用到了一个参数‘c’,它其实是copy的缩写。因为一份邮件通常会被送到第一个匹配条件的目的地,所以这里用‘c’允许我们为一份邮件加上两个匹配条件。

我们把邮件送到一个程序中,得用管道号(|),它的意思是说,把邮件作为下面的这个程序的输入。这里我们把匹配的邮件用打印机acsps打印出来。

我们再来看一个例子

:0 bc: # 打包压缩
* ^To:.*junk
| gzip >> junk-archive.gz

假如我们有个邮件列表或者邮件用来保存一些垃圾。我们可以把它的邮件正文打包压缩,供以后查阅。这里我们用了两个参数,’b'表示下面的动作行只对信件的正文起作用,并不对信件的头起作用。’c'表示我们只是copy一份邮件并对它作相应的处理。然后把邮件传递到下一个规则中。我们之所以这样做,是因为:我们只想在压缩包中保存邮件的正文,而在邮箱(mail inbox)中,我们希望它没什么变化。

其他
对于更为详细的Procmail的介绍,请参考它的联机文档。Procmail的联机文档分为不同的几个部分:

$man procmail
# 程序的基本介绍,参数介绍,在最后还有一些例子

$man procmailrc
# .procmailrc格式的详细介绍。

$man procmailex
# 若干个.procmailrc的例子

$man procmailsc
# 加权过滤,高级的过滤规则。
Procmail的作者是:Stephen R. van den Berg,(RWTH-Aachen, 德国)。最新版的Procmail在
ftp://ftp.informatik.rwth-aachen.de/pub/packages/procmail/

有用的Shell脚本

文:Charry

记录下我常用的一些脚本。

在命令行下,方便的调用FireFox播放flash文件
点击这里查看脚本内容

查找重复的文件
本脚本可以在一个目录中查找完全相同的文件,这里的“相同”指的是文件的内容,而不是指文件名。比较常用的地方是:如果你从网上下载了很多MM图片,而日积月累后,你可能不知道你已经下载了很多的重复的文件,虽然它们的文件名不同。重复的文件会显示出来,不过这个脚本一次只能发现一个重复的文件,如果你有多个文件一模一样,你需要重复执行多次。

#/bin/sh

# find the duplicate files in dir
# by charry
#

find . -name "*" -exec md5sum {} ; | sort | uniq -d -w 33

挂载、卸载Windows分区
挂载
如果你需要,请把/dev/hdax改为你自己的序号。注意,我的C盘是ntfs格式,我在挂载的时候,指定了它的用户ID为500,你可以把它改为你自己的ID。

#!/bin/sh

# mount windows parition
# by charry

usage()
{
echo "Usage: `basename $0`

[-h]"
exit 1
}

if [ $# = 0 ]
then
usage
fi

case $@ in
c)
sudo mount -t ntfs -r -o uid=500 -o iocharset=utf8 /dev/hda1 /mnt/c
;;
d)
sudo mount -t vfat -o iocharset=utf8 /dev/hda5 /mnt/d
;;
e)
sudo mount -t vfat -o iocharset=utf8 /dev/hda6 /mnt/e
;;
f)
sudo mount -t vfat -o iocharset=utf8 /dev/hda7 /mnt/f
;;
-h)
echo "A script for mounting windows partiton"
echo "Usage: `basename $0`


[-h]"
echo ""
echo "For example:"
echo " `basename $0` d"
;;
esac

卸载

#!/bin/sh

# umount windows parition
# by charry

usage()
{
echo "Usage: `basename $0`

[-h]"
exit 1
}

if [ $# = 0 ]
then
usage
fi

case $@ in
c)
sudo umount /mnt/c
;;
d)
sudo umount /mnt/d
;;
e)
sudo umount /mnt/e
;;
f)
sudo umount /mnt/f
;;
-h)
echo "A script for umount windows partition"
echo "Usage: `basename $0`


"
echo ""
echo "For example:"
echo " `basename $0` d"
;;
esac

更改文件名的大小写

把文件名从小写改为大写

#!/bin/sh

# convert file name from lowercase to uppercase

for foo in $@; do
mv $foo `echo $foo | tr [a-z] [A-Z]`
done

把文件名从大写改为小写

#!/bin/sh

# convert file name from uppercase to lowercase

for foo in $@; do
mv $foo `echo $foo | tr [A-Z] [a-z]`
done

备份脚本
用法:$backup foo.c

#!/bin/sh

# backup file

if [ ! -d ~/tmp/Backups ]; then
mkdir ~/tmp/Backups
fi

for BACKUPFILE in $@; do
cp $BACKUPFILE ~/tmp/Backups/$BACKUPFILE.bak
done

带确认窗口的删除脚本
需要X支持

#!/bin/sh

# prompt confirm dialog before removing

xmessage -buttons "Yes,No" "Remove file $1?"
RESULT=$?

if [ $RESULT -eq 101 ]; then
rm "$1"
fi

批量命名
还是拿下载MM图片举例。比如你已经下载了很多图片,而新下载的图片的名字和已经下载的名字很多都是一样的,但是它们的确不是同一张图片,你是不是每次都会被浏览器提示:“要覆盖吗?”伤透了脑筋。赫赫,下面的脚本可以帮你解决问题,当你觉得重复文件太多时,你可以执行一下,把当前目录下的所有文件批量改为由该文件的inode组成的新文件名。

#!/bin/sh

# batch rename file based on it's inode
# by Charry

usage()
{
echo "Usage: `basename $0` "
echo ""
echo "e.g.: `basename $0` \"*.jpg\" foo jpeg"
echo " the above command line will rename *.jpg to foo????.jpeg"
exit 1
}

if [ $# = 0 ]
then
usage
fi

find $1 -printf "mv '%f' $2%i.$3n" > rename.sh
sh rename.sh
rm -rf rename.sh

批量更改文件名的编码
下面的代码可以帮助你,把当前目录下的文件的文件名编码方式在gb2312和utf8之间转化。本代码的限制是,你的LC_ALL变量必须设置为zh_CN.GB2312或者zh_CN.UTF-8。

#/bin/sh

# Convert file name encoding
# by Charry
#

usage()
{
echo "Usage: `basename $0` [gb2312 | utf8]"
echo 'Enviroment variable $LC_ALL must be set properly!'
exit 1
}

warning()
{
echo "Wrong option, It should be 'gb2312' or 'utf8'!"
exit 1
}

if [ $# = 0 ]
then
usage
fi

# read original encoding type for restore
ORIGINAL=$LC_ALL

FOO=".foo.sh"
BAR=".bar.sh"
TMP=".foobar.sh"

case $@ in
utf8)
export LC_ALL=zh_CN.GB2312
;;
gb2312)
export LC_ALL=zh_CN.UTF-8
;;
*)
warning
;;
esac

echo "#!/bin/sh" > $FOO
echo "#!/bin/sh" > $BAR
echo "#!/bin/sh" > $TMP

find . -name "*" -printf "mv '%f' %in" >> $FOO
find . -name "*" -printf "mv %i '%f'n" >> $BAR

sh $FOO

case $@ in
utf8)
iconv -f gb2312 -t utf8 $BAR > $TMP
;;
gb2312)
iconv -f utf8 -t gb2312 $BAR > $TMP
;;
esac

sh $TMP

rm -rf $FOO
rm -rf $BAR
rm -rf $TMP

export LC_ALL=$ORIGINAL

TWM in a nutshell

文:Charry

本文只涉及到TWM的入门级知识,大家都知道很多UNIX下程序的教程都可以写成一本书,在这里我只介绍入门的一些东西,如果本文能帮助某些朋友对TWM有个大致的了解,就算完成它的使命了,其他的复杂部分,就需要自己去探索了。

TWM是Tab Window Manager for the X Window System的简称,它是一个窗口管理器,初次发布于1988年4月,是个非常容易上手的Window Manager。不像其他的X程序,它没有基于任何GUI组件,而是直接使用的XLib,这样带来的好处就是:小、更方便的配置。所谓窗口管理器,它是一个特殊的程序,它用来给X程序提供诸如:标题的绘制、窗口阴影、窗口图标化、用户自定义宏、鼠标点击、键盘焦点、缩放等功能。

它和GNOME、KDE不同,不是一个桌面环境(Desktop Enviroment,DE)。那些所谓的桌面环境都会有一个窗口管理器,比如CentOS的GNOME用的就是MetaCity,这些DE集成了大量的应用程序,包括一些非常便利的系统管理工具、实用小工具、游戏等,大大方便了用户。

桌面环境纵有千般好,也会有它的短处,比如:由于它的庞大,在系统启动的时候会显的很慢,其实有很多应用我们都不会用到,这个时候,你可选择只加载一个窗口管理器即可。而且你将会发现,几乎所有的窗口管理器都可以用rc文件来配置,你可以在允许的范围内,任意的配置。比如TWM的配置文件就是.twmrc。它位于用户目录下,在TWM启的时候它会首先从用户的主目录下找这个文件,如果它找不到,TWM就会使用一个系统共用的配置文件,一般情况下它位于:/usr/X11R6/lib/X11/twm/system.twmrc。

为了要启动TWM,而不是GNOME或KDE,我们需要在用户的目录下编辑一个.xinitrc的文件,它的内容如下:

#!/bin/sh

xclock -geometry 70x70+5+5 &
xterm -geometry +200+200 -fn 7x13 -fg gray -bg black &

exec twm

这样,当你在执行startx的时候,就只会启动TWM了。最后一行表示启动TWM,前面的两行表示启动的其他程序,比如xclock,它是一个时钟程序,它后面的参数表示它启动后所在的位置和大小。需要注意的是,除了最后一行,其他的行要在最后加上后台运行标志,否则后面的程序都没法进行了。除了最后一行,其他的都是可选的,你可以把你常用的一些程序放在exec twm前,这就和Windows下的启动一样。startx后,你将会发现,TWM的启动非常的快,至少比GNOME,KDE快多了,当然这样比有失公平。

TWM的配置逻辑上被分为三类概念:变量(Variables)、绑定(Bindings)和菜单(Menus)。它们都保存在用户目录下的.twmrc文件中。

变量
变量的配置必须放在第一,它用来描述字体、颜色、指针、边框宽度、图标、窗口的位置摆放,高亮、自动获得焦点等。

变量的名字和关键字是非大小写敏感的。字符串必须用引号引起来,比如:”blue”,并且字符串是大小写敏感的。

举个例子:

BorderColor "gray50"
{
"XTerm" "red"
"xmh" "green"
}

上面表示,所有的窗口的边框颜色为gray50,大致为灰色,括号中间表示特殊的情况,比如第一行的意思是:如果窗口的名字为”XTerm”,或者它的类名为”XTerm”(注),它的边框颜色就为red,即红色的。我们可定义很多窗口元素的颜色,如菜单背景、菜单前景、标题背景、标题前景等。

Color
{
MenuBackground "gray50"
MenuForeground "blue"
BorderColor "red" { "XTerm" "yellow" }
TitleForeground "yellow"
TitleBackground "blue"
}

绑定
绑定配置通常放在第二位,主要用于描述键盘或者鼠标在窗口、图标、标题、框架上动作时,产生的影响。

比如我们可以把F1键绑定为最小化操作,把F2绑定为更改窗口的层次,把F11绑定为最大化窗口,把Shift+F4绑定为关闭窗口,F12用来把窗口焦点移到某个窗口上。

"F1" = : all : f.iconify
"F2" = : all : f.raiselower
"F4" = shift : all : f.delete
"F11" = : all : f.fullzoom
"F12" = : all : f.warpto "XTerm Icon Manager"

绑定键盘的语法为:

Button or Key = modlist : context : function

Button or Key,就是鼠标的按键或者是键盘上的某个键。modlist是一些功能键或者它们的组合,比如shift, control, lock, meta, mod1, mod2,mod3, mod4, mod5等,shift, control和lock这些键大家都知道,meta在有些系统上就是alt键。其他的我也没搞明白是什么东西,如果你知道,请告诉我。context表示上下文,所谓上下文,就是指鼠标或者焦点所在的地方。比如上面的 F4键的行,其中的all表示当鼠标指针点在程序的任意位置,shift+F4都会把当前窗口关闭,上下文还包括:

root: 根窗口
frame: 窗口的框架
title: 窗口的标题
window: 窗口的客户区,就是窗口的内部那块区域,学过VC的应该很清楚
icon: 图标
iconmgr: 窗口管理器
all: 就是所有啦

再举个例子:

Button1 = : root : f.menu "TwmWindows"

表示当鼠标左键在根窗口上点击的时候,弹出TwmWindows菜单,TwmWindows是一个菜单的标志符,我将在后面说明。

上下文可以任意组合,比如想表示鼠标在框架或者标题上的绑定,我们可以这样写 “F1″ = shift : t|f : f.raise。其中t为title的缩写,f为frame的缩写。其他的上下文也都有缩写。

我们还可以把窗口的标题上加“标题按钮”,比如我们要在标题上加一个关闭按钮,我们可以这样:

LeftTitleButton "/usr/X11R6/include/X11/bitmaps/xm_noenter16" = f.delete

LeftTitleButton表示位置,然后是按钮图标的路径,最后是按钮的动作。

菜单
菜单用于给用户提供自定义单的机会。它们可以被分成不同组,方便管理。每个菜单都由一个名字来标识,这个名字将来用作f.menu的参数。并且,我们还可以定义菜单的背景色、前景色、菜单的项以及该项所对应的动作。如下例:

menu "LeftClickMenu"
{
"my menu" f.title
"fcitx" f.exec "exec fcitx &"
"kill fcitx" f.exec "exec killall fcitx &"
"" ("rgb:0/2/4":"rgb:4/b/f") f.nop
"Xterm" f.exec "exec xterm -fn 7x13 -fg gray -bg black &"
"GNOME Term" f.exec "gnome-terminal &"
"FireFox" f.exec "exec firefox &"
"Luma QQ" f.exec "exec ~/bin/LumaQQ/lumaqq &"
"Gaim" f.exec "exec gaim &"
"Time" f.exec "exec xmessage `date +\"%F %R:%S [%u]\"` &"
}

菜单的内容编辑好后,你需要设置菜单的激活条件。比如上面的菜单,我们让它在鼠标左键点击屏幕时弹出。方法是在.twmrc中加入

Button1 = : root : f.menu “LeftClickMenuButton1表示鼠标左键,root表示根窗口,可以说就是桌面。

正如你所看到的一样,配置非常的简单,其中我设置了一个空菜单,它用来分割不同类别的菜单项,它的颜色和别的稍有不同,括号中的前面表示前景色,后面表是背景色。而最后一项它的动作为f.nop表示没有任何动作。而f.exec表示执行某个程序。f.menu表示激活某个子菜单。

图标管理器
如果桌面上的图标过多,用起来比较麻烦,我们这个时候可以用图标管理器来简化工作。TWM支持多个图标管理器,每个还可以有一列或者多列,比如你想把所有的XTerm类程序的图标都放在一个图标管理器中管理,你可以创建一个如下的管理器:

IconManager
{
"XTerm" "=100x5-10+10" 1
}

XTerm是窗口的类名(注),后面的参数表示管理器窗口的位置在屏幕的右上角,大小为100X5, -10+10表示它在屏幕上的位置,最后的1表示它只有1列。这样你所打开的所有XTerm类的程序(比如xterm)的图标都会被这个管理器管理。管理器中的图标缺省是按照窗口打开的顺序来排序的,如果你愿意,你也可以修改排序的方式。

有用的设定
TWM默认情况下,在建立新窗口时,需要用户指定窗口的位置,这个“特色“实在让人头疼,不知道TWM的作者当初的用意何在。还好,有参数可以关闭它,在.twmrc的最上面加入RandomPlacement即可,以后新打开的窗口就会自动的找一个位置了。

在.twmrc中加入AutoRelativeResize,然后你就可以拖动标题栏最右边的按钮来改变窗口大小了。在实际操作中,我发现,如果要缩小窗口,需要先向放大的方向拖动,然后再往缩小的方向拖动才可以。如果不加入这个参数,要想改变窗口大小,需要把鼠标移动到右下角才可以,不够方便。

AutoRaise。有些窗口,我们会经常用到它,比如XTerm类(注)的窗口。为了方便起见,我们在配置中加入

AutoRaise{“XTerm”}把你的鼠标移动到XTerm的窗口上,看到了吧,无须任何点击,窗口就会被放到最上层。

结尾
TWM并不是一个完美的窗口管理器,比如它在某种意义上说不够漂亮。但是每个窗口管理器都有它自己独特的地方,每个人都有可能爱上TWM,也许有一天你厌烦了别的管理器,你会尝试用一下TWM,以缓解一下审美疲劳。

顺便附上我的TWM配置文件:.twmrc

附注:类的概念,前面我有提到XTerm类,我做一下解释。X下有应用程序类这种说法,每个程序都属于一个类。比如:xterm是XTerm类中的一员,xclock和oclock都属于Clock类(也有可能xclock属于XClock类)。把应用程序分类的好处之一就是,对类的设置会涵盖对它成员的设置,比如对Clock配置,这将影响到所有Clock类的程序。不过UNIX有很多应用程序类都只有一个成员,如XLoad只有xload。在TWM下,你可以设置一个菜单的动作为f.identify,用它你可以看到每个窗口的信息,其中就有它的类信息。

XFCE之初体验

文:Charry

XFCE介绍
XFCE是一个轻量级的桌面环境,,它被广泛的运用于各种UNIX中,它非常的小巧,运行程序很快,节省系统资源。XFCE融合了UNIX哲学中的“模块化”和“可重用性”这两个极为重要的思想。它包含了很多的组件,而正是这些组件构成了整个XFCE的强大。这些组件都可以单独安装,你可以按照自己的需求,定制出自己的桌面环境。

XFCE的特点:

一个简单,易于使用的桌面环境。
完全通过鼠标的拖动和按键来控制等。
与CDE 相似的主面板,菜单,applets和应用launchers。
集成的窗口管理器,文件管理器,声音管理器, GNOME应用模块,和其他一些。
可配置界面的主题。
快速,轻便,高效:对于比较老的/旧的机器或带有很少内存的机器仍然很理想。

安装
下载安装包
首先去XFCE的官方站点:http://www.xfce.org下载你要安装的XFCE的安装包,我下载的是xfce-4.2.1.1-rpm-fdr-i386.tar.bz2,我的Linux发行版是CentOS4.1,网站没有提供对应的安装包,所以所以我选择了它的近亲,Fedora版本的包。

解开后,里面大概有如下几个文件:

dbh-1.0.22-1fdr.i386.rpm
gtk-xfce-engine-2.2.6-1fdr.i386.rpm
libxfce4mcs-4.2.1-1fdr.i386.rpm
libxfce4mcs-devel-4.2.1-1fdr.i386.rpm
libxfce4util-4.2.1-1fdr.i386.rpm
libxfce4util-devel-4.2.1-1fdr.i386.rpm
libxfcegui4-4.2.1-1fdr.i386.rpm
libxfcegui4-devel-4.2.1-1fdr.i386.rpm
xfcalendar-4.2.1-1fdr.i386.rpm
xfce4-appfinder-4.2.1-1fdr.i386.rpm
xfce4-iconbox-4.2.1-1fdr.i386.rpm
xfce4-icon-theme-4.2.1-1fdr.noarch.rpm
xfce4-mixer-4.2.1-1fdr.i386.rpm
xfce4-panel-4.2.1.1-1fdr.i386.rpm
xfce4-panel-devel-4.2.1.1-1fdr.i386.rpm
xfce4-session-4.2.1-1fdr.i386.rpm
xfce4-session-devel-4.2.1-1fdr.i386.rpm
xfce4-session-engines-4.2.1-1fdr.i386.rpm
xfce4-systray-4.2.1-1fdr.i386.rpm
xfce4-toys-4.2.1-1fdr.i386.rpm
xfce4-trigger-launcher-4.2.1-1fdr.i386.rpm
xfce-mcs-manager-4.2.1-1fdr.i386.rpm
xfce-mcs-manager-devel-4.2.1-1fdr.i386.rpm
xfce-mcs-plugins-4.2.1-1fdr.i386.rpm
xfce-utils-4.2.1-1fdr.i386.rpm
xfdesktop-4.2.1-1fdr.i386.rpm
xffm-4.2.1-1fdr.i386.rpm
xfwm4-4.2.1-1fdr.i386.rpm
xfwm4-themes-4.2.1-1fdr.noarch.rpm
xfprint-4.2.1-1fdr.i386.rpm

安装核心文件
首先安装gtk-xfce-engine-2.2.6-1fdr.i386.rpm,然后安装几个lib打头的包,如果你不知道安装顺序,也不要紧,因为如果你的顺序有误,系统会提示你需要先安装哪个包。所有的lib打头的包装完以后,我们再安装xfwm4-4.2.1-1fdr.i386.rpm。这个是XFCE的窗口管理器的包,这个装完后,其实我们就可先体验一下XFCE了。编辑你的目录下面的.xinitrc,内容如下:

#!/bin/sh

exec xfwm4

然后,在命令行中输入startx,你会发现这样其实也可以用。

安装其他组件
理论上,上面的步骤已经可以满足你使用X的需求了,但是,XFCE是一个轻量级的桌面环境,上面的步骤只是安装了它的窗口管理器(WM)。虽然我们完全可以用它的窗口管理器xfwm4来管理X,但毕竟还有些不太方便。那么下面,我们就需要把其他的一些包也安装上。

其他的包的安装方法同上。首先随便找一个包安装,如果可以安装,就安装下一个。如果提示它所倚赖的包没有,则先安装它所倚赖的包。过程非常简单,这里你最好建一个空目录,把安装过的包都移这个目录下,等所有的包都被移到这个目录下后,你的安装也就完成了,这样不至于搞不清哪些包安装过了,哪些没有安装,以免引起混乱。

使用
安装完成后,在命令行中输入switchdesk XFCE,回车后再startx就可以了。如果你不想改变默认的桌面环境,你也可以直接运行:startxfce4命令,不过这样运行和前者还是有少许不同,比如输入法可能不会被自动打开等。

XFCE的配置也是非常的简单,它本身也提供了大量的主题,非常的漂亮。你完全可以用鼠标点一点就可以定制出一个你所钟意的环境出来,这一点对于初学者和懒人是非常有吸引力的。下面附上我暂时使用的类XP界面的风格。点击这里查看。

如果你系统中有GNOME或者KDE,建议你不要把它们删除,因为,我们可以在XFCE下使用它们附带安装的应用程序。

键盘快捷方式:

移动窗口有一个比较方便的方法,你不需要选中标题,而只要按住Alt键,就可以用鼠标选中窗口中的任何位置移动窗口了,这个和TWM是类似的。
最大化窗口:Alt+F5,再按一次还原。
全屏幕窗口:Alt+F11,再按一次还原。
改变窗口大小:Shift+Alt+方向键
隐藏窗口:Alt+F8
改变窗口的层次:Shift+Alt+PageUp/PageDown
上面的这些快捷键,如果你不太习惯,你可以在“窗口管理器”的配置项中配置。

其他
XFCE应该和GNOME以及KDE归属于一类,不过它比前两者速度快多了,但还是比直接使用窗口管理器慢了些。如果你厌倦了GNOME和KDE的慢,何不尝试一下XFCE呢,并且它在美观方面丝毫不比前两者差「其实我倒觉得XFCE已经胜过前两者」。

想要了解XFCE的更多更详悉的知识么,不要去到处问人或者到处去搜教程了,看它的帮助文档吧,在那里你可以找到你要的一切,如果你的英文不行的话,找个电子词典。

酷炫issue及其他

文:Charry

上过BBS的人都知道,有很多朋友的签名档非常的炫目,花花绿绿、闪闪烁烁,煞是好看。其实那些只是加入了控制字符的ASCII文档。大家知道issue和issue.net文件是Linux下的登陆提示文件,我们可不可以把这个文件也做的像BBS的签名档一样呢?当然可以。

如果只是要编辑你的BBS签名档,你大可不必看这篇文章,因为有很多非常方便的程序可以辅助你创作,比如BBSEdit就是其中之一,但是BBSEdit编辑的文件,并不一定可以做为issue。下面我介绍一下如何用vi编写这样的文档。

首先看一个彩色文字的例子:

^[[40;32mcharry.org^[[0m

其中charry.org是文本的内容,其他的是控制字符。注意这里控制字符可并不是你所看到的那样。开头的‘^[’的输入方法是:先按Ctrl键+V,然后按Esc键。第二个‘[’,就是简单的括号,你直接输入就可以了。后面是字符的颜色,文字后面的控制字符的作用是把文字的颜色还原。把这些保存好了,用more看一下,是不是绿色的’charry.org’:),如果你在X下,你得用XTerm,其他的Term可能不支持。

文字颜色的格式为:背景色;前景色m。在上面的例子中,40表示黑色,32表示绿色。常用的颜色代码有:

^[[40;32m 黑底绿字
^[[40;33m 黑底黄字
^[[40;37m 黑底白字
^[[40;36m 黑底青字
^[[41;32m 红底黄字

你注意到最后的的‘^[0m’了吗?这个是用来还原颜色控制的,你最好不要省了它,否则会影响到后面的文字的颜色。好了,你已经学会了编辑彩色文本了,把它保存为issue,然后放到/etc下,然后,退出到登陆界面,看到了吧。如果想从网络登陆的用户也看到这个彩色的画面,你用同样的方法编辑issue.net,当然最终还得看网络用户的终端是否支持。

下面我们看一下如何编辑闪烁的文字,举个例子:

^[[0;5mcharry.org^[[0m
^[[1;5mcharry.org^[[0m
^[[4;5mcharry.org^[[0m
^[[7;5mcharry.org^[[0m
^[[31;1;5mcharry^[[0m
^[[32;1;5mcharry^[[0m
^[[33;1;5mcharry^[[0m
^[[34;1;5mcharry^[[0m
^[[35;1;5mcharry^[[0m
^[[36;1;5mcharry^[[0m
^[[37;1;5mcharry^[[0m
^[[31;4;5mcharry^[[0m
^[[31;7;5mcharry^[[0m

你注意到上面代码中的数字5吗?它就是控制字符闪烁的。比如最后一行,你可以猜测到,前面的31表示前景色,中间7表示背景色,最后的5表示闪烁。你也可省略前景色,比如前四行。

上面的这些规则,是我从BBS Edit生成的代码中推断出来的,也许会有些错误,恳请大家指正。这里附上稍前例子的代码文件,点这里下载。同时,也附上我的issue文件,你可以把它保存在你的/etc下,稍作修改,体验一下彩色且闪烁的issue。上面的两个文件,我之所以以sh结尾,是为了方便您的下载,如果您要保存issue.sh,请改名为issue。

最后提示大家一下,为了让你的issue看起来更漂亮,你可以去各大BBS的ASCII艺术版拷贝一些ASCII图画,比如我的issue中的绿鸽子就是从smth拷贝来的。还有,在Makefile、Shell Script等文件中都可以使用彩色或者闪烁的效果,您可以用它来做一些醒目的提示信息。

我的ImageMagick使用心得

文:Charry

在认识ImageMagick之前,我使用的图像浏览软件是KuickShow,截图软件是KSnapShot,这两款软件都是KDE附带的软件,用起来也是蛮方便的。在一次偶然的机会中,我遇到了ImageMagick,才发现Linux竟然有如此功能强大的图像软件。

你将会发现,大部分的操作,你只要在终端下动动键盘即可,省得你用鼠标点来点去。

下面,我对ImageMagick的主要功能做一个简单的介绍,其中覆盖的大都是人们常用的一些功能,如果你要全面的了解它的知识,你可以看看它的man手册。

convert

convert顾名思义就是对图像进行转化,它主要用来对图像进行格式的转化,同时还可以做缩放、剪切、模糊、反转等操作。

格式转化
比如把 foo.jpg 转化为 foo.png:

convert foo.jpg foo.png

如果要想把目录下所有的jpg文件都转化为gif,我们可借助于shell的强大功能:

find ./ -name “*.jpg” -exec convert {} {}.gif \;

转化后的gif名称为 *.jpg.gif ,这样看起来不太自然,没关系,我们可以再来一步:

rename .jpg.gif .gif *.jpg.gif

本来,我想在find的时候,用basename来取得不带后缀的文件名的,这样就不会形成.jpg.gif这种丑陋的名子了,可是不知道为什么,就是不行,如果你知道的话,告诉我

或者,你也可用shell script来完成上述的操作:

for i in *.jpg
do
convert $i `basename $i .jpg`.gif
done

我们还可用mogrify来完成同样的效果:

mogrify -format png *.jpg

上面命令将会把目录下面所有的jpg文件转化为png格式。

convert还可以把多张照片转化成pdf格式:

convert *.jpg foo.pdf

大小缩放
比如我们要为一个普通大小的图片做一个缩略图,我们可以这样

convert -resize 100×100 foo.jpg thumbnail.jpg

你也可以用百分比,这样显的更为直观:

convert -resize 50%x50% foo.jpg thumbnail.jpg

convert会自动地考虑在缩放图像大小时图像的高宽的比例,也就是说着新的图像的高宽比与原图相同。

我们还可以批量生成缩略图:

mogrify -sample 80×60 *.jpg

注意,这个命令会覆盖原来的图片,不过你可以在操作前,先把你的图片备份一下。

加边框
在一张照片的四周加上边框,可以用 -mattecolor 参数,比如某位同志牺牲了,我们需要为他做一张黑边框的遗像,可以这样:

convert -mattecolor “#000000″ -frame 60×60 yourname.jpg rememberyou.png

其中,”#000000″是边框的颜色,边框的大小为60×60

你也可以这样加边框:

convert -border 60×60 -bordercolor “#000000″ yourname.jpg rememberyou.png

在图片上加文字

convert -fill green -pointsize 40 -draw ‘text 10,50 “charry.org”‘ foo.png bar.png

上面的命令在距离图片的左上角10×50的位置,用绿色的字写下charry.org,如果你要指定别的字体,可以用-font参数。

模糊
高斯模糊:

convert -blur 80 foo.jpg foo.png-blur

参数还可以这样-blur 80×5。后面的那个5表示的是Sigma的值,这个是图像术语,我也不太清楚,总之,它的值对模糊的效果起关键的作用。

翻转
上下翻转:

convert -flip foo.png bar.png

左右翻转:

convert -flop foo.png bar.png

反色
形成底片的样子:

convert -negate foo.png bar.png

单色
把图片变为黑白颜色:

convert -monochrome foo.png bar.png

加噪声

convert -noise 3 foo.png bar.png

油画效果
我们可用这个功能,把一张普通的图片,变成一张油画,效果非常的逼真

convert -paint 4 foo.png bar.png

旋转
把一张图片,旋转一定的角度:

convert -rotate 30 foo.png bar.png

上面的30,表示向右旋转30度,如果要向左旋转,度数就是负数。

炭笔效果

convert -charcoal 2 foo.png bar.png

形成炭笔或者说是铅笔画的效果。

散射
毛玻璃效果:

convert -spread 30 foo.png bar.png

漩涡
以图片的中心作为参照,把图片扭转,形成漩涡的效果:

convert -swirl 67 foo.png bar.png

凸起效果
用-raise来创建凸边:

convert -raise 5×5 foo.png bar.png

执行后,你会看到,照片的四周会一个5×5的边,如果你要一个凹下去的边,把-raise改为+raise就可以了。其实凸边和凹边看起来区别并不是很大。

其他
其他功能都是不太常用的,如果你感兴趣的话,可以看它的联机文档

import

import是一个用于屏幕截图的组件,下面列出的是我们常用的功能,其他的功能,你参考它的man好了。

截取屏幕的任一矩形区域

import foo.png在输入上述的命令后,你的鼠标会变成一个十字,这个时候,你只要在想要截取的地方划一个矩形就可以了

截取程序的窗口

import -pause 3 -frame foo.png

回车后,用鼠标在你想截的窗口上点一下即可。参数-frame的作用是告诉import,截图的时候把目标窗口的外框架带上,参数-pause的作用很重要,你可以试着把它去掉,对比一下,你会发现,目标窗口的标题栏是灰色的,pause就是让import稍微延迟一下,等你的目标窗口获得焦点了,才开始截图,这样的图才比较自然。

截取一个倾斜的窗口
如果想让你的截图比较cool,你可以把截取一个倾斜的窗口,方法如下:

import -rotate 30 -pause 3 -frame foo.png

截取整个屏幕

import -pause 3 -window root screen.png

注意,暂停了3秒钟,你需要在3秒钟内切换到需要截取的画面噢。

display

display应该是我们使用的最为频繁的图像处理软件了,毕竟,还是看的多

显示图片

display foo.png

如果你要显示多个文件,你可以使用通配符

display *.png

幻灯片

display -delay 5 *

每隔5个百分之秒显示一张图片

一些快捷键

space(空格): 显示下一张图片
backspace(回删键):显示上一张图片
h: 水平翻转
v: 垂直翻转
/:顺时针旋转90度
\:逆时针旋转90度
>: 放大
<: 缩小
F7:模糊图片
Alt+s:把图片中间的像素旋转
Ctrl+s:图象另存
Ctrl+d:删除图片
q: 退出

其他

ImageMagick还提供有丰富的编程接口,比如,你可以用php来调用它,用ImageMagick来生成验证码图片,效果非常棒。

ImageMagick还有一个小工具identify,它可以用来显示一个图片文件的详悉信息,比如格式、分辨率、大小、色深等等,你都可用它来帮你的忙。

如果你对命令行不太熟悉,你也可以在图片上单击,你会发现,通过鼠标你也可以完成图像的编辑。

ImageMagick的网站:www.imagemagick.org。这里是ImageMagick加工过的图片的例子。

Update an app in a matter of time & Solution (draft)

类似MSN, QQ等, 如果要在短时间内更新大量的客户端程序, 这个过程对于服务器端的压力是非常的大的, 在没有更多服务器的情况下, 下载策略尤为重要.

暂时拟定一个方案:

Auto Update

Agloco – Own The Internet

About AGLOCO

Do You Realize How Valuable You Are?
Advertisers, search providers, and online retailers are paying billions to reach you while you surf. How much of that money are you getting?

You Deserve A Piece of the Action
AGLOCO gets paid by companies to reach our Members through our Viewbar™ software.We give that money back to you.

Build the Community, Make More Money
Through our Referral Program, we reward those who are helping to build this Global Community. The bigger the community, the more money AGLOCO makes for its Members.

What’s the Catch?
No catch. Sign up, refer your friends, download the free Viewbar™ software and surf the Internet as you normally would.

Privacy Counts.
Your information will never be sold, rented,or shared with anyone else. Bulletproof privacy is a core commitment of AGLOCO.

Visit www.agloco.com for more information!

My Sniffer

最近写的一个Sniffer小程序,可以捕获当前用户上了哪些网。
(设置混杂模式部分参考网络文章,SocketInfo来自codeproject.com)

下载:http://www.mpfive.com/m/myapps/MySniffer.rar

使用方法:
1:拷贝下面的文件到你的工程下

HttpParser.cpp
SocketInfo.cpp
SocketInfo.h
define.h
SnifferManager.h
Sniffer.cpp
Sniffer.h
HttpParser.h
SnifferManager.cpp

2:在程序中 include SnifferManager.h
3:在你的程序中开启Sniffer的地方加入:

CSnifferManager *pSM = CSnifferManager::GetInstance();
pSM->StartSniffers();

4:程序关闭的地方加入:

this->ShowWindow(SW_HIDE);if(CSnifferManager::DestroyThreads())

       CSnifferManager::FreeInstance();

else

{

       PostMessage(WM_CLOSE);

       TRACE("// %s:%dn", __FILE__, __LINE__);

       return;

}

比如在OnClose中加入

void CMySnifferDlg::OnClose()

{

       this->ShowWindow(SW_HIDE);       if(CSnifferManager::DestroyThreads())

              CSnifferManager::FreeInstance();

       else

       {

              PostMessage(WM_CLOSE);

              TRACE("//%s:%dn", __FILE__, __LINE__);

              return;

       }

CDialog::OnClose();

}

Page 20 of 21« First...10...1718192021

Switch to our mobile site