LibreOfficeを手探る

はじめに

大学で「大規模ソフトウェアを手探る」という、世の中で実際に使われている大きなオープンソースのソフトウェアを改良しようとする講義がありまして、そこで僕が行ったことをここに記したいと思います。

目的

Ubuntuを使っている人にはおなじみのLibreOffice、これはウィンドウズでいうところのマイクロソフトオフィスのようなものでワード、エクセル、パワーポイントなどに対応する機能があります。

うちの大学の学科ではUbuntuの入ったノートPC(学科PC)を貸し出していて授業でもよく使うのですが、LibreOfficeを使っていてなんとなく使いずらいなと思った経験から今回の講義でいじるソフトとしてLibreOfficeを選びました。

今回やろうとしたことは、

1.ボタンの配置を変える。
2.Impress(パワーポイントのようなもの)で図形の挿入を連続でできるようにする。
3.Writer(ワードのようなもの)で下付き文字を打っているときにスペースを打つと下付き文字が解除されるようにする。

の3つです。

ダウンロードからデバッガの起動まで

ソフトウェアをいじれるようにするためにはソースコードをダウンロードしてビルドしないといけません。LibreOfficeソースコード
Index of /libreoffice/old
からダウンロードできます。学科PCに入っていたLibreOfficeのバージョンは4.2.8.2だったのでまずそれでビルドしようとしたのですが、makeするときに謎のエラーが出たので(後述)去年のこの講義で使われていたバージョン4.4.5.2を使うことにしました。ビルドも去年の先輩の記事
LibreOfficeCalcに関数候補表示機能を付けるまで 第1回 目的の設定と準備 - Qiita
を参考にしました。

$ cd (ダウンロードしたフォルダ)
$ tar xvf libreoffice-4.4.5.2.tar.xz
$ cd libreoffice-4.4.5.2
$ sudo apt-get build-dep libreoffice
$ ./autogen.sh --with-lang="ja" --prefix=インストールしたいフォルダ --enable-dbgutil

ここでgstreamer-0.10などが足りないというエラーが出たが、表示されたものをそのままapt-get install しても上手くいきませんでした。TAの方に伺ったところこういうときは

$ apt-cache search ~~~~ //例えばgstreamer-0.10

で検索して名前の頭にlib、後ろに-devがついているもの(例えばlibgstreamer0.10-dev)を入れると上手くいくことが多いらしく、実際それでなんとかなりました。

$ ./autogen.sh --with-lang="ja" --prefix=インストールしたいフォルダ --enable-dbgutil
$ make

一回目のmakeでは途中で

bash: /home/denjo/Downloads/libreoffice-4.2.8.2/bin/unpack-sources: 許可がありません
make: *** [get-submodules] エラー 126

といったエラーが出たので、

$ cd bin
$ chmod +x unpack-sources
$ cd ../
$ make

とすると4.2.8.2のバージョンだと

Error: home/denjo/libreoffice-4.2.8.2 is not a valid LibreOffice core source directory
make: *** [get-submodules] エラー 1

というエラーが出て、解決方法がわからず試しにバージョン4.4.5.2で今までの手順を繰り返すとmakeが通ったので今回の課題ではバージョン4.4.5.2を使うことにしました。最後に

$ make install

で指定したフォルダの中にlibというフォルダができているはずです。

これでEmacs上で

M-x gud-gdb
Run gud-gdb (like this): gdb --fullname (libがあるフォルダ)/lib/libreoffice/program/soffice.bin 

とやればデバッグできるようになります。M-xはAltキーとxの同時押しという意味で、これをEmacs上で入力すると下に細い窓が出てくるのでそこに入力します。

ボタンの配置を変える

さて、デバッガが起動できるようになったのでソースコードに変更を加えていきたいと思います。しかし、こんなに大きなプログラムに触れるのは初めての経験なのでどうすればいいのかよくわかりません。とりあえずボタンの配置を変えるくらいなら割と楽にできそうだと思ったのでそれを目標にします。

gdbでrunするとしばらくしてLibreOfficeが立ち上がるのですが、そうするとgdbからの操作を受け付けなくなってしまいます。どうしたものかとlibreoffice-4.4.5.2フォルダ内のREADMEファイルを読んでみるとswというフォルダがWriterに、sdというフォルダがImpressに、scというフォルダがCalcに対応しているということなのでひとまずswの中を見てみることにしました。

