Google 拼音词库转 Vimim 词库脚本

我写了一个将 Google 拼音输入法词库转换为 Vimim 词库的脚本,贴在这里,希望对大家有用。

#!/bin/bash
iconv -f gbk -t utf-8 "$@" | sed -e 's/ //g;s/^M$//g' | awk 'NR==1 {a=$3; printf "%s %s",$3,$1; next; }{ if($3==a) printf " %s",$1;else printf "n%s %s",$3,$1; a=$3;}' | sort  -d

(注意:上面那个 ^M 在 vim 中的输入方法是 Ctrl+vm。)

使用方法:
1. 在 Google 拼音输入法“属性设置->词典”选项页,将 Google 输入法词库导出为 .dic 文件,例如 google.dic。
2. 将 google.dic 拷贝到 Linux 中,或者使用 Cygwin,进入到包含 google.dic 的目录。
3. 下载本邮件附件 google2vimim,给它增加可执行权限 chmod u+x google2vimim。
4. ./google2vimim google.dic > vimim.pinyin.txt,得到的 vimim.pinyin.txt 就是符合 Vimim 规范的词库。

PS: 是的,我忘记了 r 的作用,所以上面脚本可以完全替换为:

#!/bin/bash
iconv -f gbk -t utf-8 "$@" | sed -e 's/ //g;s/r$//g' | awk 'NR==1 {a=$3; printf "%s %s",$3,$1; next; }{ if($3==a) printf " %s",$1;else printf "n%s %s",$3,$1; a=$3;}' | sort  -d

脚本的最新版本下载地址可以是:http://share.solrex.org/scripts/google2vimim

ACM 图灵奖演讲论文集

今天尝试了一下 JabRef 论文数据库的 HTML 导出,将我搜集的 ACM Turing Award Lectures 的 BibTeX 信息输出到了这个页面

由于版权问题,无法给出 PDF 文件下载,但是每篇论文后面都给出了 DOI 链接,有 ACM 数据库访问权限的用户可以自己去下载。如果您是我的朋友且对此感兴趣且无法访问 ACM Portal,可以在 IM 上联系我索取 PDF 包(为个人学习、研究或者欣赏使用),Email 是不行的,因为太大了。

事实上,这个页面的大部分文章是收入《ACM Turing Award Lectures -- The First Twenty Years : 1966 to 1985》中的,中国的苏运霖教授翻译后出版了《ACM图灵奖演讲集:前20年》。

再为 JabRef 做个广告。我不知道其它的论文管理工具能否做这样的事情,不过 JabRef 输出的 HTML 页面真的很不错。我以后要是成为学术男,就用它来发布我发表的论文列表 :)

用 Vim 对矩阵转置

前两天某个同学在科苑星空 BBS 上问到了一个有趣的问题:如何在 Vim 中对矩阵进行转置?

我当时想,转置不就是行列互换嘛,awk 可以取一列,那么拿出来每列然后打印成一行不就好了?类似于:

echo `awk '{printf "%s ",$1}' file`
echo `awk '{printf "%s ",$2}' file`
echo `awk '{printf "%s ",$3}' file`
...

本来觉得用 bash 写一个循环语句就可以了,但是怎么也尝试不出来如何替换那个 $1, $2, $3...就想没办法了只能搞 eval 了。但是我觉得这种事情 Unix 前辈们应该干过,所以就搜了一斧子,果然搜到了一个 AWK 程序,《Sed & Awk》 Ch13.9 Perform a Matrix Transposition:

#! /bin/sh
# Transpose a matrix: assumes all lines have same number of fields

exec awk '
NR == 1 {
    n = NF
    for (i = 1; i <= NF; i++)
        row[i] = $i
    next
}
{
    if (NF > n)
        n = NF
    for (i = 1; i <= NF; i++)
        row[i] = row[i] " " $i
}
END {
    for (i = 1; i <= n; i++)
        print row[i]
}' ${1+"$@"}

哈,除了觉得这样内存占用可能比较大之外,这可是一个相当不错的程序。

在 Vim 中调用就很简单了,将上面脚本保存成 transpose,加上可执行属性放在某个可执行路径下(比如 ~/bin),然后在 Vim 编辑矩阵文件时 :%!transpose 就可以了。

PS: 另外,在查证矩阵的“转置”应该写成“转秩”还是“转置”时,我在 Wikipedia 发现一个很有意思的东西:

矩阵用词
在中国大陆,横向称为“行”,纵向称为“列”。 在台湾,横向称为“列”,纵向称为“行”。

天那,要是用汉语和台湾同学讨论矩阵的话,该有多痛苦呀!

Vim 中文输入法插件 Vimim

有位朋友在 TopLanguage 讨论组里向我推荐了一款非常 cool 的 vim 输入法插件:vimim,您可以从 Vim 插件页下载 Vimim,演示程序和码表可以在 Vimim Googlepage下载。Vimim 作为一个 Vim 插件项目起始于 09 年一月份,可能知晓它的用户还比较少,所以我觉得有必要向大家推荐一下这款优秀的 Vim 插件。

作为中文用户,在 Vim 里输入中文往往是一件非常痛苦的事情,最别扭的地方在于使用中文输入法时进行 Vim 编辑模式之间切换比较麻烦,还容易误操作,这也是我一般不使用 Vim 编辑中文文档的原因。

简单地来说,这个插件使得你在 Vim 里直接输入中文(使用 Ctrl+\ 开启选词单而不是通常输入法使用的空格键)。当你不按 Ctrl+\ 时,你就是在输入英文字母,这样就避免了中英文输入法切换的问题。这个插件的使用方法和演示在 Demo 页展示得很清楚,这里我就不再赘述了。

不过在使用上我仍然面临着一个小问题,Vimim 目前提供的双拼码表是基于微软双拼的,而我习惯于智能 ABC 双拼,所以我在 Vimim 的讨论组里抱怨了一下:《希望双拼音节映射独立于码表》。

------------------------------ 我是分割线 ------------------------------

我的 BSP yo2.cn 服务器宕机从 09 年 5 月 12 日起延续了将近三天,期间可能导致部分用户遇到无法访问问题,请见谅。

NetworkManager 任重而道远

Ubuntu 8.04 以前的 NetworkManager(nm-applet) 还差强人意,好歹还能通过它来配置一下网络。中间跳过了 Ubuntu 8.10,而新的 Ubuntu 9.04 的 NetworkManger 则让我不知道该如何使用。

我依稀记得原来使用 nm-applet 对网络进行配置后,设置会被保存在它应该在的配置文件中,比如 /etc/network/interfaces, /etc/resolv.conf。但是我完全不知道目前的 nm-applet 会将网络配置保存在什么地方。无论是有线网络还是无线网络,无论是 DHCP 还是固定 IP,我一次都没有使用 nm-applet 配置成功过。有线网络还好,我很熟悉,直接写到配置文件中不费什么事情。最近忽然遇到要配置无线网络,一懒,就试试 nm-applet。用 nm-applet 可以扫描到无线网络,但是填入密钥就是无法完成验证,没办法只好转而用 iwconfig 命令配置。按说直接敲命令应该是最靠谱的事情,依然无法凑效,最后才发现原因——在使用 iwconfig 配置时,一定要 disable nm-applet 对无线网络的管理!

我觉得目前 nm-applet 可能在做一些革新性的改变,大概是想简化网络的配置方法增强易用性吧。但是一些不愉快的经历让我觉得现在的 nm-applet 是一个相当不靠谱的东西,所以我只好在所有的 Ubuntu 里禁用了 nm-applet,还是直接修改配置文件靠谱呀!

谈谈 CoolSIP

说明:本文有一些内容是基于本人的合理猜测。如果您知道更多细节,请不吝指出,但最好请提供事实依据,而不是仅仅指责我主观臆测,不尊重事实。我不知道事实,才会猜测,我如果知道事实,就不用猜测了。

CoolSIP 是清华大学信息网络工程研究中心建设的 IPV6“下一代互联网应用示范平台”提供的一个在 IPV6 网络上运行的 VoIP 软件,该软件提供了主机间通话和主机拨打传统 PSTN 电话的功能。目前该服务仅提供给 IPV6 教育和科研网用户使用,注册该服务需要 edu.cn 或者 ac.cn 后缀的大学或科研机构电子信箱。测试期间是免费的,但需要用在线时长换取话费。

我这里呢,就是说几句闲话,谈谈我对这个软件和服务的看法。

1. CoolSIP 的知识产权问题

