Thursday, October 28, 2004

Anti-idle by Screen and Expect

目標

使用 Screen + Expect,取代 Windows 上一般的 Enhanced Telnet Client 如 PacketSiTE 之流,達到在 server 上真正「一直掛站」的效果。

方法

  1. Client 端不限,PuTTY 是不錯的選擇。

  2. Server 端需要 screen、expect,以及可設定 prompt 的 shell,下面只介紹 tcsh 的作法。

  3. 首先,在 screenrc 裡面加上這一行:

    hardstatus alwayslastline '%{.bY}[%H]%{.bR} %?%-LW  %?%{..W}%n%f %t%{.bR}%?%+LW%?%=%{.bY}%0c'

    這 是用來在 terminal 上,使用最底下一行,顯示 screen 的 status line,會顯示的東西,除了 screen 裡各個「window」的 number、flag 及 title等資訊。額外左端加上 server 的 hostname,右端加上 server 的時間。這樣子長的就會有點像 Windows 的工作列,右端擺上 server 時間還有個好處是,由於每分鐘會變動一次,遇到那種 idle 太久會斷線的網路環境裡,從 local 到 server 端的連線就不會斷線,因為每分鐘至少會傳一次資料。

    以下統一一下 terminology 好了:使用 PuTTY 這端叫 local 端,跑 screen + expect 的 server 那端叫 server 端,然後連到的 bbs 站叫 remote 端。

    另 外要注意的是,目前的 window 會用高亮度顯示,上一個 window,在數字右邊會有一個 - 號。背景 window 有「嗶」聲發生,但我們還沒「轉台」過去看的 window,數字旁邊會有一個 ! 號。這個 ! 號,有助於我們掌握是哪一個 window 的 BBS 有訊息傳來。另外,您最好額外在 screenrc 裡設定:

    bell_msg 'Bell in window %n^G'

    這 樣 screen 還會在背景 window 有「嗶」發聲時,顯示短暫兩、三秒的訊息,告知是那一個窗有「嗶」聲。這個設定裡頭的 ^G 表示,然後要真的發出「嗶」聲。因為 screen 預設背景 window 的「嗶」是不發出聲音的,故利用 bell_msg 我們可以迫使其發出聲音來。

    另外要注意的是,我的 screen 版本為:

    SHEEL> screen -v
    Screen version 3.09.11 (FAU) 14-Feb-02
    
    SHEEL> uname -sr
    FreeBSD 4.10-PRERELEASE

    我 在另一台 Linux(RedHat)上設 hardstatus,底下會顯示亂碼一行。該機器的 screen 版本比我稍舊,詳細版本忘記了。目前還不確定是該版本的 screen 有問題,亦或是 screenrc 編寫的有問題(RedHat 預設了一個複雜的 screenrc)。

    由於 hardstatus line 會佔去一行的空間,而一般的 BBS 都至少要是 80 x 24,故原始的 PuTTY 設定便無法容納這額外需要的一行。所以請在 PuTTY 的 Window 設定頁裡,將 Rows 改成 25,以容納這額外多出來的 hardstatus line。

  4. 接著,利用 screen 的 dynamic title 機制,送出 title-escape-sequence 以根據 remote bbs 名稱修改 screen window title。為了要在 remote bbs 斷線之後,能夠把 screen window title 修改回來,所以一併參照 screen 的文件,替 shell 也應用上這個 dynamic title 機制。

    Screen 的 dynamic title 的原理為:當畫面顯示了一個 title-escape-sequence ( k ) 時,就會於這個 sequence 後面,尋找某個指定的字串。從這個字串的後面一個字元開始,到第一個空白字元出現為止,這中間的字串,就是會被設定為 screen window title 的字串。整個動作,會發生在 title-escape-sequence 後的第一個 \n 出現時。而要讓 screen 照上述這麼做,我們必須要指定這一個「某個指定的字串」,以及若該 screen window title 的字串不存在(指定字串之後馬上碰上 \n) 時,所要顯示的字串。設定方法便是,在 screenrc 裡,設定 shelltitle 為 'SEARCH|NAME',其中,SEARCH 就是那個「某個指定的字串」,而 NAME 就是若該字串不存在時,所要顯示的東西。配合 prompt 這個 shell 指令,反映到 screen window title 上來。tcsh + screen 的設定方法如下:

    在 tcshrc 裡設定:

    set prompt = "%{\ek\e\\%}%% "

    我自己的 prompt 會複雜一點,但總之,在最後面加上上面這個字串就對了。然後在 screenrc 裡設定:

    shelltitle '% |TCSH'

    這樣子,,screen 就會抓取每個 shell 指令,反映到 screen window title來。試打個 top 指令,就可以從剛剛在第 3 步裡所設定的 hardstatus line 上面,體驗到 dynamic title 的效果了。

  5. 接著,我們要編寫 except 檔,來達到「上 BBS 不會斷線」,以及「依據所上的 BBS 站,修改 screen window title」的功能。Expect 程式如下:

    #!/usr/local/bin/expect -f
    #
    # Name: bbs_noidle.expect
    # Description: expect script to run a shell which avoids being
    #              "autologged out"  by sending a  every
    #              specified timeout limit if no other I/O has occurred.
    # Author: Don Libes, December 30, 1991
    # Modified by: Clive Lin  to fit tw BBS.
    #              bbs_noidle.expect telnet bbs.some.where
    #              And send  only
    # Modified by: Jeff Hung  @ 20040829 to add
    #              these features:
    #              - Use zh-telnet to connect to BBS.
    #              - Able to use abbrevation instead of complete hostname.
    #              - Use screen's 'title-escape-sequence' to dynamically
    #                set window title.
    #
    
    set timeout 180
    set anti_idle "\177";
    
    log_user 0
    system stty -echo raw
    
    if {[llength $argv] <= 0} {
       send "Usage: bbs_noidle.expect ( | )\n";
       exit 0;
    }
    
    set bbs_abbr "$argv";
    set bbs_host "$argv";
    switch $bbs_abbr {
       lady     { set bbs_host "bbs.lady.idv.tw"      }
       tkucs    { set bbs_host "bbs.cs.tku.edu.tw"    }
       nctuiim  { set bbs_host "bbs.iim.nctu.edu.tw"  }
       tku      { set bbs_host "bbs.tku.edu.tw"       }
       ptt      { set bbs_host "ptt.cc"               }
       zoo      { set bbs_host "zoo.twbbs.org"        }
    }
    
    # Cause screen to print where we're connecting to.
    send "We're connecting to \033k\033\\% $bbs_abbr %\n\r";
    
    eval spawn -noecho /usr/local/bin/zh-telnet $bbs_host
    
    expect {
       timeout { send $anti_idle; exp_continue }
       -re .+  { send_user -raw -- $expect_out(buffer); exp_continue }
       -i $user_spawn_id -re .+ { send -- $expect_out(buffer); exp_continue }
    }

    傳 給這個 expect 程式的命令列參數,可以是在程式中的 switch {} 裡頭所列的 host name abbrevation,或是正確完整可傳給 telnet 當作命令列參數的的 host name。修個這裡的列表,您就可以編寫您自己的 bbs list。

    其中的秘訣在於這一行:

    send "We're connecting to \033k\033\\% $bbs_abbr %\n\r";

    在 連線前,先送出 title-escape-sequence 修改 screen window title,達到顯示目前所上的 BBS 站的效果。在程式最開頭的 set timeout 可以設定「防呆」的時間為多少秒。 而 set anti_idle 則可以設定,防呆時要送出那個字串。一般來說,送出 \177,也就是 DELETE 字元,差不多就很夠用了。

展望

  1. 將 bbs list 改用檔案設定,而非寫死在 expect 程式裡。

  2. 當真的 idle 太久時,自動修改 BBS 暱稱為「我出門了~」。

補充

補充一下,在 .cshrc 裡頭加上:

if ( ! $?WINDOW ) then
   alias screen 'env LC_CTYPE=en_US.ISO8859-1 screen -x -RR'
else
   alias screen 'env LC_CTYPE=en_US.ISO8859-1 screen'
endif

然後在 .login 裡加上:

if ( ! $?WINDOW ) then
   screen
endif

就可以在 login 後自動進入原先的 screen,進入仍在掛站中的 BBS。當然,這樣子離線就不能真的打 exit 離線,要用 screen 的 detach 離線法:C-a D D。

若 是有要在 shell 裡工作的話,請建立一個同 UID 的帳號,好比叫 jeffbbs,在 PuTTY 裡建立不同名稱的連線,預設使用 jeffbbs 登入,這樣子的話,就可以在用 jeffbbs 登入時,享用 Screen + Expect 掛站大法。而在使用 jeffhung 登入時,則是進入正常的 shell。這是因為 screen 是用 login id 來判定 session 歸屬的。


Comments: Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Comments [Atom]