2011年9月1日星期四

Konsole 里 Session Profile 命令的解析过程

Creative Commons License
本作品采用知识共享署名-非商业性使用 2.5 中国大陆许可协议进行许可。

如果没有强大的动力,我基本是不会主动去读 C++ 的源代码的。事情的起因其实很简单, 就是 Jesse#beihang-osc@freenode.net 上吼,想在 gnome-terminal 里不用 gconf 实现 http://vim.wikia.com/wiki/Change_cursor_shape_in_different_modes 里的效果。他想 看看 Konsole 是怎么实现的。作为伪 KDE 控的我当然不能放过这个机会啦……

第一步是从 anongit.kde.org 上 clone konsole 的源代码。无他,主要是为了打 patch 方便和那个无比好用的 git grep ;P 写这篇文章的时候 git HEAD 是 d6ca64fe4d。

然后这逛那逛的也一直没有头绪。忽然想,那个指令不是 "\<Esc>]50;CursorShape=1\x7""\<Esc>]50;CursorShape=0\x7" 么,干脆 git grep -n ']50' 试试。嘿,还真找到了:

src/Part.cpp:276: buffer.append("\033]50;").append(text.toUtf8()).append('\a');
src/konsoleprofile:3:/bin/echo -e "\033]50;$1\a"
lines 1-2/2 (END)

火速去 src/Part.cpp 第 276 行看:

void Part::changeSessionSettings(const QString& text)
    {
        // send a profile change command, the escape code format
        // is the same as the normal X-Term commands used to change the window title or icon,
        // but with a magic value of '50' for the parameter which specifies what to change
        Q_ASSERT( activeSession() );
        QByteArray buffer;
        buffer.append("\033]50;").append(text.toUtf8()).append('\a');

        activeSession()->emulation()->receiveData(buffer.constData(),buffer.length());
    }

注释说的挺清楚了,而且 033 就是 ASCII 的 ESC,a 是 x7。一切都对应上了。最后它 把指令送给了 activeSession()->emulation()->receiveData 。省略中间痛苦的寻找 过程直接说了,konsole 里每一个窗口/Tab都会有一个 Session, activeSession() 获取的是当前用户正在使用的 Session。每个 Session 里都会有一个 emulation 来模拟 terminal,处理用户的输入输出(src/Session.h 的 136 行)。 Session->emulation() 获取的就是它。具体的东西是 src/Session.cpp 129 行的 _emulation = new Vt102Emulation(); 嗯,跳去 src/Vt102Emulation.cpp 找 receiveData ,木有找 到…… .h 里也没有…… 想起来有可能在鸡肋里, Vt102Emulation 是继承 Emulation 的,于是再跳到 src/Emulation.cpp 看 receiveData

/*
       We are doing code conversion from locale to unicode first.
    TODO: Character composition from the old code.  See #96536
    */

    void Emulation::receiveData(const char* text, int length)
    {
        emit stateSet(NOTIFYACTIVITY);

        bufferedUpdate();

        QString unicodeText = _decoder->toUnicode(text,length);

        //send characters to terminal emulator
        for (int i=0;i<unicodeText.length();i++)
            receiveChar(unicodeText[i].unicode());

        //look for z-modem indicator
        //-- someone who understands more about z-modems that I do may be able to move
        //this check into the above for loop?
        for (int i=0;i<length;i++)
        {
            if (text[i] == '\030')
            {
                if ((length-i-1 > 3) && (strncmp(text+i+1, "B00", 3) == 0))
                    emit zmodemDetected();
            }
        }
    }

略掉编码转换和 z-modem 的过程,这个 receiveData 就是把东西送给了 receiveChar 。再找 receiveChar

// process application unicode input to terminal
    // this is a trivial scanner
    void Emulation::receiveChar(int c)
    {
        c &= 0xff;
        switch (c)
        {
            case '\b'      : _currentScreen->backspace();                 break;
            case '\t'      : _currentScreen->tab();                       break;
            case '\n'      : _currentScreen->newLine();                   break;
            case '\r'      : _currentScreen->toStartOfLine();             break;
            case 0x07      : emit stateSet(NOTIFYBELL);                   break;
            default        : _currentScreen->displayCharacter(c);         break;
        };
    }