我对 CoolSIP 软件的结构并不清楚,但基本可以肯定的一点是 CoolSIP 使用了开源的 osipeXosip。不用逆向工程该软件,查看 CoolSIP 帐户信息的保存位置就可以知道,CoolSIP 的帐户信息保存在 C:Documents and SettingsUsernameeXosip 目录下。

由于 osip 遵从的是 LGPL 协议,eXosip 遵从的却是 GPL 协议,那么从我有限的对知识产权的了解(关于 LGPL 和 GPL 可以参考这里),CoolSIP 也应该遵从 GPL 协议(对这点有问题您可以在评论中指出)。而目前来看,清华大学信息网络工程研究中心并没有发布 CoolSIP 的源代码,而且 CoolSIP 貌似将 osip 和 eXosip 库静态链接进了程序里(大概有隐藏的意图),那么可以认为他们并不认可 GPL 许可证——不过他们还没有错误地在安装文件中包含一份私有版权声明。

另外,http://ngmylife.wirelesslan.edu.cn/multimedia.html 页面中说:“当eTone出现无法登录的问题时,可以使用CoolSIP试试。”可以猜测 eTone 和 CoolSIP 有非常紧密的关系,而 eTone 又是一款流行 VoIP 软件“快门” 的前身,那么我对“快门”软件的版权也持谨慎地怀疑态度——不过我更倾向于快门不大可能犯这种错误。

2. CoolSIP 本身的程序设计问题

CoolSIP 是一个极不成熟的软件,使用过程中居然会出现程序错误,点“忽略”才能继续运行的情况;由于其版本历史仅仅显示到 2007 年 9 月 3 日 ,我想其对 SIP 协议的实现是存在问题的,例如在拨打一个通话中的电话号码时,CoolSIP 不会提示对方正在通话中。总之,该软件除了是 sip6.edu.cn 提供的 VoIP 服务的官方客户端以外,几乎一无是处。

3. CoolSIP 的 VoIP 服务

虽然 CoolSIP 客户端没有什么优点,但是 sip6.edu.cn 的 VoIP 服务器端对 SIP 协议的实现应该还算很标准的。由于 CoolSIP 使用了 eXosip 库,那么我就尝试使用另一款使用 eXosip 库的遵从 GPL 协议的开源软件 Linphone 尝试连接了一下 CoolSIP 服务器,居然能正常连接。

并且 Linphone 显示出比 CoolSIP 更友好的用户体验,例如:Linphone 支持 Windows 和 Linux 系统,更完整地支持 SIP 协议,支持自定义电话联系人列表,多帐户同时在线,通话统计记录显示,多种音频编码,更好的视频支持。像上面提到的 CoolSIP 不支持的“您拨打的电话正在通话中”提示,也可以在 Linphone 中听到。

官方提供的专门客户端 CoolSIP 竟然不如一个开源的通用客户端 Linphone,这大概是“下一代互联网应用示范平台”服务的一个笑柄吧。

PS: 使用 Linphone 登录 CoolSIP 的服务器,需要 libexosip2 和 libosip2 版本大于 3.3,我的经验表明 3.1 会出现较多问题。推荐手动依次编译 libosip2->libeXosip2->linphone。Windows 版的可执行安装程序包含了这两个库,所以不存在库版本低的问题。

请测试 JabRef 2.4.3 中文版

JabRef 2.4.3(未发布)对中文用户的一大改进就是开始支持中文界面。中文化的工作本来是由另外一个朋友和我共同来做,但由于他工作繁忙,后来就是我一个人在进行了。

目前我已经翻译了大约 80% 的样子,菜单和选项窗口基本上都翻译完了,还剩下一些少用的选项和状态栏信息。因为水平有限,也并不能遍用 JabRef 的所有 feature,很多地方可能翻译的不合适,所以想找大家帮忙测试一下,给提些意见。(JabRef 使用问题请参考“文献管理软件 JabRef 快速入门”)

您可以在 http://share.solrex.org/ibuild/ 下载到编译好的 jar 文件,jar 文件以 JabRef-2.4.3-Chinese-编译日期.jar 格式命名。您可以使用

java -jar JabRef-2.4.3-Chinese-编译日期.jar

来执行它。

如果您已经安装 JabRef 2.4.2 版,您可以覆盖 JabRef 2.4.2 的 jar 来实现直接从原有快捷方式启动 JabRef 2.4.3,方法为:

Windows:

首先到您的 JabRef 2.4.2 安装目录,我们假设为:C:Program FilesJabRef,备份 JabRef-2.4.2.jar(修改文件名为另外的名字即可),然后将 JabRef-2.4.3-Chinese-编译日期.jar 拷贝到安装目录 C:Program FilesJabRef 下,修改文件名为 JabRef-2.4.2.jar,然后再执行 JabRef.exe 就调用的是 2.4.3 版本的 JabRef 了。

Linux:

sudo mv /usr/share/java/JabRef-2.4.2.jar /usr/share/java/JabRef-2.4.2.jar.bak
sudo cp JabRef-2.4.3-Chinese-编译日期.jar /usr/share/java/JabRef-2.4.2.jar
当然,您也可以修改软链接 /usr/share/java/jabref.jar 指向 JabRef-2.4.3-Chinese-编译日期.jar

启动 JabRef 2.4.3 之后,请到 Options->Preferences->General->Language 中选择 Chinese,然后重启 JabRef 才能进入中文界面。目前只有简体中文,但是我已经提请维护者增加并区分简体中文和繁体中文,大概会在以后添加。

您可以通过以下途径将有关翻译的意见反馈给我:
1. 评论这篇文章,任何人。
2. 在我侧栏的 Google Friend Connect 留言簿留言,需要 Google 或 OpenID 帐户。
3. 给我发送 Email,我的联系方式可以在导航栏的“关于我”链接中找到。

目前所有翻译都是我的工作,所以所有错误都该归我一个人身上。有您的帮助,我才能不将这个错误带给更多人,我将很感激您为此付出的时间和努力。

下面是中文版的一个截图:
JabRef 中文版界面

关于 eYouIPB 和 CASNET

我不知道为什么在干正事的时候想写这个东西,大概也算是一种强迫症吧,想起来某件事情就总觉得有义务马上去做。那么就赶快写完吧。

这篇文章是写给有心人看的,如果您不知道 eYouIPB 和 CASNET 是什么东西,那么您可以无视以下内容。

eYouIPB 是中科院研究生院目前使用的网关登录客户端,但只能在 Windows 下使用。我用 Python 写的一个小软件 CASNET,就是一个 Linux 下的登录替代品,不过目前它也可以支持 Windows。昨天有同学发信问我有关科苑网关登录的问题,我顺便在这里做个笔记吧。

1. 有关流量统计。目前 CASNET 不支持像 eYouIPB 那样的实时的流量统计功能,因为我觉得没有必要。科苑上网是按照流量收费的,而这个流量是指网关服务器统计的流量,而不是客户端统计的流量,所以客户端的实时精确流量统计没有太大意义。

eYouIPB 带有实时的统计流量功能,但这却造成了一些问题。eYouIPB 使用 WinPcap 库做流量统计,而且这个库的版本比较低。假如系统里安装了新的 WinPcap 库,例如 Wireshark(Ethereal) 网络监听程序就会安装 WinPcap 库,那么 eYouIPB 就无法工作了。WinPcap 是一个非常强大的抓包库,而 eYouIPB 仅仅使用了其中的流量统计功能,实在很让人费解它为什么要使用 WinPcap 库并且将它的 dll 包含在自己的软件中。我想应该有更简单的办法做流量统计——如果非做不可的话。

2. 有关登录方式。科苑上网有两种登录方式:一种是 eYouIPB,使用私有的协议;一种是网页登录,使用 https 表单交互。由于 eYouIPB 协议私有,CASNET 只能模拟网页登录的方式,所以您会发现 CASNET 登录速度会比 eYouIPB 慢一点儿,因为它有一个完整的 https 安全协商和交互过程,而 eYouIPB 只需要几次简单 tcp 数据包交换就可以了。不过因为同在一个局域网内,这个速度还是在可以忍受的范围之内。

3. 有关安全性。去年我曾经研究过 eYouIPB 的协议,发现还是相对简单的。我记录的笔记已经丢失,但大概是这个样子的(应用层):“首先 eYouIPB 向服务器发起一个连接,服务器应答可以连接,然后 eYouIPB 将用户名和域发送给服务器,服务器看上去会返回一个密钥,然后 eYouIPB 使用该密钥用某种加密算法加密用户口令(或者还有其它信息)发送给服务器,服务器验证口令,回应是否可以登入。”

