Sawfish

实时控制 Sawfish

使用 sawfish-client

sawfish-client 可以对 Sawfish 进行实时控制,可以直接在终端里面运行 sawfish-client ,不过更好用的方法是使用 sawfish.elEmacs 里的 sawfish-mode 里面进行编辑,并用 C-x C-e 来执行相应的 lisp 语句。

值得注意的是,sawfish-client 里面有一个 (quit) 函数,它并不是退出 sawfish-client ,而是直接退出 Sawfish ,随便执行这个操作有可能造成你其 他 X 程序的数据丢失。

使用 sawfish-ui

sawfish-ui 是一个图形化的配置对话框,里面可以配置包括主题、快捷键等很 多东西,配置会被写入到 ~/.sawfish/custom 里面去。

使用扩展

使用 Sawfish 的扩展和 Emacs 差不多,也是通过一个 load-path 来加载扩展。 我把下载到的扩展全部放在 ~/.sawfish/extensions 里面,并在 ~/.sawfishrc 里面加入一句

然后就可以通过 require 来加载对应的扩展了。在在线资源这里可以找到许多下 载扩展的地方。这里介绍几个常用的扩展。

jump-or-exec

自从我第一次接触这个功能我就再也离不开它了,它让我保持某个程序只有一个 实例,并且能方便地切换。正如它的名字所言,jump 对应于跳转,如果程序正 在运行,则跳转到程序那里,即把程序的窗口切换到前台,这个功能让我不再需 要使用任务栏来切换窗口;exec 表示运行,如果程序没有运行,着运行它,通 常,新运行的程序会自动被带到前台,这样,不管程序有没有在运行,我只需要 一个快捷键就可以把它唤出,于是我根本不需要查看任务栏得出当前哪个程序有 没有在运行,因为我不再需要关心这个问题。这样,由于任务栏的两大存在理由 都已经没有意义,jump-or-exec 的出现让我最终抛弃了任务栏

这里可以下载到 jump-or-exec.jl ,在 Sawfish Wiki 上有一个例子讲解如 何配置。我建立了一个列表,把我常用的只需要启动一个实例的程序都配置为 jump-or-exec :

其中 kid-display-message 是我自己定义的一个工具函数,可以在这里找到。

更加智能的 emacsclient

Emacs 启动比较慢,一直以来有各种解决办法Emacs 自己提供的一个解决办法 是使用 emacsclient 来打开文件,这个方法的缺点是把要打开的文件发送给 Emacs 之后,Emacs 的主窗口并不能自动切换到前台,这个缺点可以通过窗口管 理器来解决,这里介绍使用 sawfish-client 的方法:

首先,在 ~/.emacs 里面加入 (server-start) 好让 Emacs 在启动的时候启动 server ,这样就可以使用emacsclient 来连接到 Emacs 了。然后在 Sawfish 里 面写一个切换到 Emacs 的函数,我这里使用了jump-or-exec ,虽然这不是必须 的,但是我平时也使用这个功能来切换到 Emacs

然后我可以写一个脚本来调用 emacsclient:

不过这样并不能满足要求,因为 Emacs 的启动需要几秒钟的时间,如果事先没 有启动 Emacs ,就调用 e ,则 sawfis-client -c jump-or-exec-emacs 立即 返回,这个时候 Emacs 还没有完全启动起来并执行 (server-start) ,所以 emacsclient 这个时候会抱怨找不到 server 。没关系,我们修改一下代码,如 果 Emacs 没有启动起来,就 sleep 几秒,在 Sawfish 里面 sleep 当然不好, 所以将作为一个返回值传回脚本 ,并由脚本来 sleep 。下面是修改后的 jump-or-exec-emacs 函数:

并修改脚本 e 为:

其中, jump-or-exec-emacs 里面的秒数可以根据自己 Emacs 的启动速度来设 定,这样是不是非常方便了?

iswitch-window

毕竟不是所有的程序都是只启动一个实例就够了,有些程序需要启动多个实例, 用 jump-or-exec 切换起来就比较痛苦,这里介绍了在 Sawfish 里面模拟 fluxbox 的把几个窗口放到一个窗口的多个 tab 里面的方法。

这里要介绍的 iswitch-window 是一个方便切换窗口的扩展,就像 Emacs 的 iswitch-buffer 一类的扩展那样,列出窗口列表,并根据你的输入动态更新,过 滤掉不匹配输入的项,切换起来还是非常方便的。配置也非常方便,只需要在 这里下载 iswitch-window.jl ,放到 load-path 里面,然后在 ~/.sawfishrc 里加上这两行即可:

快捷键

选择合适的快捷键

通常,常用的 ControlShiftAlt 相关的快捷键都被 Emacs 占据,不过 现在普通键盘上通常还有左右两个 Win 键,正好可以让我们用于窗口管理器的 操作。这里我把左边的 Win 键映射到 Super ,同时我也设定 wm-modifierSuper ,然后把右边的 Win 键映射到 Hyper 。通常我用 Hyper 来进行切换窗 口相关的操作(如 jump-or-execiswitch-window 等),而用 Super 来进行 窗口控制(如移动、改变大小、最大化、关闭等)。

