主要是看到了换种方式使用星际译王这篇文章,里面是用的 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 应该就能再通知窗口中显示翻译的结果了。不过与我参考的原文相比较,还是有不完美的地方,那就是不能使用快捷键关闭翻译窗口,只能点一下鼠标,或者等待超时自动关闭。不过暂时先这样了。


公司的电脑装了 Gentoo 了,现在又开始折腾 xmonad 了,前两天看到 xmobar 有个 PipeReader 的插件,可以从一个管道中读取数据并显示。而 lrcdis 则支持将歌词输出到管道中。将这两者结合起来就可以了。

首先修改 xmobar 配置文件,加入 PipeReader,读取 /dev/shm/lrcfifo 这个管道:

...
, commands = [
    ...
    , Run PipeReader "/dev/shm/lrcfifo" "lrc"
    ...
             ]

, template = "%cpu% | %memory% * %swap% | %eth0% | %StdinReader% }{ %lrc% | %date% | %ZGSZ% | %default:Master%"
...

然后运行让 lrcdis 输出到管道

$ lrcdis -m fifo

重启 xmobar 看看效果,可惜什么都显示不出来。

看了下 lrcdis 的代码,发现写入管道的时候使用了 echo -n,没有写入换行符,导致 PipeReader 读取时没有返回,简单修改下就可以了,主要是不使用 echo -n 以及去掉两处多余的 echo,patch 如下:

--- lrcdis.org        2009-09-08 15:21:13.000000000 +0800
+++ lrcdis       2012-09-17 12:56:15.000000000 +0800
@@ -605,7 +605,7 @@
        local tm min sec tmptm
        if [ "$Dismode" = "fifo" -a -p "/dev/shm/lrcfifo" ];then
                echo "write to fifo"
-               echo -n "">/dev/shm/lrcfifo #防止读lrcfifo时等待 amoblin 4.14 9:49
+               #echo -n "">/dev/shm/lrcfifo #防止读lrcfifo时等待 amoblin 4.14 9:49
        fi
 
        case "${1:-rhythmbox}" in
@@ -739,7 +739,7 @@
        elif [ "$Dismode" = "fifo" -a -p "/dev/shm/lrcfifo" ];then
                [ "$1" = T ] && line="****** $line ******"
                [ "$1" = E ] && line="错误: $line"
-               echo -ne "$line" > /dev/shm/lrcfifo
+               echo -e "$line" > /dev/shm/lrcfifo
        elif [ "$Dismode" = "notify" -a "`which notify-send 2>/dev/null`" ];then
                [ "$2" = "" ] && return
                [ "$1" = T ] && line="****** $line ******"
@@ -945,7 +945,7 @@
 PlayerStat="Unknow"
 
 GET_STAT |while read line0;do
-       [ "$Dismode" = "fifo" -a -p "/dev/shm/lrcfifo" ] && echo -n "">/dev/shm/lrcfifo #防止读lrcfifo时等待 amoblin 4.14 9:49 #非fifo模式不必要生成该文件 bones7456
+       [ "$Dismode" = "fifo" -a -p "/dev/shm/lrcfifo" ] #&& echo -n "">/dev/shm/lrcfifo #防止读lrcfifo时等待 amoblin 4.14 9:49 #非fifo模式不必要生成该文件 bones7456
        DEBUGECHO "GET_STAT: $line0<<"
        [ "$line0" = "$line1" ] && continue
        line1="$line0"

再试一下,完美显示。

不过如果你重启机器会发现又不灵了,并且运行 lrcdis 也会提示 /dev/shm/lrcfifo 已存在,这是因为 xmobar 的 PipeReader 插件直接使用 ReadWriteMode 打开管道文件,导致如果文件不存在的时候 PipeReader 会自己创建一个普通的文件。

这里我想了半天也没想到一个好的解决方案,最后只好修改 xmobar 的 PipeReader 插件,在打开管道文件之前使用 checkPipeLoop 检查传入的文件是否为管道文件,如果不是则等待,直到是管道文件才进行后面的打开操作。patch 如下(xmobar 0.18 版本已经合入了该补丁,就不需要再设置了):

--- xmobar-0.15-old/src/Plugins/PipeReader.hs   2012-09-17 13:09:07.000000000 +0800
+++ xmobar-0.15/src/Plugins/PipeReader.hs       2012-09-18 13:08:25.000000000 +0800
@@ -16,6 +16,9 @@
 
 import System.IO
 import Plugins
