Sawfish Window Manager

Sawfish --- 我的新 WM

我一直用的是 FVWM,直到我发现 Sawfish 可以用 lisp 和 Scheme 来配置。这样我可以任意扩展这个 WM 达到我的功能。我可以在里面 使用函数,起动进程,线程,设置变量,环境,continuation,…… 我拥有大量其它人的扩展,我可以修改它们来适应自己的需要。还可 以在使用的同时练习我的 Scheme 和 Common Lisp !太棒了!

Sawfish 很像 Emacs,它的配置语言非常像(实际上就是) elisp,不 过它也可以使用 Scheme 的 lexical binding 和 first class continuation. 你可以看到我在后面有一个例子里使用了Scheme 的 closure。

关于 FVWM,FVWM 是很好的 WM,以前我一直用它。不过我觉得要是 它能用 LISP 或者 Scheme 配置就好了。如果你不想了解 LISP,那 么你可以试试 FVWM

配置要点。

sawfish-client 里面可以使用类似 Scheme48 的命令,比如 ,open ,in ,quit ... 注意这个 ,quit 是退出 sawfish WM 而不 是 sawfish-client! 小心!,describe 可以得到一个函数的 docstring 帮助。还有 ,apropos, ...

(require sawfish.wm.menus)

- Sawfish 可以设置一个 wm-modifier-value,作为 "W-next" 这样

的热键的 modifier。缺省的设置是 super 键,通常在 PC 键盘上 是 Win 键,我觉得这是很自然的用来操纵“窗口”的热键 modifier。所以以后的设置中如果用到 "W-next" 这样的说法,实 际上就是指 Win 和 PageDown 同时按下。

如果你的 Win 键似乎不起作用,可以把这些内容加到 ~/.sawfish/custom 文件。

(custom-set-typed-variable (quote wm-modifier-value) (quote
(super)) (quote modifier-list))

FAQ

"W-next" 里的 "W" 是指哪个键?

这个问题请参考 这里.

如何设置起动时需要起动的程序?

可以一个一个使用 system 调用,但是你一旦忘记写一个 "&" 就会 把整个 WM 挂在那里。所以最好生成新的进程运行程序。

可以做一个表,然后把那些程序和参数放进表里,然后起动这些进程 就行了,就像我这样:

(defun wy-construct-arg-list (str sep)
  "Construct a list from a string. Using sep as separetor."
  (interactive)
  (let* ((result nil)
         (add-to-the-list
          (lambda (x)
            (setq result (cons x result)))))
    (let loop ((s "")
               (left str)
               (ptr 0))
         (setq nextchar (substring left 0 1))
         (print s)
         (catch 'exit
           (cond ((and
                   (string-match "\\s" nextchar)
                   (> (length s) 0))
                  (add-to-the-list s)
                  (setq s ""))
                 ((= (+ 1 ptr) (length str))
                  (setq s (concat s nextchar))
                  (if (> (length s) 0) (add-to-the-list s))
                  (throw 'exit))
                 (t
                  (setq s (concat s nextchar))))
           (loop s (substring left 1) (+ ptr 1))))
    (reverse result)))

;; startup programs
(defun wy-start-entry (prog-entry)
  (interactive)
  (let ((process (make-process standard-output))
        (prog-entry-list (wy-construct-arg-list prog-entry "\\s")))
    (apply start-process process (car prog-entry-list) (cdr prog-entry-list))))

(setq startup-programs
      '("gnome-panel"
        "xset b off"
        "xloadimage -onroot -fullscreen /usr/share/backgrounds/images/leafdrops.jpg"
        "xsetroot -cursor .sawfish/sawfish.xbm .sawfish/sawfish_mask.xbm"
        "xdaliclock"
        "xscreensaver -no-splash"
        "fcitx"))

(mapc wy-start-up startup-programs)

(defun wy-cleanup-on-exit ()
  (mapc stop-process (active-processes)))

(add-hook 'before-exit-hook 'wy-cleanup-on-exit)

我挂了一个 wy-cleanup-on-exit 到 before-exit-hook, 退出时这 些被 wy-start-up 起动的程序都会被关闭。

wy-construct-arg-list 是我自己写的一个函数用来把命令行分解成 一个表。我不知道 rep 有没有现成的函数可以做这个事,所以自己 写了一个。

加载扩展时如何避免找不到文件时起动失败?

你可以使用一个叫做 require-try 的函数,如果要用的扩展找不到 就返回 nil. 而不会报错。

(defun require-try (#!rest args)
  (let ((result t))
    (mapc
     (lambda (e)
       (condition-case err
           (cond ((stringp e) (load-file e))
                 ((symbolp e) (require e))
                 (t (format standard-output
                            "Invalid arg to require-try: %s" e)
                    (setq result nil)))
       (file-error
        (format standard-output "Couldn't load extension: %s" e)
        (setq result nil))))
     args)
   result))

如何改变字体?

(setq default-font (get-font "-*-simsun-medium-r-*-*-14-*-*-*-*-*-*-*"))

如何让 Shift + 右键可以拖动窗口?

绑定 "W-Button1-Click1" 到 一个函数,在里面调用 move-window-interactively 就行了。比如下面这个 wy-move-window.

注意必须先打开 sawfish.wm.commands.move-resize, 否则解析不到 move-window-interactively.

(require 'sawfish.wm.commands.move-resize)
(defun wy-move-window ()
  (interactive)
  (move-window-interactively (current-event-window)))

(bind-keys window-keymap "W-Button1-Click1" 'wy-move-window)

如何使得 C-S-Button1 可以关闭程序窗口?

(bind-keys window-keymap "C-S-Button3-Click1"
'(delete-window (current-event-window)))

如何对窗口做上标记?

你可以用一个简单的函数:

(require 'sawfish.wm.util.decode-events)
(defun define-shortcut (win bind)
  "Ask for a key and bind that key to select the current
  window. Will happily destroy previous binds for that key."
  (interactive "%w\ne")
  (when win
    (let ((key (read-event "Press shortcut key")))
      (unless (equal bind key)
        (bind-keys global-keymap key `(display-window
        ,win))))))
(bind-keys window-keymap "M-s" 'define-shortcut)

以后按 M-s 接着再按一个热键就可以标记一个窗口。但是这样有可 能引起很多热键被 WM 截获。对于 Emacs 之类的程序不太好。

所以最好使用 bookmark.jl。 它可以对窗口使用任意热键标记,但 是你必须在这个热键之前执行 bookmark-goto。在bookmark-set 之 后按一个热键,以后 bookmark-goto,然后再按那个热键就可以直接 到达那个窗口了。

bookmark.jl 可以在这里 http://sawfish.skylab.org/WikiSawfishLibrary 找到。

以下是一些简单的配置:

(when (require-try 'bookmark)
  (bind-keys global-keymap "W-j" 'bookmark-goto)
  (bind-keys global-keymap "W-b" 'bookmark-set)
  (bind-keys global-keymap "W-l" 'bookmark-print-list)
  (bind-keys global-keymap "W-k" 'bookmark-goto-last)
  (setq auto-bookmark-list 
        '(( "^emacs" . e )
          ( "^locutus$" . l )
          ( "^ji$" . j )
          ( "Galeon$" . g )
          ( "Mozilla {Build ID: [0-9]+}$" . m)
          ( "Phoenix" . p )
          ( "rxvt" . t )))
)

如何显示我一个键的绑定信息?

我设计了一个函数,可以知道一个键正在进行什么操作。你只需要按 W-w 然后按你的热键,就能知道它在所有 keymap 里的 binding 和 它们的描述。我把 describe-symbol 和 describe-key 修改成了可 以向 string port 输出,而且修正了一个不能显示复合命令的bug。 从而可以把它们舒服的显示在屏幕上。就像 这样.

下面是函数:

(define (wy-describe-symbol fun #!optional stream)
  "Display the documentation of a specified symbol into stream."
  (describe-value (symbol-value fun t) fun)
  (format (if stream stream standard-output)
          "\n%s\n"
          (or (documentation fun nil (symbol-value fun t)) "Undocumented.")))

(define (wy-describe-key key #!optional map stream)
    "Print key's binding in keymap map, to stream stream."
    (require 'rep.lang.doc)
    (require 'sawfish.wm.commands.describe)
    (let (components)
      (letrec
	  ((loop
	    (lambda (keymap)
	      (let* ((binding (and key (search-keymap key keymap))))
		(when binding
		  (setq binding (car binding))
                  (format (or stream standard-output) "\n%s:\n" map)
		  (setq components (concat components
					   (and components ? )
					   (event-name key)))
		  (cond ((keymapp binding)
			 (loop binding))
			((and (symbolp binding)
			      (keymapp (symbol-value binding t)))
			 (loop (symbol-value binding)))
			(t
			 (format (or stream standard-output)
				 "`%s' is bound to `%s'"
				 components binding)
                         (setq command binding)
                         (while (car command)
                           (setq command (car command)))
			 (wy-describe-symbol command stream))))))))
	(loop (or map global-keymap)))))