それっぽいところを探っているとlibreoffice-4.4.5.2/sw/uiconfig/swriterフォルダの中にtoolbarといういかにもなフォルダがあり、その中にはxmlファイルが大量に入っていました。いくつか開いてみるとstandardbar.xmlというファイルがWriterの上部にあるボタンの並びとそっくりな文字列の構成になっていたので,

<toolbar:toolbaritem xlink:href=".uno:PrintDefault" toolbar:visible="false"/>
<toolbar:toolbaritem xlink:href=".uno:Print"/>
<toolbar:toolbaritem xlink:href=".uno:PrintPreview" toolbar:helpid="5325"/>
<toolbar:toolbarseparator/>
<toolbar:toolbaritem xlink:href=".uno:Cut" toolbar:helpid="5710"/>
<toolbar:toolbaritem xlink:href=".uno:Copy" toolbar:helpid="5711"/>
<toolbar:toolbaritem xlink:href=".uno:Paste" toolbar:helpid="5712"/>
<toolbar:toolbaritem xlink:href=".uno:FormatPaintbrush" toolbar:helpid="5715"/>
<toolbar:toolbarseparator/>
<toolbar:toolbaritem xlink:href=".uno:Undo" toolbar:helpid="5701"/>
<toolbar:toolbaritem xlink:href=".uno:Redo" toolbar:helpid="5700"/>
<toolbar:toolbarseparator/>

試しにUndoとRedoの間に

<toolbar:toolbaritem xlink:href=".uno:ArrowShapes.left-arrow"/>

を挿入してみると、

f:id:libsoft:20161019140739p:plain

こんな感じにUndoとRedo(黄色と緑の矢印)の間にもうひとつ矢印が出てきました。
これは矢印マークを挿入するボタンなのでここにあってもまったく使いやすくはありませんが、こんな感じで好きにボタンの配置を変えられるのはちょっと便利なような気がしなくもないのではないでしょうか。

図形の連続挿入

さてここからが本番のようなものです。
図形を連続で挿入するためには図形を挿入したあとで文字を打つモードに戻るところを戻らないようにできればいいだろうと思って、図形を挿入するプログラムあたりにブレークポイントをはれればいいと思いました。
さきほども書いたようにLibreOfficeが立ち上がるとgdbからはさわれなくなるのですが、 Impressを開いて文字を打ったり図形を挿入したりしていると

[Thread 0x7fffabe73700 (LWP 3147) exited]
[New Thread 0x7fffabe73700 (LWP 3148)]
warn:ucb.ucp.gio:3080:1:ucb/source/ucp/gio/gio_content.cxx:404: ignoring GError "サポートしていない操作です" for <>
[Thread 0x7fffabe73700 (LWP 3148) exited]
[Thread 0x7fffc9dc9700 (LWP 3145) exited]

このようにgdbにwarnと表示されてどこのプログラムからそれが表示されているのかもわかるので、
図形を挿入しているときに表示される場所にブレークポイントをはれば図形を挿入したところでブレークポイントが発動してくれるのではと思い、
libreoffice-4.4.5.2/vcl/source/window/toolbox.cxx:4090
ここにブレークポイントをはって図形を挿入したところ思ったとおり止まってくれた。
しかしそこからgdbでたどっていくとsidebarという表記がよく出てくるようになったので、試しにサイドバーを消してから図形を挿入してみると止まってくれなくなってしまいました。

そこでサイドバーを消した状態で図形を挿入したらgdbからwarnを出してきた場所
libreoffice-4.4.5.2/sd/source/ui/view/viewshel.cxx:474
ブレークポイントをはると、図形を挿入しようとドラッグしてはなしたところで図形が表示される前に止まるようになりました。
ここからgdbで追っていくと、

 while ( !pSVData->maAppData.mbAppQuit )
        Application::Yield();

こんなところに行き着いて、nextでひたすらこのwhile文を繰り返しているとやがて図形が表示されて図形挿入のボタンの選択状態も解除されるのですが、そこに至るまでにこのwhile文を繰り返す回数がその時々によって変わってしまうのでstepで中に入っていこうにもいけないという困った状況に陥ってしまいました。

途方にくれてなんとなくネットに情報を求めていたところ、
同じ図形を連続して描きたい:Office(オフィス)の使い方-描画オブジェクト
こちらの記事でMicrosoftOfficeの方では図形挿入ボタンをダブルクリックすれば連続で挿入できるということを知り、ためしにImpressでもやったところできてしまいました。
もっと調べてから作業に取りかかればよかったです。

下付き文字状態をスペースを打ったら解除されるようにする

