Wednesday, April 6, 2011

小技巧: 在C++中实现在main函数之前及之后执行代码 (续)

大概1年前我写过一篇叫做 "小技巧: 在C++中实现在main函数之前及之后执行代码" 的文章, 当时文中只介绍了技巧, 却没有深究技巧实现的原因. 后来 Googol Lee 同学留言 《程序员的自我修养》这本书有详细讲述, 近期刚好把书看完, 便琢磨着自己再总结总结, 给原来的文章续一个结尾.

下面的分析都是基于以前那篇文章里的C++代码, 假设我们编译后生成的可执行文件叫做 alist. 首先祭出反汇编利器 objdump:

$ objdump -sdC alist | less

我们都知道程序在进入 main 函数之前会执行 .init 段里的代码 (这个可以从glibc的入口函数源码中分析得到), 那么我们就从 .init 开始看起:

Disassembly of section .init:

08048508 <_init>:
 8048508:       55                      push   %ebp
 8048509:       89 e5                   mov    %esp,%ebp
 804850b:       53                      push   %ebx
 804850c:       83 ec 04                sub    $0x4,%esp
 804850f:       e8 00 00 00 00          call   8048514 <_init+0xc>
 8048514:       5b                      pop    %ebx
 8048515:       81 c3 e0 1a 00 00       add    $0x1ae0,%ebx
 804851b:       8b 93 fc ff ff ff       mov    -0x4(%ebx),%edx
 8048521:       85 d2                   test   %edx,%edx
 8048523:       74 05                   je     804852a <_init+0x22>
 8048525:       e8 2e 00 00 00          call   8048558 <__gmon_start__@plt>
 804852a:       e8 41 01 00 00          call   8048670 <frame_dummy>
 804852f:       e8 0c 03 00 00          call   8048840 <__do_global_ctors_aux>
 8048534:       58                      pop    %eax
 8048535:       5b                      pop    %ebx
 8048536:       c9                      leave
 8048537:       c3                      ret

重点看红色那行, 从调用的函数名就可以看出一定是此地无银, 跟进 __do_global_ctors_aux 函数 (这个函数的源码包含在GCC中) 看看:

08048840 <__do_global_ctors_aux>:
 8048840:       55                      push   %ebp
 8048841:       89 e5                   mov    %esp,%ebp
 8048843:       53                      push   %ebx
 8048844:       83 ec 04                sub    $0x4,%esp
 8048847:       a1 fc 9e 04 08          mov    0x8049efc,%eax
 804884c:       83 f8 ff                cmp    $0xffffffff,%eax
 804884f:       74 13                   je     8048864 <__do_global_ctors_aux+0x
24>
 8048851:       bb fc 9e 04 08          mov    $0x8049efc,%ebx
 8048856:       66 90                   xchg   %ax,%ax
 8048858:       83 eb 04                sub    $0x4,%ebx
 804885b:       ff d0                   call   *%eax
 804885d:       8b 03                   mov    (%ebx),%eax
 804885f:       83 f8 ff                cmp    $0xffffffff,%eax
 8048862:       75 f4                   jne    8048858 <__do_global_ctors_aux+0x
18>
 8048864:       83 c4 04                add    $0x4,%esp
 8048867:       5b                      pop    %ebx
 8048868:       5d                      pop    %ebp
 8048869:       c3                      ret
 804886a:       90                      nop
 804886b:       90                      nop

红色部分是一个循环, 从 call *%eax 可以看出是在循环调用函数, 且 %eax 指向的是函数指针. 究竟 %eax 中是什么内容, 我们往回看那行蓝色的代码, 一切都引向了 0x8049efc 这个地址, 我们得看看其中包含什么, 再往回在其它段中寻找, 可以发现如下部分:

Contents of section .ctors:
 8049ef8 ffffffff 55870408 00000000           ....U.......

0x8049ef8 加上4正好是 0x8049efc, %eax 这个函数指针的值也就是 0x8048755 (little endian), 再来看看 0x8058755 指向的函数是什么:

08048755 <global constructors keyed to a>:
 8048755:       55                      push   %ebp
 8048756:       89 e5                   mov    %esp,%ebp
 8048758:       83 ec 18                sub    $0x18,%esp
 804875b:       c7 44 24 04 ff ff 00    movl   $0xffff,0x4(%esp)
 8048762:       00
 8048763:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 804876a:       e8 7d ff ff ff          call   80486ec <__static_initialization_and_destruction_0(int, int)>
 804876f:       c9                      leave
 8048770:       c3                      ret
 8048771:       90                      nop

继续跟进 __static_initialization_and_destruction_0(int, int) 函数:

080486ec <__static_initialization_and_destruction_0(int, int)>:
 80486ec:       55                      push   %ebp
 80486ed:       89 e5                   mov    %esp,%ebp
 80486ef:       83 ec 18                sub    $0x18,%esp
 80486f2:       83 7d 08 01             cmpl   $0x1,0x8(%ebp)
 80486f6:       75 5b                   jne    8048753 <__static_initialization_
and_destruction_0(int, int)+0x67>
 80486f8:       81 7d 0c ff ff 00 00    cmpl   $0xffff,0xc(%ebp)
 80486ff:       75 52                   jne    8048753 <__static_initialization_
and_destruction_0(int, int)+0x67>
 8048701:       c7 04 24 d5 a0 04 08    movl   $0x804a0d5,(%esp)
 8048708:       e8 5b fe ff ff          call   8048568 <std::ios_base::Init::Init()@plt>
 804870d:       b8 88 85 04 08          mov    $0x8048588,%eax
 8048712:       c7 44 24 08 28 a0 04    movl   $0x804a028,0x8(%esp)
 8048719:       08
 804871a:       c7 44 24 04 d5 a0 04    movl   $0x804a0d5,0x4(%esp)
 8048721:       08
 8048722:       89 04 24                mov    %eax,(%esp)
 8048725:       e8 1e fe ff ff          call   8048548 <__cxa_atexit@plt>
 804872a:       c7 04 24 d4 a0 04 08    movl   $0x804a0d4,(%esp)
 8048731:       e8 3c 00 00 00          call   8048772 <A::A()>
 8048736:       b8 9e 87 04 08          mov    $0x804879e,%eax
 804873b:       c7 44 24 08 28 a0 04    movl   $0x804a028,0x8(%esp)
 8048742:       08
 8048743:       c7 44 24 04 d4 a0 04    movl   $0x804a0d4,0x4(%esp)
 804874a:       08
 804874b:       89 04 24                mov    %eax,(%esp)
 804874e:       e8 f5 fd ff ff          call   8048548 <__cxa_atexit@plt>
 8048753:       c9                      leave
 8048754:       c3                      ret

终于找到了调用类的构造函数的地方, 在调用完构造函数之后还通过 __cxa_atexit 函数 (这是glibc用于内部调用的函数, 功能和我们熟知的 atexit 函数类似) 注册了一个回调函数, 函数地址是 0x804879e, 可以猜测这个函数就是析构函数, 找到这个地址:

0804879e <A::~A()>:
 804879e:       55                      push   %ebp
 804879f:       89 e5                   mov    %esp,%ebp
 80487a1:       83 ec 18                sub    $0x18,%esp
...

果然如我们所想, 这样就保证了构造函数和析构函数的配对使用. 总结一下C++中全局变量初始化的过程:

_init -> __do_global_ctors_aux -> global constructors keyed to a -> __static_initialization_and_destruction_0(int, int) -> A::A()

这里只是从反汇编的角度进行分析, 如果需要更深入了解, 还得结合glibc和GCC的源码. 关于 .ctors 段也没有详细讲述, 这些都可以在《程序员的自我修养》中找到.

Wednesday, March 16, 2011

Introduction to Inspector