不会这么简单吧!又忽然想起来, Emulation::receiveData 可能会调用 Vt102Emulation::receiveChar 的吧…… 在 src/Emulation.h 的 427 行,这货果然是 virtual 的。于是再返回 src/Vt102Emulation.cpp 找 receiveChar 。这回终于 找到处理字符序列的地方了。不过因为整个函数有101行,还要加上前面18行注释和15行宏 ,就不贴在这里了。那个函数主要是把用户输入 tokenize ,并且对 token 进行处理。这 整个过程我还不是完全理解,但是大概的内容可以猜的出来。对于本文起作用的主要是第 316 行

if (Xte         ) { processWindowAttributeChange(); resetTokenizer(); return; }

Xte 是个判断 \033] 的宏。(好吧,它其实只判断了 token 的位置和 ']') processWindowAttributeChange 就在 receiveChar 的下面

void Vt102Emulation::processWindowAttributeChange()
    {
      // Describes the window or terminal session attribute to change
      // See Session::UserTitleChange for possible values
      int attributeToChange = 0;
      int i;
      for (i = 2; i < tokenBufferPos     && 
                  tokenBuffer[i] >= '0'  && 
                  tokenBuffer[i] <= '9'; i++)
      {
        attributeToChange = 10 * attributeToChange + (tokenBuffer[i]-'0');
      }

      if (tokenBuffer[i] != ';') 
      { 
        reportDecodingError(); 
        return; 
      }
      
      QString newValue;
      newValue.reserve(tokenBufferPos-i-2);
      for (int j = 0; j < tokenBufferPos-i-2; j++)
        newValue[j] = tokenBuffer[i+1+j];
     
      _pendingTitleUpdates[attributeToChange] = newValue;
      _titleUpdateTimer->start(20);
    }

前半部分是提取 \033';' 中间的数字,然后把剩下的字串放到 _pendingTitleUpdates 里给别人处理。这里作者启动了一个 20ms 的计时器,计时器到时 间之后才会更新。这可以压缩更新的次数,避免频繁更新吧。计时器的 callback 就在下 面

void Vt102Emulation::updateTitle()
    {
        QListIterator<int> iter( _pendingTitleUpdates.keys() );
        while (iter.hasNext()) {
            int arg = iter.next();
            emit titleChanged( arg , _pendingTitleUpdates[arg] );    
        }
        _pendingTitleUpdates.clear();    
    }

简单的函数,它又发出了 titleChanged 这个信号。这个信号是在哪处理的呢?(中间 省略N多 git grep 之类的过程)是在 src/Session.cpp 的 Session::setUserTitle

void Session::setUserTitle( int what, const QString &caption )
    {
    ....
        if (what == ProfileChange) 
        {
            emit profileChangeCommandReceived(caption);
            return;
        }
    ....
    }

这个 ProfileChange 就等于我们所要的 50(src/Session.h, 341 行) ……再追踪 profileChangeCommandReceived 这个信号。(别急,快完啦)处理它的是 src/SessionManager.cpp 里的 SessionManager::sessionProfileCommandReceived

void SessionManager::sessionProfileCommandReceived(const QString& text)
    {
        // FIXME: This is inefficient, it creates a new profile instance for
        // each set of changes applied.  Instead a new profile should be created
        // only the first time changes are applied to a session

        Session* session = qobject_cast<Session*>(sender());
        Q_ASSERT( session );

        ProfileCommandParser parser;
        QHash<Profile::Property,QVariant> changes = parser.parse(text);

        Profile::Ptr newProfile = Profile::Ptr(new Profile(_sessionProfiles[session]));
        
        QHashIterator<Profile::Property,QVariant> iter(changes);
        while ( iter.hasNext() )
        {
            iter.next();
            newProfile->setProperty(iter.key(),iter.value());
        } 

        _sessionProfiles[session] = newProfile;
        applyProfile(newProfile,true);
        emit sessionUpdated(session);
    }