さて、ここからは下付き文字状態解除の実装をしたいと思います。
そもそも何故このようなことを実装しようと思ったかというと、WriterやWord(Windows)で実験レポートを書いていた時に下付き文字で変数・定数の添字を書く機会が多かったことによります。
その時、文章中・式中で添字を書こうとするたびにツールバーの「下付き」ボタンをクリックし、書き終えたらOFFにするために再び「下付き」ボタンをクリックするという操作をしなければならず、マウスとキーボート間での行き来が非常に面倒でした。
一応ショートカットキー(Writerでは[Shift+Ctrl+B])も設定されていますがもう少し楽に切り替えたいと思いました。
そこで、下付き文字としては使うことのなさそうな[Space]キーを打ったら下付き文字状態を解除できるようにするというシステムを実装しようと思いたったわけです。
ちなみに、texを使えば添え字を簡単に書けるらしいので、使うソフトをそちらに切り替えるというのもこの問題解決の一手ではあります。(自分は未だに使ったことはありません)

前置きはこのくらいにして、プログラム自体を手探っていきたいと思います。
変更を実装するためには、膨大なソースコードの中から下付き文字状態切り替えに大きく関わるソースコードを見つけ出さないことには話にならないと思ったので、それを探すことをひとまず目標として動いていきます。

まず、見つける方法として考えたのは、プログラム起動時点からgdbで順番に動きを追っていけばいずれ目的のソースコードにたどり着くだろうと思いました。
そこでgdbを起動してrunすると、前述の通りgdbをさわれなくなります。
その状況を打開するために、[Ctrl+C]を二回入力することで無理やりgdbのコマンド入力画面に戻すことができました。
そこからnext、stepなどのコマンドを使っていろいろ動かしてみたところ、
/sw/source/core/doc/DocumentStatisticsManager.cxx:147
というところにたどり着いたため、そこにbreakpointを設定するとプログラム起動時にbreakpointで止まり、無事にgdbでいじれるようになりました!

なんとか普通にgdbから探れる様になったため、ここからは本格的にgdbを使ってプログラムの動きを探って行けます!
と、意気込んで探っていくと・・・

 while ( !pSVData->maAppData.mbAppQuit )
        Application::Yield();

というコードにたどり着きました。

え?どこかで見たことある??

そう、上の図形連続挿入でも出てきましたね。
色々と動かして試してみたところ、どうやらこのYield()、Writer・Impressなどの起動からlibreofficeを開いた後の文字入力など様々な範囲まで包括している関数であり、何をしていても最終的には必ずたどり着きます。
そんな膨大な範囲を網羅している関数から探っていくなんて先が長すぎて無理・・・
ということで、この方向性で探っていく案はボツ。
別の手法を考えることにします。


授業で聞いて知っていたことですが、Ubuntuでの端末(Macでのターミナル、Windowsでのコマンドプロンプト)にはgrepというコマンドがあります。
これは検索用のコマンドで、

$grep ○○○○

と入力することでディレクトリ内をキーワード検索することができます。

とはいえ何のキーワードで検索すればいいのかもわからなかったので使っていなかったのですが、そこでTAの人からのアドバイス。
下付き文字の英語訳でgrepしてみればいいと。
調べてみると、下付き文字の英語訳は "subscript"。
正直言って、ここまでそのものズバリの英語訳があると思ってなかったので、これがわかったことはかなり嬉しい発見でした。

ということで、grepを使って調べてみます。
その結果、「FN_SET_SUBSCRIPT」という、いかにも下付き文字状態切り替えに関わっていそうな定数を発見しました!

さらにFN_SET_SUBSCRIPTでgrepして検索していきます。
検索結果に出たファイルを見ていったところ、
SVX_ESCAPEMENT_SUBSCRIPT
SVX_ESCAPEMENT_OFF
という、いかにも下付き文字状態をOFFにしそうな定数が直近にあるコードを発見!
見つけたコードがこちら
/sw/source/uibase/shells/txtattr.cxx:84付近