有时我们想要监视某个网页是否有更新, 但网站又没有提供RSS, 这时就只能定期去查看那个网页, 偶尔也会因此忘记. 这种枯燥、乏味的重复行为让人厌倦, 于是我实现了一个简单的小项目, 我把它叫做 Inspector. 输入你想要监视的网址, 选取你感兴趣的网页元素, 然后Inspector就会在有更新时通过Email通知你. It’s simple, huh?

下面是一些使用截图, 首先是选取网页元素:

Inspect

然后是Inspector发给你的通知邮件 (最好把邮件地址先加到联系人里, 一般来说第一次都会被判为垃圾邮件, 至少Gmail是这样):

Inspector Notification

网站只在Firefox、Chrome和Safari中测试过, 访问可能需要翻墙 (由于某种众所周知的原因, 你懂的). 目前项目代码已经托管在GitHub: https://github.com/xiaogaozi/inspector, 如果你有任何问题或者建议可以到 这个页面 提出.

接下来是一些实现这个项目过程中的技术吐槽, 不感兴趣的同学可以直奔 项目主站 围观.

选取网页元素这点是借鉴的Firebug, 同时借鉴的还有它的代码. 在使用JS绑定事件时遇到了 this 关键字引用错误问题, 最后在 “JavaScript: The Definitive Guide” 中找到了答案, 具体可参考书中17.1.5小节. 呵呵, JS基础没学好. “/inspect” 页面最下面的提示要感谢兰姐提醒用 iframe, 对于固定样式很有好处. 修正网页CSS、JS链接这个需要判断是否为绝对地址, 常规的也就是http或者https打头, 不过 The World Clock 网页的CSS引用很奇怪, 是 “//” 打头, 测试大猫的博客时也让我发现一个类似的bug. 像Facebook这样的通过代码动态加载CSS的也没办法, 还有如果是用 frameset 写的页面现在还暂时不支持.

页面抓取现在对于静态页面效果要好一点. 定位网页元素现在比较常用的有CSS selector和XPath, 前者我更熟一点, 不过Python标准库不支持, libxml2可以, 但它所需要的so库不能上传到GAE, 只能作罢, 而且它只能分析那些标准的XML, 遇到稍微不标准的就会出错, 这点和标准库一个德行. 但互联网上充斥着各种不标准, 这由不得开发者挑剔, 要对付它们, Beautiful Soup 是一个不错的选择. 但问题又来了, BS即不支持CSS selector, 也不支持XPath. 后来我找到一个日本人写的 BS扩展, 可以用上XPath. 至于CSS selector, 我也找到 一份Python实现, 需要结合Tidy来处理那些非标准页面, 不过我没试过.

能这么快完成, 也得益于GAE的便捷 (虽然前前后后花了有半个来月). 用户验证、Email服务、数据存储这些都能通过很简单的API搞定, 不过自带的Django模板引擎有点弱智, 一些高级语法支持不了.

页面风格抄的GitHub, 按钮是GitHub和Twitter的结合, 表格抄的GAE, About页面用AsciiDoc写的, 看起来还算凑合. 第一次用Closure Compiler, 第一次用CSSTidy, 压缩了确实不错.

最后, 如果你坚持看到这里的话, 感谢你的支持.

Sunday, January 30, 2011

ELPA: 下一代Emacs的扩展管理器

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1169

我们已经习惯了各种包管理器工具, Debian、Ubuntu有 apt-get, RedHat、Fedora有 yum, Arch有 pacman, Gentoo有 emerge, BSD有 port. 包管理器的存在让我们再也不用考虑安装、删除、升级软件的细节, 也不用为了各种包之间的依赖关系而头疼, 这也是我认为UNIX/Linux相比于Window$的一个很大的优点. 今天介绍的 ELPA (Emacs Lisp Package Archive) 就是专为Emacs设计的包管理器, Emacs发展至今已经拥有了数量庞大的第三方扩展, 但安装这些扩展的方式依旧非常原始. 下载压缩包, 解压, 放到Emacs可以识别的 load-path, 修改 “.emacs”, 这一系列动作对于用户来说实在是不够友好 (虽然Emacs用户普遍喜欢折腾). ELPA可以使得安装Emacs扩展就像使用发行版的包管理器工具安装软件一样便捷, 并且Emacs官方也 决定 在下一个Emacs版本, 即Emacs 24中默认集成ELPA. 这里 是GNU的官方ELPA页面, 在那里你也可以下载到已经集成了ELPA的Emacs 24测试版.