还是一个挂着 FIXME 的函数呢…… 不过逻辑还是比较简单的,基本上就是把当前的Profile 作为父 Profile 新建一个 Profile。然后根据命令的内容修改 Profile 的属性。也就是说 ,理论上讲,只要是 Profile 里可以改的,就可以通过 \<ESC>50;x1=y1;x2=y2\x7 来 修改。后来我又把 vim 里的 t_{S,E}I 修改成

if $TERM =~ 'xterm'
        let &t_SI = "\<Esc>]50;CursorShape=1;BlinkingCursorEnabled=true\x7"
        let &t_EI = "\<Esc>]50;CursorShape=0;BlinkingCursorEnabled=false\x7"
endif

然后在插入模式下,光标果然编程一闪一闪的竖线了。哈哈。不过需要小注意的是,用这个 方式修改的 Profile 是临时的,不会保存,新建的标签也不会继承这个 Profile。

现在就拨云见日,回顾一下整个调用过程吧

Emulation::receiveData
    ||
    \/
Vt102Emulation::receiveChar
    || tokenize/process token
    \/
Vt102Emulation::processWindowAttributeChange
    || 提取 \<ESC>] 后面的 code 和 cmd
    \/ 20ms 延迟,聚集变更
Vt102Emulation::updateTitle
    || emit titleChanged(code, cmd)
    \/
Session::setUserTitle(int, const QString &)
    || emit profileChangeCommandReceived(cmd)
    \/
SessionManager::sessionProfileCommandReceived(const QString)
{
    ...
    Profile::Ptr newProfile = Profile::Ptr(new Profile(_sessionProfiles[session]));
    ...
    newProfile->setProperty
    ...
    _sessionProfiles[session] = newProfile;
    applyProfile(newProfile,true);
    emit sessionUpdated(session);
}

回头来看,一步一步的到还挺清晰的。

多谢各位能够读到最后。作为奖励,贴一个解决上面 FIXME 的补丁吧,哈哈

commit 5fb452e51ac1a9d18952fdd26f8bfa55438aedf3
Author: Grissiom <chaos.proton@gmail.com>
Date:   Thu Sep 1 01:17:24 2011 +0800

    use a static _sessionRuntimeProfiles to store runtime profiles

diff --git a/src/SessionManager.cpp b/src/SessionManager.cpp
index 028b76f..697589c 100644
--- a/src/SessionManager.cpp
+++ b/src/SessionManager.cpp
@@ -758,9 +758,7 @@ Profile::Ptr SessionManager::findByShortcut(const QKeySequence& shortcut)

 void SessionManager::sessionProfileCommandReceived(const QString& text)
 {
-    // FIXME: This is inefficient, it creates a new profile instance for
-    // each set of changes applied.  Instead a new profile should be created
-    // only the first time changes are applied to a session
+    static QHash<Session*,Profile::Ptr> _sessionRuntimeProfiles;

     Session* session = qobject_cast<Session*>(sender());
     Q_ASSERT( session );
@@ -768,14 +766,23 @@ void SessionManager::sessionProfileCommandReceived(const QString& text)
     ProfileCommandParser parser;
     QHash<Profile::Property,QVariant> changes = parser.parse(text);

-    Profile::Ptr newProfile = Profile::Ptr(new Profile(_sessionProfiles[session]));
-
+    Profile::Ptr newProfile;
+    if (!_sessionRuntimeProfiles.contains(session))
+    {
+        newProfile = new Profile(_sessionProfiles[session]);
+        _sessionRuntimeProfiles.insert(session,newProfile);
+    }
+    else
+    {
+        newProfile = _sessionRuntimeProfiles[session];
+    }
+
     QHashIterator<Profile::Property,QVariant> iter(changes);
     while ( iter.hasNext() )
     {
         iter.next();
         newProfile->setProperty(iter.key(),iter.value());
-    }
+    }

     _sessionProfiles[session] = newProfile;
     applyProfile(newProfile,true);

