Yakuake + dtach vs Screen + urxvt

June 10, 2007 – 10:45 pm

Screen 无疑是每一个经常使用终端的用户的必备工具。它拥有许多非常实用的功能:

  • 管理会话,可以 detach 一个会话,让它在后台运行,并在其他时间/地点 attach 原来的会话。这对于经常远程登录的用户来说非常方便。登录以后,运行一些程序, Screen 会保证在你注销以后程序仍然能继续运行,并且下次登录进来可以 attach 到原来的终端会话上 (也许那里正有一个运行了几个月的 Emacs 在等着你) 。
  • 多标签功能。 Screen 可以为一个用户管理多个会话,并且每个会话里面都可以有许多“子会话”,就像许多 Tab 一样,而 Screen 强大的可定制功能让它可以真正显示一个“标签栏”,这正弥补了许多终端模拟器没有多标签功能的缺点。而且这些标签都是在 Screen 会话的管理下的,在另外的地方 attach 到这个会话上会得到同样的一系列标签。并且可以通过 Shell 里面的 Escape Sequence 来动态更改标签的标题。非常方便。
  • 屏幕分割、历史存储、搜索以及复制粘贴。这些功能在一个 X 下的终端模拟器里面可以很轻松地完成,不过如果通过远程登录或者在一个纯字符界面下, Screen 就帮了大忙了。
  • 快捷键、命令以及其他功能。

这里是一个典型的 Screen 会话的截图。这是我很喜欢的 rxvt-unicode 和 Screen 的搭配。

Screen + urxvt

rxvt-unicode (urxvt) 本身是一个非常不错的程序,同时,它还有一个不错的功能,只要开启 secondaryScroll

URxvt.secondaryScroll: true

Screen 的历史就可以进入 urxvt 的历史里,换句话说,既可以使用 Screen 的回滚功能来查看历史,也可以使用 urxvt 提供的可以用鼠标拖拽的滚动条来查看历史。不过这样也有一些不方便的地方,因为 Screen 里面是可以有多个 tab 的,在 Screen 里面切换 tab 对于 urxvt 来说是不可见的 (听说 eterm 能和 Screen 交互,把 screen 的 tab 用 eterm 自己的标签栏表现出来,不过我没有尝试过) ,所以多个 tab 的历史会混杂进同一个 urxvt 的历史。这也是我尝试 yakuake + dtach 来代替 Screen 的一个直接原因。

Yakuake 是 KDE 下的一个基于 Konsole 的非常 Cool 的一个终端程序。它平时隐藏起来,当你按下快捷键 (默认是 F12) 时从屏幕顶端滑下。非常方便,有种呼之即来,挥之即去的感觉。当然这并不是我选择 Yakuake 的主要原因,因为我使用 jump-or-exec 可以在 urxvt 上实现类似的功能 (事实上我为很多程序如 Emacs 、 QTerm 和 Firefox 等都配备了 jump-or-exec 的功能,因为我只需要它们的一个实例就够了) ,虽然不及 Yakuake 那么 Cool 。

Yakuake 基于 Konsole ,也就有了 Konsole 的许多优点:多标签、动态更改编码、字体以及颜色配置等。另外,新版本的 Yakuake 还支持屏幕分割功能。历史回滚和复制粘贴这种基本功能自然也不用说了。这些都实现了 Screen 提供的那些功能,唯一的例外就是会话管理。本来作为一个笔记本用户,很少从别的地方远程登录到自己的笔记本上,而且我也绝大部分时间都使用 X 界面,会话管理并不是必要。不过有时候会做一些奇怪的事情有可能让 X 崩溃掉,如果没有会话管理的话,X 崩溃掉了终端里面的程序自然要跟着退出了。正好有一个叫做 dtach 的程序,专门实现了 Screen 的 detach 和 attach 的功能,而抛弃了 Screen 的其他的“累赘”的功能。

现在看来 Yakuake 和 dtach 搭配起来好像是完美的 Screen + urxvt 的替代品一样。dtach 并不像 Screen 那样管理会话和历史,所以所有终端历史进入 Yakuake 的历史里面,使用 Yakuake 的滚动条来回滚查看。不过也由于 dtach 不管理会话,不能像 Screen 那样维护一个多标签。本来可以让 Yakuake 在每次新开 tab 的时候执行 dtach 新启动一个会话,但是 attach 原有的会话有些麻烦,始终不如 Screen 那样方便。后来我仔细想了想,其实执行重要的任务 (就是 X 崩溃了也要继续运行的) 的话,三个 tab 就够了。

于是我只要在启动的时候加载三个由 dtach 管理的会话即可,如果 shell 不够用了,我可以开更多的 tab ,它们不由 dtach 管理,我只要记住重要的任务在 dtach 会话下运行即可。事实上大部分时候包括重要的和不重要的工作,三个 shell 都够用了。