ELPA采用同 apt-get 等包管理器工具类似的原理, 会从指定的源服务器中下载扩展以及相关依赖扩展, 然后放到特定目录 (默认为 “~/.emacs.d/elpa/”), 可能会将Lisp代码编译成字节码, 最后激活它, 当下次Emacs启动时自动加载它.

虽然Emacs 24还没有正式发布, 但是我们已经能够提前使用ELPA (实际上ELPA已经发展了好几年). 步骤很简单, 如果你使用的是Emacs 22及以上版本, 把下面的代码复制到Emacs的 *scratch* buffer中, 将光标移动到代码的最后一行, 然后按下 C-j, 一切就都搞定了, 剩下的事情就交给代码去完成吧. 如果你使用的是Emacs 21及以下版本, 请参考 这个帮助页面.

(let ((buffer (url-retrieve-synchronously
               "http://tromey.com/elpa/package-install.el")))
 (save-excursion
   (set-buffer buffer)
   (goto-char (point-min))
   (re-search-forward "^$" nil 'move)
   (eval-region (point) (point-max))
   (kill-buffer (current-buffer))))

上面的代码执行完之后, ELPA就算安装完毕, 这时会多出一个 “~/.emacs.d/elpa/” 目录, 所有通过ELPA安装的扩展都会放在那里, 并且 “.emacs” 文件也被自动添加了加载ELPA的代码. 该如何通过ELPA安装扩展呢? 在Emacs中按下 M-x package-list-packages 会列出所有可供安装和已经安装的扩展, 如下图:

ELPA

将光标移动到需要安装的扩展上, 按下 i 键, 就会出现如上图的 “I” 标志, 选定好之后按下 x 键便开始安装扩展. 还有其它一些按键:

按键 功能
n
下移一行
p
上移一行
r
刷新扩展列表
i
标记为安装
d
标记为删除
u
取消标记
g
还原所有标记
x
执行标记
q
关闭当前窗口
h
帮助
?
查看当前扩展的详细注释信息

当前ELPA的最大缺点是扩展数量还不是很多, 截至写这篇文章时仅有134个扩展. 有人说是因为ELPA只接受自由软件的缘故, 但我觉得大多数Emacs扩展开发者并不会太在意这个, 可能最大的原因还是因为大部分开发者都不知道ELPA的存在. Emacs发展这么多年, GNU也没有 像Vim那样 集中管理各种第三方扩展, 好在官方也终于决定在Emacs 24中集成ELPA, 可以预想将来也会逐渐丰富扩展仓库. 如果你是第三方扩展的开发者并且也愿意将扩展提交给ELPA, 可以参考 这个页面, YASnippet的作者pluskid也写了 一篇给需要提交给ELPA的作者的建议, 希望ELPA的扩展仓库可以越来越丰富.

最后, GNU正在寻找用于Emacs 24的合适配色主题, 有兴趣参与的同学可以访问 这个页面 自定义一份主题提交给GNU, 也许你的方案就会出现在下一个版本的Emacs中. 参与开源, 我为人人, 人人为我.

Sunday, January 23, 2011

解决Mac中Emacs、MacVim不能正确读取PATH的问题

发现我现在的题目真是越来越长了, 越来越有论文开题的范了~

因为安装了MacPort, 我在 “.bashrc” 文件中自定义了 PATH 变量, 但当我打开 “Emacs.app” 时却发现 PATH 变量并没有被正确读取, 最明显的表现就是会找不到我用MacPort安装的程序. 经过一番查找才 发现, 原来是因为Mac OS X中的GUI程序在启动时并不鸟Shell初始化文件, 管你 bash, tcsh, 什么 sh. 在Mac中有一个专门给GUI程序 设置环境变量的地方: ~/.MaxOSX/environment.plist, 这是一个二进制文件, 你可以使用Xcode自带的 “Property List Editor.app” 来查看、修改其中的内容, Mac中还有一个命令 defaults 也可以对这种类型的文件进行读写. 我们可以稍微修改一下 “.bashrc” 文件以便每次自定义好 PATH 变量之后就立即修改 “environment.plist” 文件, 代码如下:

export PATH=...
defaults write ~/.MacOSX/environment PATH "$PATH"

这样当再次打开 “Emacs.app” 时查看到的 PATH 变量就是正确的值了. 同样我在使用 “MacVim.app” 时也遇到了这样的情况, 我本来以为通过上面的方法已经解决, 但… 你懂的, 再次经过一番查找, 很多人也只是推荐上面的方法. 还有 勾选MacVim的 “Preferences” 中的 “Launch Vim processes in a login shell.”, 但在我的MacVim中没有找到这个选项, 应该是新版本取消掉了. 我怀疑这是MacVim的bug (不然为啥 “Emacs.app” 好好的…), 不过貌似也不是所有人都有遇到. 而我现在找到的唯一解决办法是直接从Terminal中执行 “mvim” 命令来启动MacVim, 这样就会继承Shell的环境变量, 不过这也是不得已才想出的临时解决办法, 毕竟我以后就不能直接用Quicksilver来启动MacVim了, 真是遗憾.

综上, Mac的App真够折腾.

Friday, January 21, 2011

AsciiDoc简介

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1157

AsciiDoc是什么?

AsciiDoc 是一种简单的基于纯文本的文档生成工具, 与它类似的还有 reStructuredText, Markdown. 说是生成文档, 其实它可以将纯文本文件转换成各种类型, 比如:

使用AsciiDoc进行文档编写最著名的恐怕是Git官方的 Git User’s Manual (我表示对于初学者很难看懂), 这篇博客也是通过AsciiDoc生成, 文后会附上本文的原始代码以便参考.

Thursday, January 20, 2011

小技巧: 在Mac中添加字体, 并让XeTeX能够找到

当使用LaTeX排版时, 我习惯于使用Adobe的中文字体, 生成出来的PDF效果很好. 但由于某种众所周知的原因, 各种操作系统都没有自带, 因此我需要手动添加.

以前在使用Ubuntu时, 只需要将字体文件放到"/etc/fonts/fonts.conf"配置文件中指定的字体目录, 再执行"fc-cache -f"即可. 而Mac OS X提供了更简便的方法进行字体的安装, 只需要打开"Font Book", 在"File"菜单中点击"Add Fonts..."即可. 不过有一点必须注意, 字体必须安装到"Computer"这个"Collection"中, 否则在使用xelatex编译时会找不到字体. 建议修改"Font Book"设置中的"Default Install Location"为"Computer", 如下图.

Thursday, November 4, 2010

使用MinGW编译FFmpeg

友情提示: 编译FFmpeg本来就是一件体力活, 使用MinGW来编更是相当蛋疼, 因此, 如果你没有刚刚作出一个非常艰难的决定, 请立即停止这种自虐行为.

FFmpeg的人估计恨死Window$了, 如果你曾经有过使用VS编译的想法, 现在可以放心抛弃这种打算了. 这时如果你依然很不幸需要用到MinGW来编译, 那么下面的一些提示应该对你的编译过程有所帮助.

  1. configure的参数会稍微特别点:
    $ ./configure --enable-memalign-hack --disable-debug
    
    --disable-debug建议加上, 否则编译出的archive会很大很大...
  2. 现在先不忙着敲make, 我们需要patch一下MinGW的库文件, 否则就会出现诸如"implicit declaration of function 'strncasecmp'"这样的错误. 打开这个链接: http://fate.arrozcru.org/mingw32/patches/, 依照着里面的diff文件挨个patch吧, 完成就可以顺利编译了, 别忘了还要安装Yasm哦.

  3. 编译完成之后你应该可以在各个子目录中找到archive文件, 拿着这些文件做爱做的事吧.

不过小a娘也不好调教, 如果你需要再拿去链接, 可以参考FFmpeg这篇官方指南: http://www.ffmpeg.org/general.html#SEC23. 你可能还会遇到下面一些链接错误.

  • "unresolved external symbol _strcasecmp", 这个不知是不是MinGW的bug, 居然没有提供strcasecmp()函数, 解决方法是自己实现一个相同的函数, 可以参考GNU C Library
  • "unresolved external symbol __imp____lc_codepage", 这是一个更加诡异的错误, 解决方法我在这篇文章中找到, 主要就是把"Runtime Library"从/MD改为/MT
  • "error LNK2005: XXX already defined in LIBCMT.lib MSVCRT.lib", 最诡异错误出现, 操蛋的VS库啊, 在这篇文章的帮助下, 找到了解决办法, 在"Ignore Specific Library"里添加"MSVCRT.lib"即可.

Friday, October 15, 2010

Wish

April Story

淡淡的灯光, 轻轻的音乐, 飘扬的樱花, 已经许久没有看岩井俊二的电影, 对于《四月物语》, 一看就能知道这是他的电影. 电影结束得让我有点意犹未尽, 榆野卯月一个人打着红色的雨伞站在那里, 默念着心中的话语. 看着雨伞上滴落的雨水, 绽放出一丝笑容, 曾几何时, 我也会这样傻傻地干着傻傻的事. 不是因为雨水多么可爱, 只因心中有那么一股暖流在那时流过, 因为某个人, 因为某件事. 在那个阳光明媚的午后, 我也傻傻地望着不远的转角, 期待着某个身影的出现.

喜欢电影从头至尾的清新感觉, 日本干净的街道, 温馨的房间, 以及空旷草地上那抹夕阳. 我跟珍妮说我以后也想要一个很温馨, 很舒适的房子, 不需要多大, 我也不在乎它是租来的, 还是买来的. 但是它会有那种米色的窗帘在卧室, 当窗外阳光透进来时屋内会格外温馨. 有一张很大很舒服的床, 让人看到的第一眼就想要跳上去. 客厅会有布料沙发, 上面或许还有几个柔软的抱枕和玩具. 窗帘是白色的, 会给人一种清新的感觉. 还要一盏小台灯, 夜幕降临时它能为我照耀出轻柔的灯光. 也许, 也许我还能抱着你静静地倚在那里.

中午和同事在附近尝了广东的早茶, 餐厅是一个有着幽暗灯光的地方, 倒很适合情侣. 虽然叫做早茶, 不过不是去品茶, 广东的早茶有着各种小吃和点心, 再配上几碗恬淡的粥, 油而不腻. 也许我也该慢慢学着懂得生活, 从小到大的教育是教我怎样学习, 怎样考上好学校, 却没有教我怎样生活, 生活的本质又是什么. 当集体的信仰缺失, 就会造成一个民族的精神滞后.

我跟大猫说我们还年轻, 有那么多的事等着我们去做, 不管我们的未来如何, 总要知道希望就在前方, 就像很久之前一位同学告诉我说: 希望一定会实现的. 这句话我一直记着.

Sunday, September 19, 2010

那些与彩蛋有关的事情

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1006

前几天@liancheng同学在Twitter上传说中GCC有一个关于#pragma的彩蛋, 大致是说Stallman同学对#pragma很有意见, 因此在GCC中埋了一个彩蛋, 只要发现#pragma就会蹦出俄罗斯方块. 这个很有意思的八卦让我想要一探究竟, 经过一番查找, 这个彩蛋的确是存在的, 不过不是蹦出俄罗斯方块, 根据Wikipedia上一篇文章的介绍, GCC会试图依次启动Hack, Rogue, 以及Emacs中的Towers of Hanoi (汉诺塔), Hack和Rogue都曾经在「桃源」的另一篇文章[经典游戏推荐: NetHack]中提及. 不过这个彩蛋现在已经不存在了, Stallman同学曾经在接受采访时谈到 (请在采访稿中搜索"Easter eggs"), 这个彩蛋其实是针对C标准开的一个玩笑, 后来由于某种原因被移除. 幸运的是, GCC的古老代码依然存在, 下面是从GCC 1.21中提取出来的"遗体":
/* This was a fun hack, but #pragma seems to start to be useful.
   By failing to recognize it, we pass it through unchanged to cc1.  */

/*
 * the behavior of the #pragma directive is implementation defined.
 * this implementation defines it as follows.
 */
do_pragma ()
{
  close (0);
  if (open ("/dev/tty", O_RDONLY) != 0)
    goto nope;
  close (1);
  if (open ("/dev/tty", O_WRONLY) != 1)
    goto nope;
  execl ("/usr/games/hack", "#pragma", 0);
  execl ("/usr/games/rogue", "#pragma", 0);
  execl ("/usr/new/emacs", "-f", "hanoi", "9", "-kill", 0);
  execl ("/usr/local/emacs", "-f", "hanoi", "9", "-kill", 0);
nope:
  fatal ("You are in a maze of twisty compiler features, all different");
}

关于#pragma彩蛋的故事结束了, 但彩蛋本身就像一个充满魔力的物体, 一直是Geek间津津乐道的话题. Linux中的彩蛋不胜枚举, LinuxTOY有一篇文章大概介绍了其中的一小部分. 还有著名的Google Reader中的Konami Code彩蛋. xkcd的命令行版unixkcd更是隐藏了大量彩蛋, 其中就包括Konami Code. 还有一个颇有意思的彩蛋, 在Emacs中按下"M-x butterfly C-M-c"即可, 这个彩蛋的由来是因为xkcd上曾经有一篇漫画 (这篇漫画很有趣, 推荐大家看看, Linux程序员定会泪牛满面, lol) 虚构了Emacs中有这么一个彩蛋, 于是开发Emacs的那群家伙就真的在Emacs 23.1中加入了这个彩蛋...

彩蛋是程序员创造的独一无二的标志, 也许是为了好玩, 也许是为了吐槽, 也许是为了方便调试, 总之彩蛋给这个世界带来了一个值得纪念的回忆. 如果你还知道什么有趣或者有意义的彩蛋, 欢迎分享, :)