C++ 代码看的比较少,Konsoel 的代码也是刚看。有什么不对的地方还请指教~;P

2010年12月13日星期一

两件难事

Creative Commons License
本作品采用知识共享署名-非商业性使用 2.5 中国大陆许可协议




1,idea is cheap, show me the logic

这个小标题是最近发过的一个状态。 [1] 它是说在接收观点的时候,对观点的论证比观点 本身重要。(所有意义以中文为准,英文有所偏差,见谅……)而且竟然得到了片聪大牛的回复 ,虽然我用耍赖皮的方法回复了聪……其实我那个状态本身就没有show the logic ;-P


发这个状态的导火索是一篇好友的日志 [2] 。那篇日志的观点不去评价,但是我看的时候 实在没看明白原文两个部分之间的推理关系,直到后来好友给我用四个回复给我解释了一遍 我才明白。这个时候我突然发现,观点并不那么重要,重要的是支撑观点的论据以及从论据 推导观点的逻辑。


现在是信息时代了,每天上网都会接触各式各样的观点,想法。最近看文章有个习惯,就是 总要看看作者是用什么来论述他的观点的,怎么样用论据来论述的。(当然受限于本人低下 的语文水平,这个目标常常达到不了……)遇到那种只讲观点不讲理由的文章,一概略过。比 如以前无聊的时候看过咸郎平的一本书,书里充斥着“我预料它是这样,几年之后果然就验 证了。”、“我说XX是那样,它就是那样”的话语,然而知识性的东西很少。这就让人读着很 不爽。 [3] 就好像如果我说“买黄金吧,保赚!”,但是任何一个理性的人都不会单纯因为 我这一句话而投资黄金一样。一个只有观点而没有靠谱论据的文章只能证明作者当时持有那 种观点,其他的什么都说明不了,甚至没法证明作者不是忽悠人的。


2,idea is cheap, show me the implementation

这个小标题是说,实现想法比想法本身重要的多。这个是晚上和嘉哥聊天的时候忽然想到的 。


可能有的人要问,为什么想法不重要呢?现在不是求创新嘛?创新就是要有新的 idea,那 么idea怎么就不重要了呢?我觉得,一个人从早晨起来到晚上睡下,睁眼看着这花花绿绿的 世界,难免会有 idea 出来。一个人要是说他什么都没想过,从来都没有过灵感,我觉得真 不可能。但是,生活为什么还是这么平淡呢?很大一部分是因为我们没有去实现自己的想法 。大的例子就不举了,一个小例子,比如,“去K歌”这个想法很多人肯定都有过。那么这个 想法和定地方,定时间再动员大家一起去这个过程比起来哪个更难呢?显然是后者。一个没 有被实现的想法就好像一个被父母抛弃到戈壁的婴儿一样,总是会夭折的。当然一个人的想 法肯定会有很多很多,不可能每个都实现。不过或许也可以想想,不知不觉中有多少有用的 想法就这样被抛弃了呢?……


3,cheapie is cheap, show me the valuable

其实上面两条说得都是废话——接收观点的时候观点不如论证重要,想出了观点(想法)之后观 点不如实现重要——不管观点是别人给你的还是自己“凭空”想出来的,既然已经“有”了,就自 然没有那些还没有(或者不是那么明显)的东西重要啊…… 日志写到这就有一种要死的感觉, 费劲吧啦的原来说的都是废话……


忽然想起笑来老师曾经推荐的李宗盛的歌《你像个孩子似的》了 [4] ,以此为结尾吧:


……
工作是容易的 赚钱是困难的
恋爱是容易的 成家是困难的
相爱是容易的 相处是困难的
决定是容易的 可是等待是困难的
……

[1]这个句型只是从 Linus 早在2000年就说过的话借过来的: http://lkml.org/lkml/2000/8/25/132
[2]http://blog.renren.com/blog/229147260/504805782?fromfriendblog
[3]后来有一次无聊的时候拿了他的另一本书看,貌似好些了……
[4]http://www.lixiaolai.com/index.php/archives/1525.html