我使用这样一个脚本来启动 Yakuake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/sh
 
if [ -z "$1" ]
then
    DIR=$PWD
else
    DIR=$1
fi
 
if dcop | grep -q yakuake
then                            # yakuake running
    dcop yakuake DCOPInterface slotAddSession
    dcop yakuake DCOPInterface slotRunCommandInSession "cd $DIR"
else
    yakuake
    # load my dtach sessions
    while ! dcop | grep -q yakuake
    do
        sleep 0.5
    done
    dcop yakuake DCOPInterface slotRunCommandInSession \
        "export DTACH=dtach && dtach -A ~/.dtach/term-0 /bin/zsh"
    sleep 0.5
    dcop yakuake DCOPInterface slotSetSessionTitleText "[kid]"
    dcop yakuake DCOPInterface slotAddSession
    dcop yakuake DCOPInterface slotRunCommandInSession \
        "export DTACH=dtach && dtach -A ~/.dtach/term-1 /bin/zsh"
    sleep 0.5
    dcop yakuake DCOPInterface slotSetSessionTitleText "[kid]"
    dcop yakuake DCOPInterface slotAddSession
    dcop yakuake DCOPInterface slotRunCommandInSession \
        "export DTACH=dtach && dtach -A ~/.dtach/term-2 /bin/zsh"
    dcop yakuake DCOPInterface slotSetSessionTitleText "[kid]"
    dcop yakuake DCOPInterface slotSelectSession 0
fi
 
# show yakuake
if [ "false" = `dcop yakuake 'yakuake-mainwindow#1' shown` ]
then
    dcop yakuake DCOPInterface slotToggleState
fi

如果 Yakuake 已经在运行了,则打开一个 tab , cd 到指定目录或当前目录 (这非常有用,用这个脚本替换掉 konsole 的话,就可以在 konqueror 文件管理器里面按 F4 来快速在 Yakuake 里面开一个新 tab 并转到该目录了) ,如果没有运行则启动它,并加载三个默认的 dtach 会话。其中与 Yakuake 的交互通过 dcop 来进行。KDE 提供的 dcop 让我们可以很轻松地在脚本里面调用 KDE 程序提供的复制功能。我把这个脚本链接到 ~/.kde/Autostart/ 下面,在 KDE 启动的时候自动启动它。

Konsole 同样允许通过 Shell 里面的 escape sequence 来改变标题栏和标签栏。Yakuake 继承了更改标签栏的功能,但是由于 Konsole 内部实现的一些原因 (Konsole 正要经历巨大修整,参见 Konsole 大整修) Yakuake 不能使用 Konsole 的那种方法来设置标签标题。不过可以 hack 一下在更改标题的同时更改标签栏标题,下面是针对 Yakuake 2.8 的 patch :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
diff -ur src/main_window.cpp src.orig/main_window.cpp
--- src/main_window.cpp 2007-06-01 00:29:06.000000000 +0800
+++ src.orig/main_window.cpp  2007-06-01 00:10:27.000000000 +0800
@@ -342,8 +342,6 @@
     connect(session, SIGNAL(destroyed(int)), this, SLOT(slotSessionDestroyed(int)));
     connect(session, SIGNAL(titleChanged(const QString&)), this, SLOT(slotUpdateTitle(const QString&)));
 
-    connect(session, SIGNAL(titleChanged(int)), this, SLOT(slotUpdateTitle(int)));
-
     widgets_stack->addWidget(session->widget());
     sessions_stack.insert(session->id(), session);
 
@@ -783,10 +781,6 @@
     title_bar->setTitleText(title);
 }
 
-void MainWindow::slotUpdateTitle(int id)
-{
-    slotRenameSession(id, sessions_stack[id]->title());
-}
 
 void MainWindow::slotIncreaseSizeW()
 {
diff -ur src/main_window.h src.orig/main_window.h
--- src/main_window.h 2007-06-01 00:12:58.000000000 +0800
+++ src.orig/main_window.h  2007-05-06 08:32:58.000000000 +0800
@@ -217,8 +217,7 @@
         void slotUpdateSize();
         void slotUpdateSize(int new_width, int new_height, int new_location);
         void slotUpdateTitle(const QString& title);
-        void slotUpdateTitle(int id);
-
+
         void slotIncreaseHeight();
         void slotDecreaseHeight();
         void slotSessionDestroyed(int id = -1);
Only in src: Makefile.am
Only in src: Makefile.in
diff -ur src/session.cpp src.orig/session.cpp
--- src/session.cpp 2007-06-01 00:13:48.000000000 +0800
+++ src.orig/session.cpp  2007-05-06 08:32:58.000000000 +0800
@@ -395,7 +395,6 @@
     {
         session_title = title;
         emit titleChanged( session_title);
-        emit titleChanged( session_id);
     }
 }
 