从该协议的实现来看,安全性有限。一个是用户可以通过不停地登入登出收集明密文对,另外反汇编 eYouIPB 看起来也不是那么困难,尤其是可以使用 Winsock 32 的 API 来定位负责加密的代码段。一旦加密算法被了解,窃听 eYouIPB 的数据包就能获得用户的口令,那么帐户就可以被窃取。所以相比而言,https 的登录方式更值得信任。

PS: 今天顺便搜索了一下,有个朋友曾经对 1.03 版本的 eYouIPB 协议进行过分析,我感觉和 2.0 版本差距不大。而且这个人完全用 C 实现了 eYouIPB 1.03 的加解密函数如果他仅仅凭逆向工程做出这个结果的话,我是相当地佩服的(因为我花了一整天的工夫也没有做出来一点儿东西)。如果当初我能看到这个结果的话,说不定就有 clue 去模拟 eYouIPB 的协议而不是使用 https 方式了,现在是懒得再去钻研这个东西了。只是不知道 2.0 是否还在使用同样的加解密函数,如果仍在使用的话,那么对有心人来说这个口令保护措施基本上算是不存在了。

4. 有关软件运行速度。这个比较主要是 Windows 平台下,CASNET 是用 Python 脚本写成的,而看起来 eYouIPB 使用 VC++ 写的,在软件的大小和运行速度上 eYouIPB 要显然优于 CASNET。由于 CASNET 的 Windows GUI 版需要额外的 GTK 运行时库和 Python 库支持,大概会加载到内存中 16M 左右的数据,不过这些东西运行时真正用到的不多,所以有些部分除了初始化时,其它时候很可能是驻留在交换区中,所以还是可以接受的。

在 Linux 平台下,因为 GTK 和 Python 库是被非常广泛使用的,所以 CASNET 并不需要额外占用很多内存。

之所以会发布 Windows 版,是因为相比 eYouIPB 而言,Wireshark 对我更重要,所以我无法使用 eYouIPB,而又讨厌网页登录时每次都要选择下拉菜单。再加上顺便看看 PyGtk 在 Windows 下的表现如何。但比较讽刺的是,Windows GUI 版的 CASNET 下载量要超过其它版本加一起的下载量,看来它的存在还是对某些用户有一些帮助。

5. 有关软件功能。eYouIPB 的功能已经被锁定了很多年,但是 CASNET 一直在根据用户的需求增加或者删减某些功能,例如余额不足提醒,断线自动重连,一键切换登录模式等。还有一个未发布的功能是关机自动下线。这是一个我一直想实现的功能,因为当用户关机忘记离线时,恰好分配到原 IP 的用户就可以使用该帐号,造成流量被窃取。这个功能在 Linux 版本上已经基本实现,但是 Windows 版本仍然没有找到可用的方法实现。

FBI 终端登录主题

FBI Terminal KDM&GDM Greeter Theme

慵懒阳光下的北京虽然还有些乍暖还寒,随着穗穗掉落的杨花,春天还是到了。在这样美好又带有一点儿遗憾的春日里,我这样的宅男依然龟缩在带有落地窗的宿舍,凝望着楼下长长的北京正负电子对撞机,考虑我被辐射挂掉的可能性有多大。

下面是这样对生命深刻地思考之余的一个副产品:FBI 终端登录主题。某日看到了 Windows 下有一个很酷的 FBI Terminal 登录主题,在 gnome 和 kde look 搜索一下未果后,就决定自己做一个,产生了这个东西。谁知道在我费劲地搞出来它之后,才搜索到有类似的 Gnome 主题,不过我可以自我安慰的是它不能很好地支持我的电脑分辨率。

这是一款 Linux 下 KDE4 和 GNOME 桌面的登录欢迎主题,安装并选择它作为登录主题后,欢迎界面就会变成类似于 FBI Terminal 的样子,如图所示。其实我并不知道真正的 FBI Terminal 是什么样子,只是按照 Windows 下某款 FBI Terminal 主题进行的修改。不过依然很酷,是吧?

这款主题可以支持 KDM4 和 GDM,没有在 KDM3.x 进行过测试。目前这个主题可以支持三种分辨率:1024x768, 1208x800, 1280x1024,使用其它分辨率的用户可以按照宽高比选择相近的比率主题。

FBI 终端登录主题 for Linux 的下载页面是:http://share.solrex.org/misc/fbi_theme

另外,Happy April Fool's Day! :)

Kubuntu 9.04 KDE4 问题

这些问题也未必是 Kubuntu 的问题,很有可能是 KDE4 本身的问题。

1. .gtkrc-2.0-kde4 实际不能用。

GTK 的程序在 KDE4 下显示太难看了,尤其是菜单那块,整整突出来一个方块。Firefox 和 Thunderbird 还好,可以更换个主题啥的。像 Pidgin, scim 之类,那个难看呀,和 KDE4 的主题一点儿都不搭。

都说在 System Settings->Appearance->GTK Styles and Fonts 中选择 Use my KDE style in GTK applications 就可以了(需要先安装 gtk-qt-engine-kde4),但我发现一个很奇怪的问题。选择了该选项之后,是会在家目录下新建一个名为 .gtkrc-2.0-kde4 的文件,里面是 GTK 主题的设置:

# This file was written by KDE
# You can edit it in the KDE control center, under "GTK Styles and Fonts"

include "/usr/share/themes/Qt4/gtk-2.0/gtkrc"

style "user-font"
{
    font_name="DejaVu Sans"
}
widget_class "*" style "user-font"

gtk-theme-name="Qt4"
gtk-font-name="DejaVu Sans 9"

systemsettings 还提示需要重新登录 KDE 之类的话。但是一旦重新登录,这个 .gtkrc-2.0-kde4 就会被删除,无论把该文件的权限设置成啥样,然后 GTK 程序还是老样子。真的搞不清楚是谁跟这个文件有仇,见了它就删。

我登来登去实在没辙了,干脆直接把它改名为 .gtkrc-2.0,没想到 GTK 程序的主题立马 OK,根本不用重新登录。你说 KDE4 的方法和提示这不是害人吗?

PS: 我在另外一台电脑上发现,当先装 Ubuntu,再安装 kubuntu-desktop 时,那个 .gtkrc-2.0-kde4 是有作用的。

2. 用户帐户图片无法更改。

在 System Settings->About Me 中,提示说 Click to change your image,点击根本没用,会弹出来一个:Your administrator has disallowed changing your image. 甚至使用 root 权限,更改 root 用户的 image,也是这个提示。而 KDE4 目前没有提供一个像以前版本的 kcontrol 中进入管理员模式的按钮,所以我实在不知道该怎么办了。这个错误好像在 SUSE, Fedora, Gentoo 的邮件列表中都有报告,没看到有可用的解决方法。而且这个提示也太恶心,谁是 Your administrator?root 的 administrator 是谁?在哪里 disallowed 的?是 user privilege 不够吗?我深深地怀疑是不是 KDE4 目前没有实现这个东西,搞个提示唬人的。

PS: 我知道本文涉及到了 Linux 世界的两大战争:发行版之争和桌面系统之争,评论请就事论事,不要涉及这两点。

Patch for Libjingle with GCC 4.2.4 on Ubuntu

It is a svn diff result, not a patch, actually.

So, what is Libjingle? Quoted from http://code.google.com/p/libjingle/:

Libjingle, the Google Talk Voice and P2P Interoperability Library, is a set of components we provide to interoperate with Google Talk's peer-to-peer file sharing and voice calling capabilities. The package includes source code for Google's implementation of Jingle and Jingle-Audio, two proposed extensions to the XMPP standard that are currently available in draft form.

You can check out the head revision of Libjingle from its svn repository using command:

svn checkout http://libjingle.googlecode.com/svn/trunk/ libjingle-read-only

Then ``./autogen.sh'' and ``make'' as we usually do for building a *nix software. You will find many errors during ``./autogen.sh'' and ``make''. To fix them, first, some LIBs should be installed:

sudo apt-get install build-essential libexpat1-dev libglib2.0-dev libogg-dev libssl-dev libasound2-dev libspeex-dev openssl libortp7-dev libmediastreamer0-dev libavcodec-dev

I am not very sure if these LIBs are enough. If you have some problem with this, please let me know.

