主要是看到了换种方式使用星际译王这篇文章,里面是用的 awesome。这两天在 xmonad 折腾了一下,虽然没有像原文中那样好用,不过也算是可用了。

xmonad 没有自己的通知窗口,这里只能用 notify-send 来发送翻译结果。基本思路很简单,就是按照以下步骤来:

  1. 获取选中的文字,或者是输入的文字,这里使用 dmenu 获取用户输入
  2. 调用 sdcv 获得翻译后的文字
  3. 调用 notify-send 显示翻译结果

折腾了两天原因主要是卡在第三步上了,一共遇到两个问题。

第一个问题是只显示了一个标题,不显示翻译内容。在我手动将翻译结果一行一行的使用 notify-send 显示后,发现只要带有 < 号的都不可以;后来才知道我用的 notification-daemon 可以使用 html 式标签对显示内容进行格式化。而 sdcv 输出的结果有 html 的一些特殊字符,导致解析失败。

第二个问题是乱码,显示的中文都变成了乱码,但是在终端中手动发送中文字符是没有问题的。后来看了 xmonad-contrib 的源代码才知道我使用的 safeSpawn 函数会使用 encodeString 对输入的参数进行编码。暂时不清楚为什么会对参数编码,简单的在配置文件中定义一个 mySafeSpawn 函数搞定。

下面是配置方法,首先,引入一些模块:

import Data.Char
import XMonad.Util.XSelection
import System.Posix.Process (createSession, executeFile, forkProcess)

定义快捷键,我这里使用 modMask + s 对选中的文件进行翻译,使用 modMask + Shift + s 键弹出 dmenu,对输入的文字进行翻译:

...
myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $

    -- launch a terminal

    [ ((modm .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
      ...
      , ((modm              , xK_s),      getSelection  >>= sdcv)
      , ((modm .|. shiftMask, xK_s),      getDmenuInput >>= sdcv)
      ...
    ]
...

其中 getSelection 是获取选中文字的函数,包含在 XMonad.Util.XSelection 模块中;getDmenuInput 函数是用来获取 dmenu 输入的文字的,这里过滤了不可见字符:

getDmenuInput = fmap (filter isPrint) $ runProcessWithInput "dmenu" ["-p", "Dict: "] ""

sdcv 函数完成调用 sdcv 取得翻译结果,并调用 notify-send 进行发送:

sdcv word = do
    output <- runProcessWithInput "sdcv" [word] ""
    mySafeSpawn "notify-send" [word, trString output]

这里使用了 mySafeSpawn 去执行 notify-send 命令,mySafeSpawn 是从 XMonad.Util.Run 里面拷贝过来的,只是去掉了 encodeString 的调用:

mySafeSpawn :: MonadIO m => FilePath -> [String] -> m ()
mySafeSpawn prog args = io $ void_ $ forkProcess $ do
    uninstallSignalHandlers
    _ <- createSession
    executeFile prog True args Nothing
        where void_ = (>> return ()) -- TODO: replace with Control.Monad.void / void not in ghc6 apparently

trString 函数是用于将翻译后结果中的 html 特殊字符进行转义的,我这里只对<>&三个符号进行了转义,如果你使用的是 xfce4-notifyd 这样的服务端,应该就不需要转义了:

trString = foldl (\s c -> s ++ (trChar c)) ""

trChar c
    | c == '<' = "&lt;"
    | c == '>' = "&gt;"
    | c == '&' = "&amp;"
    | otherwise = [c]

修改完后,使用 modm + q 重启 xmonad,在浏览器或者终端中选中一个单词,然后按 modMask + s 应该就能再通知窗口中显示翻译的结果了。不过与我参考的原文相比较,还是有不完美的地方,那就是不能使用快捷键关闭翻译窗口,只能点一下鼠标,或者等待超时自动关闭。不过暂时先这样了。