For LaN

Thursday, August 19, 2010

小工具介绍: XBindKeys, Global Menu

原文发表于「桃源」: http://linux.cuit.edu.cn/?p=987

小高子一直都是一个近乎狂热的键盘控, 寻找各种可以通过键盘实现的操作. 自从结识了GNOME Do这个神器一般的软件, 启动各种程序再也没有用到鼠标. 不过有一点是GNOME Do办不到的, 当Firefox有多个账户时, 这时通过GNOME Do启动Firefox就有点混乱, 每次必须通过冗长的命令行在多个Firefox账户间切换. 可不可以把这些命令绑定到键盘上呢? 那就是今天的第一位主角了: XBindKeys.

安装好XBindKeys之后, 先生成默认的配置文件:
$ xbindkeys --defaults > ~/.xbindkeysrc
配置文件的语法规则也很简单, 第一行为命令, 第二行为快捷键, 比如:
"firefox -P default -no-remote"
  control + shift + f
这样就可以把任何命令绑定到键盘上了.

Global Menu最初是大猫同学在Twitter上推荐的, 听名字很容易知道这个软件是用来干嘛的, 效果图如下:

Global Menu

怎么安装呢? 这里有一个官方安装指引, Ubuntu用户可以通过PPA源:
$ sudo add-apt-repository ppa:globalmenu-team/ppa
$ sudo apt-get update
$ sudo apt-get install gnome-globalmenu
将menu bar放在屏幕最顶端的设计源于Mac OS, 不过这可不仅仅是单纯的创新或者为了好看, vgod同学的这篇文章详细分析了这种GUI设计背后的数学背景.