Even if you have all of these LIBs installed, you will still get some errors such as:

../../talk/base/stringutils.h:272: error: extra qualification 'talk_base::Traits::' on member 'empty_str'
../../talk/base/base64.h:26: error: extra qualification ‘talk_base::Base64::’ on member ‘Base64Table’
../../talk/base/base64.h:27: error: extra qualification ‘talk_base::Base64::’ on member ‘DecodeTable’

So here is a patch for source code errors like this. IMPORTANT NOTE: gcc version 4.2.4 on Ubuntu 8.04, libortp7.

Index: talk/p2p/base/sessionmanager.h
===================================================================
--- talk/p2p/base/sessionmanager.h    (revision 7)
+++ talk/p2p/base/sessionmanager.h    (working copy)
@@ -156,7 +156,7 @@

   // Creates and returns an error message from the given components.  The
   // caller is responsible for deleting this.
-  buzz::XmlElement* SessionManager::CreateErrorMessage(
+  buzz::XmlElement* CreateErrorMessage(
       const buzz::XmlElement* stanza,
       const buzz::QName& name,
       const std::string& type,
Index: talk/session/phone/linphonemediaengine.cc
===================================================================
--- talk/session/phone/linphonemediaengine.cc    (revision 7)
+++ talk/session/phone/linphonemediaengine.cc    (working copy)
@@ -80,24 +80,24 @@
     }
#endif
#ifdef HAVE_SPEEX
-    if (i->name == speex_wb.mime_type && i->clockrate == speex_wb.clock_rate) {
-      rtp_profile_set_payload(&av_profile, i->id, &speex_wb);
-    } else if (i->name == speex_nb.mime_type && i->clockrate == speex_nb.clock_rate) {
-      rtp_profile_set_payload(&av_profile, i->id, &speex_nb);
+    if (i->name == payload_type_speex_wb.mime_type && i->clockrate == payload_type_speex_wb.clock_rate) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_speex_wb);
+    } else if (i->name == payload_type_speex_nb.mime_type && i->clockrate == payload_type_speex_nb.clock_rate) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_speex_nb);
     }
#endif

     if (i->id == 0)
-      rtp_profile_set_payload(&av_profile, 0, &pcmu8000);
+      rtp_profile_set_payload(&av_profile, 0, &payload_type_pcmu8000);

-    if (i->name == telephone_event.mime_type) {
-      rtp_profile_set_payload(&av_profile, i->id, &telephone_event);
+    if (i->name == payload_type_telephone_event.mime_type) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_telephone_event);
     }
    
     if (first) {
       LOG(LS_INFO) << "Using " << i->name << "/" << i->clockrate;
       pt_ = i->id;
-      audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, i->id, 250);
+      audio_stream_ = audio_stream_start(&av_profile, 2000, (char *)"127.0.0.1", 3000, i->id, 250);
       first = false;
     }
   }
@@ -106,7 +106,7 @@
     // We're being asked to set an empty list of codecs. This will only happen when
     // working with a buggy client; let's try PCMU.
      LOG(LS_WARNING) << "Received empty list of codces; using PCMU/8000";
-    audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, 0, 250);
+    audio_stream_ = audio_stream_start(&av_profile, 2000, (char *)"127.0.0.1", 3000, 0, 250);
   }
 
}
@@ -114,12 +114,12 @@
bool LinphoneMediaEngine::FindCodec(const Codec &c) {
   if (c.id == 0)
     return true;
-  if (c.name == telephone_event.mime_type)
+  if (c.name == payload_type_telephone_event.mime_type)
     return true;
#ifdef HAVE_SPEEX
-  if (c.name == speex_wb.mime_type && c.clockrate == speex_wb.clock_rate)
+  if (c.name == payload_type_speex_wb.mime_type && c.clockrate == payload_type_speex_wb.clock_rate)
     return true;
-  if (c.name == speex_nb.mime_type && c.clockrate == speex_nb.clock_rate)
+  if (c.name == payload_type_speex_nb.mime_type && c.clockrate == payload_type_speex_nb.clock_rate)
     return true;
#endif
#ifdef HAVE_ILBC
@@ -171,8 +171,8 @@
#ifdef HAVE_SPEEX
   ms_speex_codec_init();

-  codecs_.push_back(Codec(110, speex_wb.mime_type, speex_wb.clock_rate, 0, 1, 8));
-  codecs_.push_back(Codec(111, speex_nb.mime_type, speex_nb.clock_rate, 0, 1, 7));
+  codecs_.push_back(Codec(110, payload_type_speex_wb.mime_type, payload_type_speex_wb.clock_rate, 0, 1, 8));
+  codecs_.push_back(Codec(111, payload_type_speex_nb.mime_type, payload_type_speex_nb.clock_rate, 0, 1, 7));
  
#endif

@@ -181,8 +181,8 @@
   codecs_.push_back(Codec(102, payload_type_ilbc.mime_type, payload_type_ilbc.clock_rate, 0, 1, 4));
#endif

-  codecs_.push_back(Codec(0, pcmu8000.mime_type, pcmu8000.clock_rate, 0, 1, 2));
-  codecs_.push_back(Codec(101, telephone_event.mime_type, telephone_event.clock_rate, 0, 1, 1));
+  codecs_.push_back(Codec(0, payload_type_pcmu8000.mime_type, payload_type_pcmu8000.clock_rate, 0, 1, 2));
+  codecs_.push_back(Codec(101, payload_type_telephone_event.mime_type, payload_type_telephone_event.clock_rate, 0, 1, 1));
   return true;
}

Index: talk/xmpp/xmppclient.h
===================================================================
--- talk/xmpp/xmppclient.h    (revision 7)
+++ talk/xmpp/xmppclient.h    (working copy)
@@ -138,7 +138,7 @@
     }
   }

-  std::string XmppClient::GetStateName(int state) const {
+  std::string GetStateName(int state) const {
     switch (state) {
       case STATE_PRE_XMPP_LOGIN:      return "PRE_XMPP_LOGIN";
       case STATE_START_XMPP_LOGIN:  return "START_XMPP_LOGIN";
Index: talk/third_party/mediastreamer/msrtprecv.c
===================================================================
--- talk/third_party/mediastreamer/msrtprecv.c    (revision 7)
+++ talk/third_party/mediastreamer/msrtprecv.c    (working copy)
@@ -26,7 +26,7 @@
MSMessage *msgb_2_ms_message(mblk_t* mp){
     MSMessage *msg;
     MSBuffer *msbuf;
-    if (mp->b_datap->ref_count!=1) return NULL; /* cannot handle properly non-unique buffers*/
+    if (mp->b_datap->db_ref!=1) return NULL; /* cannot handle properly non-unique buffers*/
     /* create a MSBuffer using the mblk_t buffer */
     msg=ms_message_alloc();
     msbuf=ms_buffer_alloc(0);
@@ -120,7 +120,7 @@
         gint got=0;
         /* we are connected with queues (surely for video)*/
         /* use the sync system time to compute a timestamp */
-        PayloadType *pt=rtp_profile_get_payload(r->rtpsession->profile,r->rtpsession->payload_type);
+        PayloadType *pt=rtp_profile_get_payload(r->rtpsession->rcv.profile,r->rtpsession->rcv.telephone_events_pt);
         if (pt==NULL) {
             ms_warning("ms_rtp_recv_process(): NULL RtpPayload- skipping.");
             return;
Index: talk/third_party/mediastreamer/audiostream.c
===================================================================
--- talk/third_party/mediastreamer/audiostream.c    (revision 7)
+++ talk/third_party/mediastreamer/audiostream.c    (working copy)
@@ -112,7 +112,7 @@
             RtpSession **recvsend){
     RtpSession *rtpr;
     rtpr=rtp_session_new(RTP_SESSION_SENDRECV);
-    rtp_session_max_buf_size_set(rtpr,MAX_RTP_SIZE);
+    rtp_session_set_recv_buf_size(rtpr,MAX_RTP_SIZE);
     rtp_session_set_profile(rtpr,profile);
     rtp_session_set_local_addr(rtpr,get_local_addr_for(remip),locport);
     if (remport>0) rtp_session_set_remote_addr(rtpr,remip,remport);
@@ -133,7 +133,7 @@
     /* creates two rtp filters to recv send streams (remote part)*/
    
     rtps=rtp_session_new(RTP_SESSION_SENDONLY);
-    rtp_session_max_buf_size_set(rtps,MAX_RTP_SIZE);
+    rtp_session_set_recv_buf_size(rtps,MAX_RTP_SIZE);
     rtp_session_set_profile(rtps,profile);
#ifdef INET6
     rtp_session_set_local_addr(rtps,"::",locport+2);
@@ -147,7 +147,7 @@
     rtp_session_set_jitter_compensation(rtps,jitt_comp);
    
     rtpr=rtp_session_new(RTP_SESSION_RECVONLY);
-    rtp_session_max_buf_size_set(rtpr,MAX_RTP_SIZE);
+    rtp_session_set_recv_buf_size(rtpr,MAX_RTP_SIZE);
     rtp_session_set_profile(rtpr,profile);
#ifdef INET6
     rtp_session_set_local_addr(rtpr,"::",locport);
@@ -217,8 +217,8 @@
     ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate);
     ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_BITRATE,&pt->normal_bitrate);
    
-    ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_FMTP, (void*)pt->fmtp);
-    ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FMTP,(void*)pt->fmtp);
+    ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_FMTP, (void*)pt->send_fmtp);
+    ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FMTP,(void*)pt->send_fmtp);
     /* create the synchronisation source */
     stream->timer=ms_timer_new();
    
