我一直用的是 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))
这个问题请参考 这里.
可以一个一个使用 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-*-*-*-*-*-*-*"))
绑定 "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)
(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 秒之后最小化聚焦窗口。
用 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
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 时一定要小心,如果用 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)
(bind-keys window-keymap "W-Insert" 'insert-workspace-after)
我自定义了一些函数。如果你上次切换了窗口,那么按 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))))
(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))
先做一些包装函数,然后绑定到键上:
(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:
比如“把焦点移动到右边的窗口”?用一个扩展叫做 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 扩展就可以了。几乎所有窗口操作,比如最大化,最小化, 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)))
(bind-keys window-keymap "W-F9" 'run-shell-command)
;; 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 之间轮转,而且可以在任意条件的窗口之间轮转。
(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 起动屏保,锁定屏幕,并且关闭显示器。
下面这个设置使用了 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 "^emacs@" ,(lambda () (display-message "emacs loading...") (wy-run "emacs &")) ;; f2 is bound to toggle between the last two buffers ,(lambda (wind) (synthesize-event "F12" wind)))))
可以。因为 librep 支持 Scheme. 你可以使用 lexical binding 和 first class continuation. 举一个例子:
(define con t) (display-message (call/cc (lambda (ok) (define con ok) "kick"))) (con "haha")
(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)))
(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"))
(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, 它就到窗口中央。