P.S. 正如每个定理都有它的局限性一样,这里说的两个观点也有自己的局限性。不排除那 些极其牛的,极其天才的,神来之笔似的观点因为难以获得而显得珍贵。

2010年6月4日星期五

第一次面试

Creative Commons License
本作品采用知识共享署名-非商业性使用 2.5 中国大陆许可协议进行许可。




  1. 平时对细节的注意还是不够。很多细节的问题都没有答好。会用未必代表懂啊!有的问 题是看过,但是感觉太琐碎,所以没用心记,结果面试杯具了。勿以事小而不学啊……
  2. 小概念区分得还是不清楚。尤其是答 sed 的时候把通配符 * 和正则表达式里的 * 搞混了,场面极其狼狈…… 不过最后的那个 char 符号问题答得还是比较好的 。其实那个问题我也只是平时一不小心才注意到的……
  3. 介绍自己的项目的时候还是急于显示自己的东西了,以至于一开始把面试人说得云里雾 里…… ;( 下回还是要慢慢来……

2010年4月30日星期五

北航毕设论文 latex 模板

Creative Commons License
本作品采用知识共享署名-非商业性使用 2.5 中国大陆许可协议进行许可。

原来的源码是在北航的 ftp 上找到的,从源代码里看貌似是一个叫 WangNan 的大牛在 2006 年 3 月份写的。由于对于中文来说用 XeLateX 比较方便,而原来的模板又没有针对它的选项,所以我对原模板略作修改(改动的地方占了不到原文件的 1%),使他能更好的支持 xelatex。源码包里有一个小例子。在 texlive-20091107(under linux) 下可以编译通过。不过需要略微注意的还是字体,adobefonts 选项会选择 adobe 的那一套中文字,winfonts 会选择 Sim 那一套。linux 下面安装很简单,找到字体文件仍到 ~/.fonts 里,应该就可以了;win 下的应该也类似。


已知问题:

  1. 原模板里有一个 \CJKtilde 命令,但是他在 xeCJK 里已经没有了。因为没有找到对应的命令,所以就简单的把它注释掉了。
  2. 原模板里的 BUAAbibsty.bst 貌似有问题,在我这里一直编译不过…… 我又不知道怎么样编辑 bst 文件,所以也跳过它了。
如果有大牛能解决以上问题的,欢迎联系我。


源码包在这里。如果您在下载过程中遭遇任何问题,那不是我们的错。您遭遇了伟大的“防火墙”。您可以先爆句粗口,再平静一下心情,看看别的“健康的”网站(比如人民网之流)。过几分钟之后把上面连接里的 http 该成 https,再试试。实在不行,可以电邮我。


感谢国家。感谢原作者,模板版权归原作者所有。感谢北航开源软件俱乐部,想了半天,还是觉得把文件放到这里来共享比较合适。

2010年2月10日星期三

开了一个技术博客

在这里: http://blog.csdn.net/grissiom (好不容易在今天完成了第一篇文章……)以后这里就放一些关于生活啊之类的碎碎念吧~那里会放一些编程啊,硬件啊之类的东西。我尽量会不转帖的。有了分享,还用转贴么?把类别分开也能方便大家看。(问题是,看的人有几个呢?嘿嘿……)

最后,无论在哪里,都欢迎大家拍砖,人气低的时候欢迎灌水~;)

2010年1月30日星期六

又是一年考期时

Creative Commons License
本作品采用知识共享署名-非商业性使用 2.5 中国大陆许可协议进行许可。

=========================================

其实这是很久以前写的东西。久到大三上的那个考期,觉得郁闷,于是就写了。但是以后的考期就还是那么过的,没有什么变化…… 以后大概也就不会有考期这个东西了,把它贴出来作为纪念吧……

=========================================


又是一年考期时


其实只是上了大学才知道“考期”这个名词,其实只是过完了大一上才知道大学里原来还有 一个“考期”的时间专门留给复习 —— 准备最后的期末考试。