映射 Win 键可以用 xmodmap 程序来完成,这里介绍了映射的方法。还可以用这 个方法来交换 Control 和 Caps Lock 键。这里是我的 ~/.Xmodmap 文件:

keycode 115 = Super_L
keycode 116 = Hyper_R
clear mod3
add mod3 = Super_L
clear mod4
add mod4 = Hyper_R

配置好之后要保证启动 X 的时候 xmodmap 程序去加载这个文件,重新启动一下 X ,并在一个终端模拟器里面输入 xmodmap ,看是否有我们写进去的这些内容,如 果没有的话,则要在 ~/.xinitrc~/.xsession 之类的文件里面手工加上一 句:

xmodmap ~/.Xmodmap

如何表示快捷键

Sawfish 里面表示快捷键的方法和 Emacs 里面差不多,但是又不是完全一样,最 郁闷的就是想要把某个快捷键绑定到某个命令上,但是有不知道该如何写。这里 有一个函数可以查看输入的快捷键的名字,有点类似于 Emacs 里面的 C-h k , 不过这个并不显示键的绑定信息,也不会连续抓取 key sequence ,但是还是很 有用的:

显示出来的快捷键的名字就可以直接作为 bind-keys 的参数。

使用 key sequence

Sawfish 也可以像 Emacs 那样使用 key sequence ,方法和 Emacs 差不多,都 是使用 keymap ,不过新版本的 Emacsdefine-key 等函数中会自动判断并 定义需要的 keymap ,而在 Sawfish 中则需要手动定义 keymap 。这里我写了 一个 kid-bind-keys ,能够自动定义 keymap :

这样,就可以像 Emacs 那样直接设定复杂的 key sequence 了,例如:

但是要保持定义快捷键的方式一致,如果你一会自己手动定义 keymap ,一会儿 使用 kid-bind-keys 来定义,有可能会出问题,因为 kid-bind-keys 并不知道 你定义的 keymap 。另外,如果一个快捷键有多种表示方式(例如,在我这里 Super-hW-h 是同样的,因为我把 wm-modifier 设定成了 Super 键),也不 要多种表示混用,否则会导致某一种表示方式定义的快捷键丢失。

抓图

为了方便抓图,我自己试着写了一个模块来完成这个工作:

要能够正常工作,还需要一个脚本 sawfish-capture

可以看见其实是使用 ImageMagick 包的 import 命令抓图,并用 xloadimage 显示出来,而那么多的代码其实主要是处理文件名方面,如果能够计算出几乎每 次都很让人满意的默认文件名的话,抓图就很惬意了!在输入文件名的那个窗口 里面,虽然编辑功能比不上 Emacs 的 minibuffer ,但是也能够进行文件名补 全了。

要使用这个功能,把他放到合适的路径下(这里介绍了应该把模块放到什么路径 下面),并让那个 ZShell 脚本可以运行(如果没有 ZShell ,你也可以用自己喜 欢的 shell 写一个类似的,或者,如果不需要抓图之后预览一下的话,可以直 接在 Sawfish 的 lisp 代码里面运行 import 抓图就行了。) ,然后可以绑定 到相应的快捷键:

Sawfish 小技巧

nil 的引用问题

注意在 Sawfish 配置里面, (null (car '(nil))) 返回为假,不过可以这样来 得到返回为真的值: (null (car `(,nil))) 。这是一个容易犯错误的地方,不 过在 Elisp 或者是 CommonLisp 中, nil'nil 是相等的,所以不会出现这 种情况。还有一个办法就是使用 () 来代替 nil(null (car '(()))) 求值就 会得到想要的 t

我想出现这样事情的原因是在 librep1 里面仅仅以 () 来表示假,而 nil 只 是一个普通的值为 ()symbol 而已,这个 symbol 本身并没有得到特殊对待 而理解为假。

显示消息

Sawfish 提供了 display-message 函数可以在屏幕上显示一段文字,不过这段 文字并不会自动消失,于是我自己弄了一个函数,可以使用 display-message 来显示一段文字,并让消息在指定的一段时间之后自动消失:

wm-modifier

在设定 Sawfish 的快捷键的时候经常看到 W-h 这类的快捷键, W 其实就是 wm-modifier ,这是可以定制的,不过通常我们设定他为 Super 键,这样可以 避免和 Emacs 的快捷键冲突。可以在 sawfish-ui 里面设定这个值。

启动程序

我使用 startx 来启动 X ,所以我的必要的程序都在 ~/.xinitrc 里面启动, 但是在 Sawfish 退出的时候并不知道其他程序的存在,所以他们经常是异常退 出。一个办法就是在 Sawfish 里面使用 start-process 来启动程序,并在退出 之前结束掉它们。

模块

Sawfish 的扩展可以写成一个一个的模块,通过 require 来加载,例如 sawfish.wm.util.prompt 。Sawfish 到 load-path 里面去找对应的模块,例 如,它会去 load-path 中的目录下面的 sawfish/wm/util 子目录下面寻找 prompt.jlprompt.jlc 文件来加载 sawfish.wm.util.prompt 模块。

Sawfish 在线资源

Footnote

1. librep 是 Sawfish 的脚本语言,一种很类似于 Elisp 的 Lisp 方言。