+import System.Posix.Files
+import Control.Concurrent(threadDelay)
+import Control.Exception
 
 data PipeReader = PipeReader String String
     deriving (Read, Show)
@@ -23,6 +25,17 @@
 instance Exec PipeReader where
     alias (PipeReader _ a)    = a
     start (PipeReader p _) cb = do
+        checkPipeLoop p
         h <- openFile p ReadWriteMode
         forever (hGetLineSafe h >>= cb)
         where forever a = a >> forever a
+                
+checkPipeLoop :: FilePath -> IO ()
+checkPipeLoop file = do
+    handle (\(SomeException _) -> waitForPipe) $ do
+    status <- getFileStatus file
+    if isNamedPipe status
+      then return ()
+      else waitForPipe
+    where waitForPipe = threadDelay 100000 >> checkPipeLoop file
+

最后看一下最终的效果,歌词在右上角时间的左边:

使用 xmobar 的 PipeReader 显示歌词

spdy的中文简介可以参看维基百科的条目

如果你的服务器使用的是apache2.2或以上,且开启了mod_ssl的话,就可以通过安装mod-spdy模块来使用spdy。

如果没有开启ssl当前是无法使用spdy的,这是因为,当你在浏览器敲入一个网址的时候,浏览器并不知道服务器是否支持spdy,所以只能使用http协议。当使用ssl后,就可以通过NPN扩展知道上层的应用协议是http还是spdy了。估计要等spdy正式成为http 2.0标准之后才能在非ssl情况下使用吧。

需要注意的是,当我装完后发现,偶尔有些页面打开不完全,日志提示如下

[Wed Aug 01 09:08:25 2012] [error] [client 183.12.64.115] PHP Fatal error: Cannot redeclare __() (previously declared in /var/www/blog/wp-admin/load-styles.php:17) in /var/www/blog/wp-admin/load-scripts.php on line 17, referer: https://zlb.me/wp-admin/update-core.php
[Wed Aug 01 09:08:25 2012] [error] [client 183.12.64.115] PHP Parse error: syntax error, unexpected T_FUNCTION, expecting ')' in Unknown on line 0, referer: https://zlb.me/wp-admin/update-core.php
[Wed Aug 01 09:08:37 2012] [error] [client 183.12.64.115] PHP Fatal error: Cannot redeclare __() (previously declared in /var/www/blog/wp-admin/load-styles.php:17) in /var/www/blog/wp-admin/load-scripts.php on line 17, referer: https://zlb.me/wp-admin/update-core.php
[Wed Aug 01 09:14:27 2012] [error] [client 183.12.64.118] PHP Fatal error: Cannot redeclare __() (previously declared in /var/www/blog/wp-admin/load-styles.php:17) in /var/www/blog/wp-admin/load-scripts.php on line 17, referer: https://zlb.me/wp-admin/update-core.php

最后发现mod-spdy专门有个Using mod_spdy with PHP的页面。原来mod_spdy使用了多线程,而mod_php不是线程安全的,这样就会导致问题。解决方法就是使用mod_fcgi替代mod_php。

如果你是使用最新的Firefox或者Chrome浏览器,在访问我的Blog时,就已经是在使用spdy协议传输数据了,当然你访问的要是https://zlb.me/这个网址。

Chrome用户可以打开chrome://net-internals/#spdy页面来查看spdy的信息,而Firefox用户也可以安装SPDY indicator扩展来查看当前页面是否使用了spdy协议。

更新:在 Ubuntu 14.04 (Apache 2.4) 上安装 mod_spdy 可以参见我的另一篇文章:在 Apache 2.4 中使用 mod_spdy


反正都是重头开始,顺便把域名也换了,现在的Blog的默认地址是 https://zlb.me/,以前注册的域名一直没怎么用。趁这个机会赶紧给用起来。


以前的 Blog 是放在 DirectSpace 的 VPS 上的,过年回来后比较忙,就忘续费了,结果杯具了,VPS 被终止了,数据什么的都没了。虽然 Blog 很少更新了,但是有时候看着自己以前的文章,能够看到自己心情的变化,勾起当时的回忆,这种感觉挺不错的。现在突然几年来的数据都没了,心情那是相当郁闷。

现在只有从头开始来了,新的Blog暂时放到了免费的 Amazon EC2 东京机房,在我这里访问的速度还不错。