Index: talk/third_party/mediastreamer/msrtpsend.c
===================================================================
--- talk/third_party/mediastreamer/msrtpsend.c    (revision 7)
+++ talk/third_party/mediastreamer/msrtpsend.c    (working copy)
@@ -85,7 +85,7 @@
{
     guint32 clockts;
     /* use the sync system time to compute a timestamp */
-    PayloadType *pt=rtp_profile_get_payload(r->rtpsession->profile,r->rtpsession->payload_type);
+    PayloadType *pt=rtp_profile_get_payload(r->rtpsession->snd.profile,r->rtpsession->snd.telephone_events_pt);
     g_return_val_if_fail(pt!=NULL,0);
     clockts=(guint32)(((double)synctime * (double)pt->clock_rate)/1000.0);
     ms_trace("ms_rtp_send_process: sync->time=%i clock=%i",synctime,clockts);
Index: talk/base/base64.h
===================================================================
--- talk/base/base64.h    (revision 7)
+++ talk/base/base64.h    (working copy)
@@ -23,8 +23,8 @@
   static std::string decode(const std::string & data);
   static std::string encodeFromArray(const char * data, size_t len);
private:
-  static const std::string Base64::Base64Table;
-  static const std::string::size_type Base64::DecodeTable[];
+  static const std::string Base64Table;
+  static const std::string::size_type DecodeTable[];
};

} // namespace talk_base
Index: talk/base/stringutils.h
===================================================================
--- talk/base/stringutils.h    (revision 7)
+++ talk/base/stringutils.h    (working copy)
@@ -269,7 +269,7 @@
template<>
struct Traits<char> {
   typedef std::string string;
-  inline static const char* Traits<char>::empty_str() { return ""; }
+  inline static const char* empty_str() { return ""; }
};

///////////////////////////////////////////////////////////////////////////////

You killed all these errors? Congratulations! You can start talking with your gtalk friends with command ``call'' in talk/examples/call/ !

PS: If you are working with GCC 4.3.x, more strict checking is applied on the code. However, most errors can be fixed by adding some C headers into the #include fields, such as: <cstdlib>, <cstring>.

Gnome-Do 和 Launchy

我很喜欢那种充满创意的小程序,特别是超出我的想像那种,大概是我的想像太贫乏,因此老有惊喜。像 Synergy, Paint.Net, JabRef, Trash-cli 等,都让我觉得,哦,还有这样的软件!最近我又发现了两个,Gnome-DoLaunchy

通常情况下,为了使用方便,无论是用 Linux 还是 Windows,我总会在屏幕的最上方建一个工具栏,里面是到常用程序的快捷方式。尤其是用 Windows 时,老是按 Win+D 显示桌面然后再找启动程序太痛苦了;Linux 的程序都可以从命令行启动,状况还好一点。

但是我没想到还有像 Gnome-Do 和 Launchy 这样的程序,让我不用从一堆快捷方式中辨别我要的程序。按 Win+Space 就呼出一个小启动框,可以打开软件、打开文件夹、打开文档、打开书签、Google 搜索、Pidgin 聊天,真的是太酷了。我非常非常喜欢,它解放了我的快速启动栏。感谢开源社区给我们带来这么好的软件。

Launchy v.s. Gnome-do

文献管理软件 JabRef 2.4.3 r2919

For JabRef version 2.4.2 with bug "Could not parse number of hits" (while searching ACM portal) fixed, please click here.

参考文献管理软件 JabRef 主页上的版本是 2.4.2,这个版本有一些 bug,最讨厌的就是无法在线搜索 ACM 论文库,老出现“Could not parse number of hits” 错误。

曾经用老版本的时候还没有这个问题,而 ACM 又是我常用的论文库之一。今天实在无奈了,就到 SF 上把 JabRef 最新的代码 check out 出来,用 ant 编译了一下,运行后发现这个 bug 解决了。因此我顺便打了个 jar 包和 deb 包,扔到了 http://share.solrex.org/ibuild/,有兴趣的同学自己去下吧。

注意:这个包基于 SVN 版本 r2919,不是 JabRef 官方发布,下载请注意,若有 bug,造成的后果请自负。关于此软件的使用方法,请看这里

PS:有同学报告说排序不可用,我试了一下,确实如此。而且虽然 2.4.3 貌似有中文的语言选项,但其实是不能选的。那我还是先解决 ACM 的问题吧,同时上传了 2.4.2 fix ACM 的版本,如果不愿意测试 2.4.3,可以选择这个。

Ubuntu 9.04 安装手记

此次安装是在一台 Dell OptiPlex 740 台式机上进行,处理器 AMD Athlon 64 X2 4400+,显卡 nVIDIA GeForce 6150,声卡 SigmaTel STAC9200。Ubuntu 版本为 9.04 Jaunty Jackalope Alpha 5 amd64 位版,硬盘安装。

1. 硬盘安装时在分区那步仍无法列出分区表,需要 Ctrl-Alt-F2 进入控制台 2 手动 umount -l /hd-media 后重试。

2. 开箱显示正常,但 Compiz 3D 特效无法开启,可能因为没有可用的受限驱动。

3. 默认 ALSA 驱动不支持该声卡,但选择 OSS 驱动可以支持(很奇怪)。必须在音量设备那开启对 capture 设备,才能录入声音,此时可以使用 ALSA 驱动。在使用 Mplayer、SMplayer 之类播放器播放视频时,需要手动在选项中选择声音输出的驱动为 OSS。

4. 安装 smplayer 之后即支持 avi 格式解码,不用手动下载 codecs。在播放 720p 以上高清视频时,最好将 smplayer 的本地缓存调整为 10M 以上(默认为2M),否则容易引起底层 mplayer 内存问题,会无法播放、内存占用过大死机或崩溃。

5. Socks 代理软件 Dante 源代码在该 x86_64 平台上编译后运行会出现段错误;更换为 antinat,经源码编译后运行正常。

6. Libfetion for 64 位 deb 包不可用,异常退出;经源码编译后运行正常。

7. sendsms 编译为 64 位版本会出现段错误,但短信仍能发送成功。

9. 网易的 Ubuntu 软件仓库镜像不包含 Jaunty,cn 的镜像没有总镜像快,security 的连接速度太慢。正式发布后,网易已经有了 Jaunty 源。

10. 使用 Firefox 时可直接安装 Adobe Flash Player 10 64 位版本。

11. 新的状态通知很漂亮,可以显示 Pidgin 上好友的登录和短讯。

12. 根分区使用 ext4 文件系统,数据分区为了与其它系统保持共享仍采用 ext3 文件系统。

13. 安装中文支持时可以选择是否安装该语言用户常用软件,选择是则会增加 PCManX、StarDict 等软件。PCManX 中包含非常全的港台和大陆 BBS 地址簿,但是字体有问题,未解决;QTerm 5.3 会崩溃。

14. 传说中的启动加速没有明显感觉,也许是因为启动的服务太多。

15. Ubuntu 9.04 Jave 程序中文显示不正常,部分文字变成方格。文泉驿字体从 wqy-zenhei.ttf 改为 wqy-zenhei.ttc,导致 java 程序中文字体显示不正常。在 /etc/java-6-sun/fontconfig.properties 中添加 wqy 相关内容,diff 文件可以从这里下载。Java 貌似不可用 ttc 字体,从 8.04 拷来 wqy-zenhei.ttf 到 /usr/share/fonts/truetype/wqy 目录下可解决,wqy-zenhei.ttf 也可以从这里下载。