或许考试这个“龙门”就是留给有准备的人跳的。不过大学以前,印象里没有留过时间专门 复习期末考试的 —— 高考除外,其性质与期末考试不同,暂不属于讨论范畴。基本上是讲 完考,考完撤,上午一门下午一门,连考三天还不费劲。


上了大学,风水变了。有孩子喊,14 天考 10 门,能熬出来都成神了。不禁莞儿。想想, 我也是这么一路抱怨过来的。当年也怨过也骂过也狠过。也曾经用一晚上两三个小时把一 学期要学的东西吞进去过。当年是这样,今年,恐怕还是这样。


“大学”这两个字或许真的有魔力。进来之前是向往,进来之后是体味。甚至有大学生们常 挂在嘴边的“大学生活”这个词来凸显生活与以前别处的不同。不过显然没有“大学学习”这 个词。不过学习确实也是不一样了。以前不用专门抽时间来复习的,现在需 要腾一个月时 间了。以前上午考一门下午考一门不皱眉头的,现在今天考一门明天考一门就要抱怨了。


我不是旁观者,我也抱怨,甚至愤恨。


可是还是要思考为什么,为什么考试密集了会抱怨,为什么要有考期。


可能是知识太多,太复杂。其实我觉得,刚来时的传言“讲一、练二、考三”,平心而论, 再夸张一点,其实是“讲三、练二、考一”。基本上老师都是“不为难”,考的肯定是讲的子 集。理论上说,上课听,记住了,考试过是没问题的。老师说“上课听讲,课下消化”,没 有听说过“上课听讲,考期消化”的。从这一点来说,考期并无必要。


没有了考期,也就无从抱怨考期安排不合理了,该抱怨没有考期了。以前还有机会过,甚 至可以拿高分,现在连机会都没有了,这怎么能行呢?


其实是抱怨错了地方。如前所述,如果功夫下在平时,期末想过很容易。可是“平时”是个 隐性的时间,“考期”是个显形的时间。人们容易注意显性而忽略隐性的东西。比如大地震 ,举国关注,可以国内关注吸烟导致死亡的人却很少。其实,每年吸烟至死 500 万人,比 大地震死亡人数要多得多。美国的伊战在 08 年的时候已经死掉 4000 多人了。不过大部 分人不是在进攻的时候死掉的,而是在漫长的占领期内一点一点被各种事件杀掉的。如果 在第一波进攻内就死掉 4000人,估计它也就顶不住国内的压力要撤军了。可是现在,有谁 在关注么?同样是死人,“大家一起死”和“一个一个慢慢死”效果就很不同。同样是学习,“ 一个学期里 学习”和“一个考期里学习”所引起的关注也就不一样 —— 更何况考期还挂一个“ 考”字呢?


这也是一种懒惰。“枪打出头鸟”,“抓典型”,有意而为之的时候是一种手段、策略;无意 而为之的时候是一种祸患:只抓住了部分而没有把握整体;只听到了聒噪的“一小撮”而没 有看到“沉默的大多数”。有人可能会拿 80/20 原则来反驳。那么我会说:“ 什么样的人能 用 20% 的时间学习 80% 的知识并且把它们掌握牢固?”


我不是,我还是要用剩下的 20% 时间吞 80% 的知识,只期望于考试考过,不奢望于牢固 掌握。悲哀啊!莫过于此。


恐惧源于不知,待到明了之后,也就无所谓惧怕了。


立此存照

2009年12月11日星期五

Some Twist on the Blog

Creative Commons License
本作品采用知识共享署名-非商业性使用 2.5 中国大陆许可协议进行许可。

I spent some time on twist my blog(grissiom.blogspot.com) today. The major changes are direct RSS feed to feedburner( http://feeds.feedburner.com/Grissiom ) and add a CC icon on the blog. I choose NOT allow commercial uses of your work and Allow modifications of your work(not require as long as others share alike). So the articles on this blog will be covered by that license. Take care of this.

Also, I found a script named rst2wp.py from Using ReStructured Text with WordPress which is wonderful work. I adapted it to meet my blogspot's need. The script can be found on my MLES repository along with my other Make Life Easier Scripts ;-)

Hope you could enjoy them :-)