diff -ur src/session.h src.orig/session.h
--- src/session.h 2007-06-01 00:14:11.000000000 +0800
+++ src.orig/session.h  2007-05-06 08:32:57.000000000 +0800
@@ -72,8 +72,8 @@
     signals:
         void destroyed(int id);
         void titleChanged(const QString&);
-        void titleChanged(int);
-
+
+
     private:
         void createInitialSplits(SessionType);
         void split(QWidget* active_terminal, Orientation o);

之后在 Shell 的提示符里面做点手脚就自动更新标签栏了。我用的是 Zsh ,下面的配置可以在标签栏标题里面显示当前目录。如果是在一个 dtach 管理的会话里,则用方括号“[]”括起来 (在前面的启动 Yakuake 的脚本里面设置了 DTACH 环境变量用于识别是否在 dtach 会话里) :

if [[ -z "$DTACH" ]];
then
    P_TITLE=$'%{\e]0;%1/\a%}'
else
    P_TITLE=$'%{\e]0;[%1/]\a%}'
fi

有 Zsh 提供的功能,可以在执行某条命令之前先做一些动作,比如更新标签栏以显示出当前正在执行的命令:

CMD=${1[(wr)^(*=*|sudo|-*)]}
if [[ -z "$DTACH" ]]
then
    echo -n "\e]0;$CMD\a"
else
    echo -n "\e]0;[$CMD]\a"
fi

下面是我的 Zsh 的提示符的完整配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/bin/zsh
autoload colors zsh/terminfo
if [[ "$terminfo[colors]" -ge 8 ]]; then
    colors
fi
 
if [[ "$TERM" = "dumb" ]]
then
    prompt='%(?..(%?%))%n@%1/ %(!.#.$) '
else
    local P_GREEN="%{$terminfo[bold]${fg[green]}%}"
    local P_CLEAR="%{$terminfo[sgr0]%}"
    local P_ERROR="%{$terminfo[bold]${fg[red]}%}%(?..(%?%))$P_CLEAR"
    local P_USER="%(!.${bg[green]}${fg[black]}ROOT.%n)$P_CLEAR@"
    local P_DIR="%1/"
    local P_PRO=" %(!.#.$) "
    local P_TITLE
    if [[ "$TERM" = "screen" ]]; then
        P_TITLE=$'%{\ek%1/\e\\%}'
    elif [[ "$TERM" = "xterm" ]]; then
        if [[ -z "$DTACH" ]];
        then
            P_TITLE=$'%{\e]0;%1/\a%}'
        else
            P_TITLE=$'%{\e]0;[%1/]\a%}'
        fi
    else
        P_TITLE=""
    fi
    prompt="$P_ERROR$P_USER$P_GREEN$P_DIR$P_CLEAR$P_PRO$P_TITLE"
fi
preexec () {
    local CMD=${1[(wr)^(*=*|sudo|-*)]}
    if [[ "$TERM" == "screen" ]]; then
        echo -n "\ek$CMD\e\\"
    elif [[ "$TERM" == "xterm" ]]; then
        if [[ -z "$DTACH" ]]
        then
            echo -n "\e]0;$CMD\a"
        else
            echo -n "\e]0;[$CMD]\a"
        fi
    fi
}

如下是一个效果截图。

Yakuake + dtach

我已经用了大约一个周了,感觉非常好,几乎从原来的 Screen + urxvt 平滑过渡过来。不过我也并不能说哪种方案更好。如果你绝大部分时间都是在 X 下渡过 (特别是,如果你使用 KDE) ,而且不经常远程登录的话,推荐使用 Yakuake + dtach 获得更好的用户体验。否则,还是推荐使用 Screen ,因为它具有更强大的会话管理能力。

  1. 7 Responses to “Yakuake + dtach vs Screen + urxvt”

  2. 好东东,一直在用Yakuake,最不爽的就是他不保存会话

    By Jarod on Feb 1, 2008

  3. 不知道你还关注这个页面不,这个脚本用不了了,在google搜了一下说kde4已经抛弃了dcop改用了dbus,小弟技术实在是差,不知道你还维护这个页面否

    By cbkid on Oct 17, 2008

  4. @cbkid,
    已经没有维护了,不好意思啊! :-/

    By pluskid on Oct 17, 2008

  5. 请问, “export DTACH=dtach && dtach -A ~/.dtach/term-0 /bin/zsh”的作用是?

    By lenciel on Oct 28, 2008

  6. @lenciel,
    用 dtach 来运行,这样终端推出之后原来的 shell 还能在后台运行,而且下次打开终端的时候能自动 attach 过来。

    By pluskid on Oct 28, 2008

  7. 谢谢回复,我的意思是,如果我不需要像你一样改标签的名字,export DTACH=dtach这一部分是不是可以不要呢?

    By lenciel on Oct 29, 2008

  8. @lenciel,
    恩,那样的话应该可以不要了。 :)

    By pluskid on Oct 29, 2008

Post a Comment