16. 在另一台笔记本 Dell D630 上安装 9.04 正式版时,发现居然默认不支持 NVS135M 视频驱动,需要配置好网络后,使用命令 sudo apt-get install nvidia-glx-180 安装 Nvidia 180 版本的驱动,或者自己到 NV 网站上下载最新驱动。

应用程序打包技术之三(rpm 篇)

1. 应用程序打包技术之一(源代码篇)
2. 应用程序打包技术之二(deb篇)
3. 应用程序打包技术之三(rpm 篇)
4. 应用程序打包技术之四(exe篇)

rpm 是 RedHat 系 Linux 使用的软件包格式。流行的 Linux 发行版:Fedora, RHEL, OpenSUSE, Oracle 包括国产的红旗 Linux,都采用 rpm 来管理软件包。

我不是很喜欢 rpm 软件包格式,原因主要有两个,一个是它的依赖关系很难处理,另一个是控制文件比较复杂。但是 rpm 包有着非常广泛的应用,也是一个提高生产力的重要工具。

像前一篇文章提到的一样,checkinstall 也可以用来打 rpm 包,但我不是很熟悉这个软件。这里我仅仅介绍如何使用原始的 rpm(rpmbuild) 工具来打 rpm 软件包。

和前面 deb 包使用 fakeroot 一样,rpmbuild 也是利用沙盒的方式来构建软件包,除了控制文件比较繁琐,过程可能还更自动一些。构建 rpm 包的工作目录默认是 /usr/src/redhat,如果您在 Debian 下安装了 rpm 这个软件包,它可能是 /usr/src/rpm。您可以使用 rpm --eval %_topdir 查看自己的 rpm 工作目录。

$ tree `rpm --eval %_topdir`
/usr/src/redhat
|-- BUILD
|-- RPMS
|   |-- athlon
|   |-- i386
|   |-- i486
|   |-- i586
|   |-- i686
|   `-- noarch
|-- SOURCES
|-- SPECS
`-- SRPMS

我们可以看到,/usr/src/redhat/ 下有几个子目录:SOURCES 用来存放源代码包,SPECS 用来存放 spec 控制文件,BUILD 用来解压源代码包和构建软件,RPMS 里存放的是打好的二进制应用程序 RPM 包,SRPMS 里存放的是打好的源代码 RPM 包。但是,我们应该知道 _topdir 是可以在 spec 文件中修改的,否则我们只能用 root 才能在默认的工作目录 /usr/src/redhat 下创建文件和执行命令。我们可以先把工作目录树拷贝到用户自己的目录中:

$ cp -r /usr/src/redhat ~/rpm

这样,我们就可以在用户目录 ~/rpm 下打 rpm 包了。打 rpm 包之前的准备工作只有两件:1. 准备好源代码包并放置在 SOURCES 目录下;2. 准备好 spec 控制文件并放在 SPECS 目录下。如何打源代码包我们已经在 《源代码篇》中介绍过,下面我们主要来介绍 spec 文件的格式。下面是一个实例 spec 文件。

$ more ~/rpm/SPECS/casnet.spec
%define   _topdir    /home/solrex/rpm
Name:     casnet
Version:  1.3
Release:  1
License:  GPL
Packager: Solrex Yang
Summary:  CASNET Client
Group:    Network
Source:   %{name}-%{version}.tar.gz
URL:      http://share.solrex.org/casnet/
Prefix:   /usr

%description
CASNET is a gui client for ip gateway of GUCAS(Graduate University of Chinese
Academy of Sciences), which is written in Python and PyGtk.

%prep
%setup -q

%build

%install
make -e PREFIX=%{prefix} install