switch ( nWhich )
    {
        case FN_SET_SUB_SCRIPT:
        case FN_SET_SUPER_SCRIPT:
        {
            SvxEscapement eEscape = SVX_ESCAPEMENT_SUBSCRIPT;
            switch (eState)
            {
            case STATE_TOGGLE:
            {
                short nTmpEsc = ((const SvxEscapementItem&)
                            aSet.Get( RES_CHRATR_ESCAPEMENT )).GetEsc();
                eEscape = nWhich == FN_SET_SUPER_SCRIPT ?
                                SVX_ESCAPEMENT_SUPERSCRIPT:
                                SVX_ESCAPEMENT_SUBSCRIPT;
                if( (nWhich == FN_SET_SUB_SCRIPT && nTmpEsc < 0) ||
                            (nWhich == FN_SET_SUPER_SCRIPT && nTmpEsc > 0) )
                    eEscape = SVX_ESCAPEMENT_OFF;

目的のコードを発見したところで、今度はこのコードの意味を理解するとしましょう。

実際は色々と紆余曲折ありましたが、gdbで動きも確認しながらも、なんとか重要そうな変数の意味を理解しました。
簡単に、それらの意味は以下の通り
nWhich: 現在どのボタンが押されたか
nTmpEsc: ボタンを押される直前にどの状態(下付きor通常)になっていたか
eEscape: 下付き文字状態をオフにする際、ここにSVX_ESCAPEMENT_OFFを代入

このあたりの変数を、スペース入力時に上手く書き換えられる様に出来れば、目的のシステムを実装できるだろうというところまでは突き止めることが出来ました。


スペース入力時に下付き文字状態を解除出来るようにするためには、スペースを入力したのかどうか判別出来なければならない、つまり文字入力時に関わるコードも見つけなければなりません。
そこで、grepしたり、
Search
ここでそれっぽい単語で検索してみたりしていたところ、
svx/source/svdraw/svdview.cxx:182
ここにブレークポイントをはるとキーボードでなにか入力した途端ブレークポイントが発動するようになりました。
そこから少し辿って行くと、
sw/source/uibase/docvw/edtwin.cxx:1453
ここに

sal_Unicode aCh = aKeyEvent.GetCharCode();

このように変数が定義されていて、aChの中身を見てみると入力した文字の文字コードが代入されていました。
なのでこの下にaChにスペースの文字コードが代入されたら下付き文字状態を解除するコードを書けばいいことがわかりました。

しかし、前述のnWhich・eEscapeなどの変数はローカル変数であり、aChのあるコードのファイルとは全く違うディレクトリに存在しているため、直接それらの変数の値を書き換えることは簡単には出来ません。
きれいに変更を加えるためには、nWhich・eEscapeがあるコードからさらに先を辿り、もっと決定的なコードを見つけ、aChのあるコードからもアクセス出来る範囲にある下付き文字状態切り替えを担っている変数を見つけなければなりません。

その方針でなんとか頑張ってみようと思いましたが、この時点であまり時間は残っておらず、目的の変数・コードを見つけることはできませんでした。


しかし、試しに下付き文字状態を解除している部分をまるごとコピーして、aChに文字コードが代入されているところの直下にペーストしてみるという、若干無理矢理な方法をやってみることにしました。
そのままmakeすればエラーが大量に出ることはわかりきっているので、下付き文字状態を管理しているファイルにインクルードされているファイルを全てインクルードしてからmakeしてみたところ「この関数は定義されていない」といった感じのエラーが山ほど出てきました。
とりあえずエラーが出ないように最低限削ってみようと思い、いろいろ試したところ、

 if(aCh==' '){
        SvxEscapement eEscape = SVX_ESCAPEMENT_OFF;
        SfxBindings& rBind = GetView().GetViewFrame()->GetBindings();
        rBind.SetState( SfxBoolItem( FN_SET_SUPER_SCRIPT, false ) );
        SvxEscapementItem aEscape( eEscape, RES_CHRATR_ESCAPEMENT );
        rSh.SetAttrItem( aEscape );       
    }

これを入れたら下付き文字状態でスペースを打ったら下付き文字状態が解除されるようになりました!

終わりに

最終的には、理想通りの挙動をするプログラムを実装することは出来た点は満足できました。
とはいえ、あまり綺麗な実装法ではないだろうという自覚はあるので、その点は少し不満の残る結果ではあります。
実装できたとは言っても、試しにやってみたらできた、という程度のものなので、結局どういう関数・変数・ファイルが原因で実装できたのかという具体的な話はよくわかっていないままです。
出来ればそのあたりの原因を究明したうえで、より理想通りのプログラムの変更を実装してみたいところでしたが、現在の自分の知識・技術ではそこまでたどり着くことは出来ませんでした。

libreofficeソースコードをいじってみたいという方々への思いつくアドバイスとしては
・Application::Yield()にたどり着いてしまったら、別の方針を考える
grepなどを使って上手く目的のソースコードを見つける
などですね。

稚拙な文章だったかもしれませんが、何か一つでも参考になる点があれば幸いです。