(defun describe-what-am-i-doing ()
  "Read a event and search the keymaps for it's definitions. Detailed."
  (interactive)
  (let ((s (make-string-output-stream))
        (key (read-event (concat "Describe key: "))))
    (mapc 
     (lambda (map)
       (wy-describe-key key map s))
     '(global-keymap 
       window-keymap 
       title-keymap 
       root-window-keymap 
       border-keymap
       close-button-keymap
       iconify-button-keymap
       maximize-button-keymap
       menu-button-keymap
       ))
    (setq msg (get-output-stream-string s))
    (if (> (length msg) 0)
      (display-message msg
       `((background . ,tooltips-background-color)
         (foreground . ,tooltips-foreground-color)
         (x-justify . left)
         (spacing . 2)
         (font . ,tooltips-font)))
      (display-message (concat
                        "key `"
                        (prin1-to-string (event-name key))
                        "' is not bound"
                        )))))

(setq tooltips-background-color (get-color "black"))
(setq tooltips-foreground-color (get-color "red"))
(setq tooltips-font (get-font "-*-lucida-medium-r-*-*-14-*-*-*-*-*-*-*"))
(bind-keys global-keymap "W-w" 'describe-what-am-i-doing)

如何在屏幕上显示信息?

用 display-message 可以在屏幕中央显示字符串,用 display-tooltip 可以在鼠标处显示字符串。

如何定时执行操作?

用 rep 的 make-timer 函数就可以。比如:

(require 'rep.io.timers)
(make-timer 
 (lambda ()
   (iconify-window (input-focus)))
   2)

会在 2 秒之后最小化聚焦窗口。

如何得到 sawfish 知道一个窗口的一切?

用 window-snooper 可以得到 sawfish 知道的一切信息。包括窗口 参数,键绑定,窗口属性……

(require 'window-snooper)
(rplacd (cdr window-ops-menu)
         `(("Snoop" window-snooper)
          ,@(cddr window-ops-menu)))

如何方便的切换窗口?

用 iswitch-window 最好。不但可以正则表达式选取窗口,而且可以 用热键对它们进行 iconify, shade, ... 等操作。

具体可以的操作有这些:

;; C-s,A-s,TAB    find next matching window
;; C-r,A-r,M-TAB  find previous matching window
;; C-g,ESC        quit waffle
;; C-u            clear input buffer
;; backspace      delete previous character
;; C-z,A-z        iconify window
;; C-h,A-h        shade window
;; RET            select window

如何让 W-next 可以轮换窗口?

iswitch-window 可以方便的寻找到窗口,但是你总是需要按一下RET 才能到达。如果你只是想快速的轮换窗口,sawfish 有一个函数叫 cycle-windows,把它绑定到一个键就行了。

比如我绑定到了 W-next:

(bind-keys global-keymap "W-Next" 'cycle-windows)

因为我的 Emacs 用 C-next 轮换 buffer, Sawfish 用 W-next 当然很自然了。

如何让一个窗口一直在最上面?

改变窗口的层就行了。这里有3个函数可以帮助你设置窗口的层:

wy-raise-window-layer 可以帮你把窗口放到更上面一层, wy-lower-window-layer 可以帮你把窗口放到下面一层, wy-reset-window-layer 可以把窗口放到第 0 层。

(defun wy-raise-window-layer (window)
  (interactive "%w")
  (let ((layer (1+ (window-get window 'depth))))
    (display-message (concat 
                           "Layer " 
                           (prin1-to-string layer)))
    (set-window-depth window layer)))

(defun wy-lower-window-layer (window)
  (interactive "%w")
  (let ((layer (1- (window-get window 'depth))))
    (display-message (concat 
                      "Layer " 
                      (prin1-to-string layer)))
    (set-window-depth window layer)))

(defun wy-reset-window-layer (window)
  (interactive "%w")
  (let ((layer 0))
    (display-message (concat 
                      "Layer " 
                      (prin1-to-string layer)))
    (set-window-depth window layer)))

(bind-keys window-keymap "W-KP_Add" 'wy-raise-window-layer)
(bind-keys window-keymap "W-KP_Subtract" 'wy-lower-window-layer)
(bind-keys window-keymap "W-=" 'wy-reset-window-layer)

如何防止 system 起动的程序把 WM 挂起?

使用 system 时一定要小心,如果用 X 的程序,后面一定要加 "&" 让它在后台运行,否则 WM 就会被挂起,键盘会被锁定。有时你不得 不重新起动。

为了避免这种情况,让程序的起动更加流畅。你可以参考这个函数:

(defun wy-run (cmd)
  "Run a command in a new process. And let it start in
background. Without stop the current WM execution"
  (interactive)
  (system 
   (if (string-match ".*&\\s*$" cmd)
       cmd
     (concat cmd " &"))))

我们用 (wy-run "程序") 代替 (system "程序")。这样每次起动程 序时,会首先起动一个新的线程。在这个线程里调用 system 起动那 个程序。如果命令行最后没有 "&", 我们自己给它加一个 "&". 这样 就不会挂起我们的 WM 了。

能不能选中一些字符就进行拼写检查?

用 sawfish-spell 就行了。随便在那个窗口选中一些字符然后按 C-W-c 就行了。

(when (require-try 'sawfish-spell)
  (bind-keys global-keymap
             "C-W-c" spellcheck-interactive))

能不能方便的缩小窗口使它不要挡住它下面的窗口?

你可以使用 shrink-windows-to-fit 扩展。绑定4个键到4个方向缩 小的函数就行了。Magic!

(when (require-try 'shrink-windows-to-fit)
  (bind-keys window-keymap "M-W-Left" 'shrink-window-left)
  (bind-keys window-keymap "M-W-Right" 'shrink-window-right)
  (bind-keys window-keymap "M-W-Up" 'shrink-window-up)
  (bind-keys window-keymap "M-W-Down" 'shrink-window-down)
)

如何设置菜单?

只要定义一个菜单,把它绑定到一个键上就行了。注意,必须先加载 sawfish.wm.menus 才能使用 popup-menu 函数。

(setq menu-program-stays-running t)

(require 'sawfish.wm.menus)
(setq my-menu
    '(("screen" (wy-run "rxvt -e screen -xRR &"))
      ("rxvt" (wy-run "rxvt &"))
      ("Emacs" (wy-run "emacs &"))
      ("Gthumb" (wy-run "gthumb &"))
      ("Phoenix" (wy-run "phoenix &"))
      ("gv" (wy-run "gv &"))
))


(defun popup-my-menu ()
  (interactive)
  (popup-menu my-menu))

(bind-keys global-keymap "W-Button1-Click1" 'popup-my-menu)
(bind-keys global-keymap "W-Button2-Click1" 'popup-root-menu)
(bind-keys global-keymap "W-Button3-Click1" 'popup-window-menu)
(bind-keys window-keymap "W-Button1-Click1" 'popup-my-menu)
(bind-keys window-keymap "W-Button2-Click1" 'popup-root-menu)
(bind-keys window-keymap "W-Button3-Click1" 'popup-window-menu)

如何绑定一个热键插入一个新的 workspace 在当前的 workspace

之后?

(bind-keys window-keymap "W-Insert" 'insert-workspace-after)

如何返回上次访问过的窗口和 workspace?

我自定义了一些函数。如果你上次切换了窗口,那么按 M-` 就可以 回到上一个窗口,如果你上次切换了 workspace,那么 M-` 就回到 上一个工作区。

;;; switch to last workspace or window
(define window-or-workspace? 'window)
(add-hook 'leave-workspace-hook
          (lambda (current)
            (setq workspace-last current)
            (setq window-or-workspace? 'workspace)))

(add-hook 'focus-out-hook
          (lambda (current)
            (setq window-last current)
            (setq window-or-workspace? 'window)))

(bind-keys global-keymap
    "W-`"
    (lambda ()
      (if (eq window-or-workspace? 'workspace)
          (select-workspace workspace-last)
        (display-window window-last))))

如何绑定一些键可以在4个角落移动窗口?

(defun shove-window (dir &optional no-focus)
  "Move focused window 'left, 'right, 'up or 'down to screen edges."
  (interactive)
  (let* ((win (input-focus))
         (pos (window-position win))
         (dim (window-frame-dimensions win))
         (endx (car pos))
         (endy (cdr pos)))
    (cond ((eq dir 'left) (setq endx 0))
          ((eq dir 'right) (setq endx (- (screen-width) (car dim))))
          ((eq dir 'up) (setq endy 0))
          ((eq dir 'down) (setq endy (- (screen-height) (cdr dim)))))
    (move-window-to win endx endy)
    (unless no-focus
      (display-window win))))


(bind-keys global-keymap "C-M-Left" '(shove-window 'left))
(bind-keys global-keymap "C-M-Right" '(shove-window 'right))
(bind-keys global-keymap "C-M-Up" '(shove-window 'up))
(bind-keys global-keymap "C-M-Down" '(shove-window 'down))

如何绑定一些键进行最小化,取消最小化,shade, unshade?

先做一些包装函数,然后绑定到键上:

(defun wy-shade-window (win)
  (interactive "%w")
  (shade-window win))

(defun wy-unshade-window (win)
  (interactive "%w")
  (unshade-window win))

(defun wy-iconify-window (win)
  (interactive "%w")
  (iconify-window win)
  (setq last-iconified-window win))

(defun wy-uniconify-window ()
  (interactive)
  (uniconify-window last-iconified-window)
  (display-window last-iconified-window))

(bind-keys window-keymap "W-Up" 'wy-shade-window)
(bind-keys window-keymap "W-Down" 'wy-unshade-window)
(bind-keys window-keymap "C-W-Down" 'wy-iconify-window)
(bind-keys window-keymap "C-W-Up" 'wy-uniconify-window)

(defun wy-shade-window (win)
  (interactive "%w")
  (shade-window win))

(defun wy-unshade-window (win)
  (interactive "%w")
  (unshade-window win))

(defun wy-iconify-window (win)
  (interactive "%w")
  (iconify-window win)
  (setq last-iconified-window win))

(defun wy-uniconify-window ()
  (interactive)
  (uniconify-window last-iconified-window)
  (display-window last-iconified-window))

(bind-keys window-keymap "W-Up" 'wy-shade-window)
(bind-keys window-keymap "W-Down" 'wy-unshade-window)
(bind-keys window-keymap "C-W-Down" 'wy-iconify-window)
(bind-keys window-keymap "C-W-Up" 'wy-uniconify-window)

如何绑定一个键可以把鼠标抓到聚焦窗口中央?

(require 'sawfish.wm.focus)
(defun wy-warp-to (x y)
  (interactive)
  (let* ((win (input-focus))
         (xpix (floor (inexact->exact (* x (car (window-dimensions win))))))
         (ypix (floor (inexact->exact (* y (cdr (window-dimensions win)))))))
    (warp-cursor-to-window win xpix ypix)))

(setq warp-to-window-enabled t)
(bind-keys global-keymap "W-c" '(wy-warp-to 0.5 0.5))

注意,你必须设置 warp-to-window-enabled 为 t.

如何把窗口移动到屏幕中间,如何在四角循环移动窗口?

可以使用一个扩展叫做 animate-move.

(when (require-try 'animate-move)
  (bind-keys window-keymap
             "M-W-s" 'toggle-window-shaded
             "M-W-i" 'iconify-window
             "M-W-c" 'animate-center-window
             "M-W-k" 'delete-window
             "M-W-r" 'rotate-move))

如何改变窗口的风格

使用 set-frame-style 可以改变窗口边框风格。比如

(set-frame-style (get-window-by-name-re "rxvt")
'microGUI)

可以把名字匹配 "rxvt" 的窗口,都设置成microGUI 的风格。

(find-all-frame-styles)

可以得到一个所有 frame-style 的 list. style 就是所谓“主题”。

如何使一个窗口不被装饰?

这个你需要用 set-window-type 修改窗口的 window-type 属性。比 如,

(set-window-type (get-window-by-name-re "[Dd]ali") 'unframed)

可以把 xdaliclock 的窗口设置为不加标题和边框。另外还有很多 window-type:

default
缺省类型。
transient
临时窗口。就像那种弹出窗口。
shaped
非矩形窗口。只给它标题栏,而没有边框。
shaped-transient
上面两个的组合。
shaded
只有标题栏。
shaded-transient
shaded 而且 transient.
unframed
没有任何装饰。

如何可以按方向聚焦窗口?

比如“把焦点移动到右边的窗口”?用一个扩展叫做 focus-by-direction 就行了:

(require-try 'focus-by-direction)
(bind-keys window-keymap "C-W-KP_Up" 'focus-north-warp)
(bind-keys window-keymap "C-W-KP_Left" 'focus-west-warp)
(bind-keys window-keymap "C-W-KP_Right" 'focus-east-warp)
(bind-keys window-keymap "C-W-KP_Down" 'focus-south-warp)

如何 undo?

使用 undo 扩展就可以了。几乎所有窗口操作,比如最大化,最小化, shade, 改变大小,移动,……都可以 undo.

如何绑定一些键可以方便的抓图?

(defun capture-root-window ()
  (interactive)
  (make-thread
   (lambda ()
     ((system "import -window root shot.png")
      (system "display shot.png&")))))


(defun capture-this-window ()
  (interactive)
  (let ((w (current-event-window)))
    (when w
      (make-thread
       (lambda ()
         (display-message (concat
                           "import -window "
                           (prin1-to-string (window-id w)) " window.png"))
         (system (concat "import -window "
                         (prin1-to-string (window-id w)) " window.png"))
      (system "display window.png&"))))))


(defun capture-region ()
  (interactive)
  (let
    ((process (make-process standard-output)))
    (start-process process "import" "capture.png")))

(bind-keys global-keymap "F7" 'capture-root-window)
(bind-keys window-keymap "W-F7" 'capture-this-window)
(bind-keys window-keymap "M-F7" 'capture-region)

如何绑定一些键可以方便的关闭窗口?

(bind-keys window-keymap "C-S-Button3-Click1"
'(delete-window (current-event-window)))

如何绑定一些键可以方便的执行 shell 命令?

(bind-keys window-keymap "W-F9" 'run-shell-command)

如何设定一些热键方便的操纵 xmms?

;; short cuts for xmms
(bind-keys global-keymap "Pause" '(wy-run "xmms -u&"))
(bind-keys global-keymap "W-KP_Left" '(wy-run "xmms -r&"))
(bind-keys global-keymap "W-KP_Right" '(wy-run "xmms -f &"))
(bind-keys global-keymap "W-KP_Insert" '(wy-run "xmms -p &"))
(bind-keys global-keymap "W-KP_Delete" '(wy-run "xmms -s &"))
(bind-keys global-keymap "W-KP_Enter" '(wy-run "xmms -u &"))

如何绑定一些键可以方便的在 rxvt 之间轮换?

设计一些函数就可以完成这个功能。不过这些函数还有更多的功能。 不但可以在 rxvt 之间轮转,而且可以在任意条件的窗口之间轮转。

(defun name-match-p (win name)
  (string-match name (window-name win)))

(defun class-match-p (win class)
  (string-match class (nth 2 (get-x-property win 'WM_CLASS))))

(defun rotate-around (elem lst)
  (if elem
      (append (cdr (memq elem lst)) (reverse (memq elem (reverse lst))))
    lst))

(defun rotate-list-left (lst)
  (append (cdr lst) (list (car lst))))

(define next-window-by
  (let ((my-windows (window-order current-workspace)))
    (lambda (pred repeating? . args)
      (if repeating?
          (setq my-windows (rotate-list-left my-windows))
        (setq my-windows (window-order current-workspace)))
      (catch 'foo
        (mapc (lambda (w)
                (when (apply pred w args)
                  (throw 'foo w)))
              (rotate-around (input-focus) my-windows))
        nil))))

(defun focus-next (pred &rest arg)
  (let ((repeating? (eq (car last-command) 'focus-next)))
    (let ((win
           (if repeating?
               (apply next-window-by pred t arg)
             (apply next-window-by pred nil arg))))
        (display-window win))))

(bind-keys global-keymap "H-e" '(focus-next class-match-p t "emacs"))
(bind-keys global-keymap "H-M-r" '(focus-next class-match-p nil "rxvt|xterm"))
(bind-keys global-keymap "H-r" '(focus-next class-match-p t "rxvt|XTerm"))
(bind-keys global-keymap "H-m" '(focus-next class-match-p t "XMMS"))
(bind-keys global-keymap "H-x" '(focus-next name-match-p t "xv|ImageMagick"))

最后两句可以使得每按一下 H-r 就跳到下一个 rxvt, 每按一下 H-e 就跳到下一个 emacs. 每按一下 H-M-r 就跳到下一个 rxvt 或者 xterm, 而且可以跨过 workspace 轮转。

如何设置热键锁定屏幕?

(bind-keys global-keymap
	"C-Pause" '(system "xscreensaver-command -activate&"))

(bind-keys global-keymap "C-Break"
    '(system "xscreensaver-command -lock &"))

(bind-keys global-keymap "M-Pause"
    '(system "xscreensaver-command -lock & (sleep 2; xset dpms force off) &"))

以上的设定是:C-Pause 起动屏幕保护, C-Break 起动屏保锁定屏 幕,M-Pause 起动屏保,锁定屏幕,并且关闭显示器。

如何设置热键可以马上跳到 Emacs 的窗口?

下面这个设置使用了 jump-or-exec。按下 W-e 时:

(when (require-try 'jump-or-exec)
  ;; load emacs
  ;; or switch to it
  ;; and toggle my notes buffer (bound to F1) if already focused
  (bind-keys global-keymap
             "W-e" `(jump-or-exec "^[email protected]"
                     ,(lambda ()
                        (display-message "emacs loading...")
                        (wy-run "emacs &"))
                     ;; f2 is bound to toggle between the last two buffers
                     ,(lambda (wind)
                        (synthesize-event "F12" wind)))))

Sawfish 可以用 Scheme 吗?

可以。因为 librep 支持 Scheme. 你可以使用 lexical binding 和 first class continuation. 举一个例子:

(define con t)
(display-message (call/cc (lambda (ok) (define con ok) "kick")))
(con "haha")

常用函数

选择窗口:

current-event-window
这就是得到发生事件时刻窗口的最好办法。
get-window-by-name(-re)
按名字寻找窗口,返回一个窗口对象。
get-window-by-id
以 window id 返回窗口对象。
get-window-by-class-re
这个缺省没有提供。不过我们可以轻松 实现一个:
(defun get-window-by-class-re (re)
  "Get a window by matching against its class"
  (let ((windows (window-order))
        w done match)
    (while (and windows (not done))
      (setq w (car windows))
      (setq windows (cdr windows))
      (when (string-match re (window-class w))
        (setq done t)
        (setq match w)))
    (when match match)))
window-order
得到一个所有窗口的 list. 如果加一个参数 workspace 就得到某个 workspace 的所有窗口 list.

一些自定义的方便函数

(defun name-match-p (win name)
  (string-match name (window-name win)))

(defun class-match-p (win class)
  (string-match class (nth 2 (get-x-property win  'WM_CLASS))))

有了这两个判断函数我们可以定义一些函数可以跳转,或者操纵那个 满足条件的窗口。

(defun rotate-around (elem lst)
  (if elem
      (append (cdr (memq elem lst)) (reverse (memq elem (reverse lst))))
    lst))

(defun rotate-list-left (lst)
  (append (cdr lst) (list (car lst))))

(define next-window-by
  (let ((my-windows (window-order current-workspace)))
    (lambda (pred repeating? . args)
      (if repeating?
          (setq my-windows (rotate-list-left my-windows))
        (setq my-windows (window-order current-workspace)))
      (catch 'foo
        (mapc (lambda (w)
                (when (apply pred w args)
                  (throw 'foo w)))
              (rotate-around (input-focus) my-windows))
        nil))))

(defun focus-next (pred &rest arg)
  (let ((repeating? (eq (car last-command) 'focus-next)))
    (let ((win
           (if repeating?
               (apply next-window-by pred t arg)
             (apply next-window-by pred nil arg))))
        (display-window win))))

比如,绑定 F4 可以在 rxvt 之间循环:

(bind-keys global-keymap "F4" '(focus-next class-match-p "rxvt"))

操纵窗口:

delete-window-safely
关闭窗口。就是说,如果窗口支持,那么 发送一个WM_DELETE_WINDOW 到目标窗口,如果不支持,那么鸣响。
move-window-interactively
移动窗口。但是它需要一个参数 win. 所以需要包装一下:
(defun wy-move-window ()
  (interactive)
  (move-window-interactively (current-event-window)))

移动光标

(defun wy-warp-to (x y)
  (interactive)
  (let* ((win (input-focus))
         (xpix (floor (inexact->exact (* x (car (window-dimensions win))))))
         (ypix (floor (inexact->exact (* y (cdr (window-dimensions win)))))))
    (warp-cursor-to-window win xpix ypix)))

比如我们可以绑定:

(bind-keys global-keymap "W-w" '(wy-warp-to 0.5 0.5))

这样鼠标不管在哪里,一按 W-w, 它就到窗口中央。

重要的变量

last-command
上一个执行的命令。可以依据这个来判断某些东西。 比如我们上面的 focus-next 函数就用这个来判断自己是否在被反复 执行,如果是这样它就不更新窗口列表。