%files
%{prefix}/bin/casnetconf
%{prefix}/bin/casnet
%{prefix}/bin/casnet-gui
%{prefix}/share/casnet/casnetconf.py
%{prefix}/share/casnet/casnet.py
%{prefix}/share/casnet/casnet-gui.py
%{prefix}/share/casnet/pics/*.png
%{prefix}/share/applications/casnet.desktop
%{prefix}/share/icons/casnet.png

我们可以看到,spec 文件与 deb 包的 control 文件有很多相似之处:Name, Version, Release, License, Packager, URL 是软件的名称、版本、小版本、使用的协议、维护者和网址;Summary 和 %description 是软件的信息;Group 是软件所归类别。剩下的就有些不同了:Source 是指软件的源代码包名称,rpm 会到 SOURCES 目录下找这个包;Prefix 是软件的默认安装位置;%prep、%build、%install 下分别是软件的预处理、编译和安装命令;%files 是所有需要被打包进去的文件。

因此,作者需要将 spec 文件中的各个域填写正确。%prep 下的 %setup 宏一般是用来将源代码 tar 包释放到 BUILD 目录下;%build 下可以添加 %configure 宏和 make 命令,如果您的软件需要编译的话;%install 下是您的安装命令;%files 下是您程序运行所需要的全部文件,其路径即为您安装完软件后它应该在的位置。%{name} 可以用来指代 Name: 域的内容,%{version } 指代 Version: 域的内容,以此类推。

需要注意的是,rpmbuild 使用 BUILD/%{name}-%{version} 作为您的源代码包释放后的目录,进入其进行编译。因此当您使用 tar 打源代码包时,tar 包的顶层目录名应该为 %{name}-%{version} 而不是仅仅是 %{name},比如:

$ tar czvf casnet-1.3.tar.gz casnet-1.3
$ mv casnet-1.3.tar.gz ~/rpm/SOURCES

现在我们在 ~/rpm/SOURCES 下有了 casnet-1.3.tar.gz,在 ~/rpm/SPECS 目录下有了 casnet.spec,那么我们就可以使用下面的命令生成 rpm 包了。如果您的安装位置 Prefix 选择的是一个系统目录,比如 /usr, /usr/local 之类,您也许需要使用 root 权限来运行 rpmbuild 命令。

$ rpmbuild -ba ~/rpm/SPECS/casnet.spec

这样我们就能在 ~/rpm/RPMS/i386 目录下获得我们生成的 rpm 软件包了。rpmbuild 命令的执行过程是这样的:首先根据 spec 文件定位源代码包,然后将源代码包释放到 BUILD 目录下,使用 spec 文件中给出的命令编译和安装,然后将 spec 中列的文件提取出来,按照包信息打出来 rpm 包。

我们可以使用 rpm -qpi 命令来查看 rpm 包的信息,rpm -qpl 命令来查看 rpm 包所包含的文件列表:

$ rpm -qpi ~/rpm/RPMS/i386/casnet-1.3-1.i386.rpm
Name        : casnet                       Relocations: /usr
Version     : 1.3                               Vendor: (none)
Release     : 1                             Build Date: Thu 26 Feb 2009 08:03:50 PM CST
Install Date: (not installed)               Build Host: laptop
Group       : Network                       Source RPM: casnet-1.3-1.src.rpm
Size        : 40883                            License: GPL
Signature   : (none)
Packager    : Solrex Yang
URL         : http://share.solrex.org/casnet/
Summary     : CASNET Client
Description :
CASNET is a gui client for ip gateway of GUCAS(Graduate University of Chinese
Academy of Sciences), which is written in Python and PyGtk.

$ rpm -qpl ~/rpm/RPMS/i386/casnet-1.3-1.i386.rpm
/usr/bin/casnet
/usr/bin/casnet-gui
/usr/bin/casnetconf
/usr/share/applications/casnet.desktop
/usr/share/casnet/casnet-gui.py
/usr/share/casnet/casnet.py
/usr/share/casnet/casnetconf.py
/usr/share/casnet/pics/casnet.png
/usr/share/casnet/pics/offline.png
/usr/share/casnet/pics/online.png
/usr/share/icons/casnet.png

如果您想了解更详细的内容,您可以进一步参考 Jake's RPM Build Tutorial 这篇文章。

应用程序打包技术之二(deb篇)

1. 应用程序打包技术之一(源代码篇)
2. 应用程序打包技术之二(deb篇)
3. 应用程序打包技术之三(rpm 篇)
4. 应用程序打包技术之四(exe篇)

deb 是 Debian 系 Linux 使用的软件包格式,也是我最欣赏的软件包格式。我所知道的打 deb 软件包的方法有两种,一种是使用 checkinstall,另一种是使用 dpkg。

checkinstall 不仅仅可以用来打 deb 包,还可以打 rpm 和 tgz 包,而且使用方法相对简单。但是 checkinstall 的运行不是那么稳定,我搞不懂它在什么情况下才能正常运行,而且它的定制性不是很强,使用时老是要交互地输入些信息,所以我还是放弃了使用它来打包软件。感兴趣的朋友可以在网上搜索一下这个程序的使用方法。

dpkg 是 Debian 的“原生”包管理软件,但是很多人不太愿意使用 dpkg 来打包 deb。究其原因可能是需要写麻烦的配置文件,但是写配置文件的一个好处就是在下次打包时候可以直接用上次的配置文件,只修改一个版本号就可以了,而不用每次都需要填包信息。在介绍如何打 deb 包之前,我们现看一下如何解 deb 包。

$ sudo apt-get install tree
$ dpkg -X /var/cache/apt/archives/tree_1.5.1.1-1_i386.deb fakeroot
$ cd fakeroot
$ dpkg -e /var/cache/apt/archives/tree_1.5.1.1-1_i386.deb
$ tree
.
|-- DEBIAN
|   |-- control
|   `-- md5sums
`-- usr
    |-- bin
    |   `-- tree
    `-- share
        |-- doc
        |   `-- tree
        |       |-- README
        |       |-- changelog.Debian.gz
        |       |-- changelog.gz
        |       `-- copyright
        `-- man
            `-- man1
                `-- tree.1.gz

dpkg -X 是将 deb 包的内容文件释放出来,dpkg -e 是将 deb 包的控制信息释放出来。前面执行那个 sudo apt-get install tree 是为了将 tree_1.5.1.1-1_i386.deb 下载到本地 apt cache,如果您已经安装过 tree 这个软件,可以为 apt-get 加上 -d 参数,使其只下载而不安装。

从上面 tree 命令的执行结果我们发现,deb 包解开后分两部分:一部分是控制信息,在 DEBIAN 目录下;一部分是安装内容,在 usr 目录下。现在您大概明白为什么我们使用 fakeroot 作为目录名了,因为这个目录就是一个"假根目录",您在这个目录下所有的修改,最后都会被映射到目标机的根目录 / 下。比如 fakeroot/usr/bin/tree 这个文件,就会被安装到 /usr/bin 下,以此类推。

只要您能理解 fakeroot 这个目录映射,您就知道如何安放自己的文件了。为了让生成的包将文件 foo 安装到目录 /usr/xx/yy 目录下,您只用在 fakeroot 目录下建立 usr/xx/yy 目录,并将 foo 拷贝进去就行了。

好,下面进入关键的配置文件部分,关于 control 和 md5sums。

$ more DEBIAN/control
Package: tree
Version: 1.5.1.1-1
Architecture: i386
Maintainer: Ubuntu MOTU Developers
Original-Maintainer: Florian Ernst
Installed-Size: 92
Depends: libc6 (>= 2.6-1)
Section: utils
Priority: optional
Description: displays directory tree, in color
Displays an indented directory tree, using the same color assignments as
ls, via the LS_COLORS environment variable.
.
Homepage: http://mama.indstate.edu/users/ice/tree/

我们可以看到,control 文件中包含的主要是软件的版本和维护者信息,我相信大家都能基本看懂上面这些信息什么意思:Package 包名(tree)、Version 版本(1.5.1.1-1)、Architecture 目标机架构(i386 386及以后)、Maintainer 维护者(Ubuntu MOTU Developers)、Original-Maintainer 原维护者(Florian Ernst)、Installed-Size 安装后大小(92K)、Depends 依赖软件包(libc6 不低于 2.6-1 版本)、Section 包分类(工具)、Priority 优先级(可选)、Description 包描述、Homepage 软件主页。

由于咱们分析这个包是 Ubuntu 发布的包,所以包信息给的比较全,其实并不是上面所有的信息都有必要提供(小声说一句,就算全提供也不是很难吧?除了咱不用的,Original-Maintainer 这种就算了)。关于哪些信息比较重要,以及每个域的具体含义和可选项,可以参考 Debian 的文档 Debian Policy Manual Chapter 5 - Control files and their fields

您也可以依样画葫芦,写一个类似的 control 文件放到 DEBIAN 目录下,提供一些自己软件包的信息,基本有这个配置文件就可以打包了。

$ more DEBIAN/md5sums
d60a3b4736f761dd1108cb89e58b9d4e usr/bin/tree
981ea0343c2a3eb37d5fc8b5ac5562df usr/share/man/man1/tree.1.gz
483a56158a07a730ec60fc36b3f81282 usr/share/doc/tree/README
ea56d78ae0d54693ae8f3c0908deeeff usr/share/doc/tree/copyright
4456e04c3c268eabcd10ee9b949a9b9a usr/share/doc/tree/changelog.gz
ec104db6914cfce2865a0d8c421512bb usr/share/doc/tree/changelog.Debian.gz

md5sums,这文件名一看,就知道是保存着软件包中各文件的 md5 校验值,用来校验软件包是否被损坏了。其实这个文件纯属“腊月三十逮兔子,有它没它都过年”,您可以完全不提供它。

这样呢,我们就准备好了 deb 包的内容文件和控制信息:控制文件放在了 fakeroot/DEBIAN 目录下,内容文件放在 fakeroot/usr 下,目录树就像开头 tree 命令的结果。下面只需要一个命令就能打出来 deb 安装包了:

$ cd ..
$ dpkg -b fakeroot/ foo.deb

这时候当前目录下就出现了 foo.deb。您可以使用 dpkg -I foo.deb 查看 foo.deb 的控制信息,dpkg -c foo.deb 查看 foo.deb 包含了什么文件,sudo dpkg -i 安装 foo.deb。

小技巧:

1. 如果您懒得自己新建一个控制文件和目录树,您完全可以像本文开头那样,找一个简单的软件包,将它的内容和控制信息释放出来,对它进行修改,然后打出来自己的包。

2. 生成 md5sums 文件不是什么难事,您只需要在 fakeroot 目录使用下面这个命令:

$ md5sum `find usr -type f` > DEBIAN/md5sums
或者
$ find usr/ -type f -exec md5sum {} + > DEBIAN/md5sums

3. 将您的可执行文件拷贝到 fakeroot/usr 下并不一定要手动一个个拷。如果您使用 GNU 自动工具集,./configure 时加个参数 --prefix=fakeroot/usr/ 即可;如果您自己写的 Makefile,可以在 Makefile 中使用一个变量 PREFIX=/usr,当您不加参数时,make install 的安装目标就是 /usr 下,您可以使用 Makefile -e PREFIX=fakeroot/usr/ install 来覆盖 Makefile 中的变量设置。

GUCAS IP 网关登录客户端 1.3 发布

如果您不知道这软件是干嘛的,那您就不用往下看了。这个软件是中科院研究生院师生使用的,更新公告发表在这里只是为了记录一下版本发布历史。

软件主页:http://share.solrex.org/casnet

最新版本 1.3-1(2009年2月11日发布) 更新

  1. 增加了单击更换登录模式功能。
  2. 增加了自动断线充连功能。
  3. 增加了余额不足提醒功能。
  4. 解决了以前版本的一些 BUG。

那些 ssh 教我的事

我以前以为 ssh 只能登录主机执行命令,ssh 教我它还会 scp;

我以为会 scp 很厉害了,ssh 教我它还会 sftp;

我以为会 sftp 了不得了,ssh 教我它还会 sshfs;

我以为会 sshfs 已经相当牛了,ssh 教我它还能把服务器端口绑定到本机端口(ssh -L);

我以为把服务器端口绑定到本机端口就已经上天了,ssh 教我它还能把本机端口绑定到服务器端口(ssh -R)...

然后,我怨念已久的共享上网终于实现了!

PS: 用软件还得选老牌开源软件,openssh 那么多年没更新了,在 Windows 下用 cygwin 运行都比 freesshd 稳定,不得不赞一个。Win 下的一些软件,窗口搞得花里胡哨的,功能和稳定性却不知道放到第几位了,跟人一样,长得好看的未必靠谱。

The Gold Old Tools: Pic and Chem

我本来只想写一下 pic 这个小工具,因为很少有中文资料介绍其用法。但是写下来没想到一个小软件联系到那么多典故,更让我对当年的贝尔实验室高山仰止。The gold old days never come again.

目录
1. Pic 的作者
2. 一个 Pic 的简单例子
3. 一个复杂的例子
4. 关于 chem 的典故
5. 在 LaTeX 中使用 pic

1. Pic 的作者

Pic,我敢打赌大部分读者没有听说过这个软件(或者说语言),但是我也敢打赌大部分读者听说过它的作者。

所以我只好从它的作者 Brian W. Kernighan 开始介绍了。不过也许用不着我介绍,假如你学过 C 语言,并且碰巧读过那本经典的 The C Programming Language(K&R);假如你使用 Linux,并且碰巧用过 AWK;或者你不小心学了一门编程语言,并且碰巧你学会写的第一个程序叫做“Hello, world”,那么你都应该感谢 Kernighan。没错, K&R 和 AWK 中的 K 都是代表 Kernighan,而且 Kernighan 是第一个使用 “Hello, world” 的人

2. 一个 Pic 的简单例子

Pic,是 Kernighan 写的一个图像排版软件,它主要用来作为 troff 中图片的预处理工具。简单点儿来说,pic 就是用来画特定一类图的工具,比如流程图、状态图、Petri~网、化学分子式等等。就像可以用写代码的方式写文档一样(TeX),我们也可以用写代码的方式画图,pic 就是这样一种语言。我们来看一个简单的例子(来自 More Programming Pearls):

.PS
ellipse "Source" "Code"
arrow
box "Compiler"
arrow
ellipse "Object" "Code"
.PE

将上面代码保存成一个 compiler.pic 文件,然后执行下面命令:

$ pic compiler.pic | groff | ps2eps > compiler.eps

我们就能得到名为 compiler.eps 的图像,下面这张图是使用 convert 命令从 compiler.eps 转换成的 png 图像:

Compiler(无法看到此图,可能因为您无法连接国外网站)

其实 Linux 下的 pic 是 GNU 版本的 gpic,groff 也是 GNU 版本的 troff,它们是都是被 GNU 重写了的自由软件版本的 pic 和 troff。

上面编译的步骤是,先用 gpic 将 compiler.pic 预处理为 troff 文档,然后用 groff 生成 ps 文件,然后用 ps2eps 将 ps 文件转化为 eps 图片。由于它们都是从标准输出打印文件,所以我们就用管道将这三个命令连接了起来,并且最后将标准输出重定向到 eps 文件。

3. 一个复杂的例子

看到这些,可能有人就怀疑说,这图很简单嘛,用 Photoshop 或者 GIMP 也花费不了多长时间,我干吗要再去学一门语言呢!那么,你尝试用 Photoshop 画一下下面这张图片。

Chemical Structure(无法看到此图,可能因为您无法连接国外网站)

画这张图的代码如下:

.cstart
    R:  benzene pointing right
        bond left from R.V4 ; HO
        bond -150 from R.V3 ; CH3O
        bond right from R.V1 ; C
        double bond up from C ; O
        bond right from C ; N
        bond 45 ; C2H5
        bond 135 from N ; C2H5
.cend

看到这,可能有人感叹说,哇,pic 真厉害!错(or 半错?),上面这段代码不是 pic 语言,而是 chem 语言。pic 是 troff 的预处理器,而 chem 又是 pic 的预处理器。所以上面这段代码的编译命令是:

$ chem.pl chem.chem | pic | groff | ps2eps > chem.eps

chem.pl 先把上面代码转换成 pic 语言,然后再用 pic 进行处理。

4. 关于 chem 的典故

关于 chem,还有一段典故:

话说当年(别问我,我不知道是哪一年)一个星期一的下午,Kernighan、 Jon Bentley 和一个同在贝尔实验室的化学家 Lynn Jelinski 闲扯了一会儿,Lynn 就向他俩抱怨在文档中插入化学结构式实在太麻烦了。正好 Kernighan 刚写了 pic,就想,咦,这不可以用 pic 画吗?于是那天晚上 K&B 就写了一个简单的前端。Lynn 一看,哎,不错!他们仨花了一个星期时间完成了 chem 语言,还在 Computers & Chemistry 杂志上发了篇 paper,名字叫做:Chem - a program for phototypesetting chemical structure diagrams。

别着急,还没完。话说当年(1990) GNU 要搞自由软件版本的 troff,开始重写 troff,把前端中的 pic, tbl, soelim 和 eqn 都重写了,就剩下画数学公式的 ideal 和 chem 没有重写。到现在你看你自己 Linux 中的 info groff,在 Introduction->Preprocessor 的最后还能找到:“There are other preprocessors in existence, but, unfortunately, no free implementations are available. Among them are preprocessors for drawing mathematical pictures (`ideal') and chemical structures (`chem').“

时间慢慢到了 2006 年 10 月 19 号,一个叫 Bernd Warken 的小子在 groff 的邮件列表中吼了一嗓子:哈哈,我用 Perl 重写了 chem!然后维护者 Werner LEMBERG 大叔就说:Great! 才有了我上文中的 chem.pl,原始的 chem 是用 AWK 语言写的。但是 chem.pl 到现在还没有到 groff 的正式发行版中,原因很囧,groff 在 2006 年 10 月 16 号以后还没有 release 新版本。所以如果你想使用 chem.pl,只能到 groff 的 cvs 仓库下载:http://cvs.savannah.gnu.org/viewvc/groff/contrib/chem/?root=groff

5. 在 LaTeX 中使用 pic

上面只是简单介绍了一下用 pic 做 eps 图片的过程,但是 pic 还可以用到 LaTeX 中,这样简单的图片就直接在 TeX 里写了。比如我们的第一个图片,就可以这样在 TeX 中使用:

\documentstyle{article}
\begin{document}
  \newenvironment{centergpic}{}{\begin{center}~\box\graph~\end{center}}
     \begin{centergpic}
.PS
ellipse "Source" "Code"
arrow
box "Compiler"
arrow
ellipse "Object" "Code"
.PE
     \end{centergpic}
\end{document}

.PS 和 .PE 一定要在行首。将上面文件保存成 compiler.tex,用 pic 预处理一下,再用 latex 编译:

$ pic -t compiler.tex > compiler1.tex
$ latex compiler1.tex
$ dvipdfmx compiler1.dvi

就能生成含有该图的 pdf 文件。

PS:若有人对 Pic 感兴趣,请阅读 Eric S. Raymond 大叔写的 “Making Pictures With GNU PIC”。你没看错,就是写《大教堂和集市》和《Unix 编程艺术》那个 Raymond 大叔。你也不用下载它,到 /usr/share/doc/groff/ 下面找一找吧,那个叫做 pic.ps.gz 的。

Aspell: 程序员的拼写检查利器

作为一个程序员,尤其是非英语母语国家(ESL or EFL)的程序员,写出漂亮的注释可能要比写出漂亮的代码更难。就比如 Eric 的“来自英语母语国家的”女友就有 “Programmers are English-challenged.“ 的评论。

那么如何在程序的注释中避免犯一些低级语法或者拼写错误呢?Eric 也在 Some useful tools for you to write English articles on Linux 中推荐了几个小工具。我这里算拾人牙慧,稍微写一点儿我非常欣赏的 Aspell 拼写检查工具。

Aspell 是一个强大的拼写检查工具,尤其是对于程序员来说。在 Linux 下,大部分程序员应该是用 Vim 或者 Emacs 写代码,它们有内建拼写检查功能,比如 vim 可以用 :setlocal spell spelllang=en_us 开启对美式英语的拼写检查。不过很少人会安装或者使用拼写检查功能,不是每个人都喜欢写代码时面对一堆高亮的词组(当它们不仅检查注释时,哦,天那!)。幸运的是,我们有 Aspell。

Aspell 使用方法非常简单,比如只想检查 C 或者 C++ 风格的注释和字符串中的拼写错误,就可以用这样的命令:

$ aspell --mode=ccpp -c test.cpp

终端里就会列出一个一个注释中的错误,并给出修改意见。接下来的工作就很简单了,按照窗口下面每个键对应的功能,选择更换单词或者忽略该单词。如下图所示:

Aspell 拼写检查工具(无法看到此图,可能因为您无法连接国外网站)

Aspell 还有更多模式,比如检查 HTML, TeX, XML, Perl 等等一些文档或程序中的拼写,更多内容就请看 Aspell 的帮助吧。

Aspell 的用户习惯保存在 ~/.aspell.en.prepl 和 ~/.aspell.en.pws 两个用户自定义替换和忽略单词列表里,可以通过备份或者修改这两个列表来改变 aspell 对某些单词拼写检查的策略。