GNOMEフレームワーク
GNOMEとは
編集w:GNOME (GNU Network Object Model Environment) (発音:グノーム)は、w:GPLライセンスで提供されるGUIデスクトップ環境で、多くのLinux上およびUnix上で動作します。GNOMEは、Linux上で、GUI的な直感的な操作方法を提供するために用いられます。
もしユーザーに特に外見にこだわりが無く、またコンピュータを扱うスキルが相応にあるのなら、特にUnix系の環境ではGUIのデスクトップ環境が無くとも、コンピュータについてほとんど全ての管理や操作を行うことができます。
例としてプログラムの起動を行うことを考えてみます。Windowsやw:Macでは、プログラムを起動する際に対応するアイコンを用いて操作を行うことが普通です。 一方、デスクトップ環境が動いていないUnixではプログラムを起動するためのアイコンは提供されません。さいわい、アイコンが存在しなくとも対応するコマンド端末(一昔前は「シェル」とも言った。今では「ターミナル」ともいう。)からプログラムの名称を指定すれば、対応するプログラムを起動することができます。これは、デスクトップ環境を起動した上で対応するアイコンを用いた操作を行うことと同じ操作に対応します。コマンド端末の説明や実際の操作についてはUNIX/Linux入門を参照してください。
上の例から分かるように、コンピュータに慣れたものにとってはGUIデスクトップ環境はいくつかの場合を除いて無くてもよいものです。これはGUIで行うことができる操作は、大抵の場合、GUI無しでも行うことができるからです。
GNOMEは、初心者にも簡単にコンピュータを操作できる手法を提供します。このためには、熟練した利用者がコマンド端末を用いて行えることを、直観的なGUIとともに利用者に提供する方法を作成する必要があります。控え目にいってもこれは高度なプログラムです。例えば、GNOMEが提供するソースコードは、ライブラリからアプリケーションまで多岐に渡っており、それらは更にw:GTK (ツールキット)やw:GStreamerなどの他のライブラリに依存しています。
GPLライセンスは、そのプログラムの改変や組み込みなどの行為に対してソースコードを公開するよう義務づけています。そのため、GNOMEの利用者はGNOMEのシステムがどのように動いているかを知ることができます。これらの動作には様々な工夫がなされており、その中にはプログラミングが得意なものにとっても非常に興味のある内容が含まれています。
ここでは、GNOMEがどのように動作しているかを様々な角度から見て行きます。具体的にはGNOMEが起動するプロセスを確認し、それらがどのような動作とGUIを提供しているかをソースコードを通して見ていきます。特に、"gnome-panel"と"nautilus"(ノーチラス)および"metacity"(メタシティー)の動作を確認します。
gnome-panelは上の図上方と下方にある、パネルを提供するアプリケーションです。このアプリケーションはいわゆるw:ランチャー(launcher)であり、登録されたアプリケーションを起動するために用いることができます。このアプリケーションは主にWebブラウザやオフィススイート等のよく用いられるアプリケーションを起動するために用いられます。また、GNOMEやコンピュータの設定を行うためのプログラムもここに含まれる場合があります。
対応する"シェル"の機能は実行可能なアプリケーションを指定されたときに、そのアプリケーションを起動する機能です。熟練した利用者はよく使うアプリケーションの名称を把握しており、それを用いてアプリケーションを起動することができます。このため、ある程度アプリケーションの名称を把握していればランチャーは不要になります。 もちろん、あって損になるものではないでしょう。
次に、nautilus(ノーチラス)は、いわゆるファイルブラウザです。nautilusは、上の図では全てのウィンドウの後方にある"壁紙"を提供するアプリケーションです。実際にはそれだけではなく、基本的なファイル操作のための機能も提供します。例えば、ファイルのコピーや移動、消去などの操作をGUIで提供します。また、いわゆる"ゴミ箱"の機能も提供します。
ファイル操作はw:OSの機能として基本的であり、対応する操作は"シェル"の基本機能でもあります。例えば、ファイルのコピーはUnixでいうcpコマンドを用いてなされますが、このコマンドは読みだし先のファイルと書き出し先のファイルを開いてファイルディスクリプタを与え、そのファイルから読み出した内容をコピー先のファイルへ書き出すことで行われています(w:coreutils-x.x/src/cp.cを参照)。nautilusはこのような操作に加えてマウスの動作やドラッグアンドドロップなどの操作も把握している必要があります。
一方、壁紙を提供する機能は単に画像を配置する機能であり、画像を提供する機能がライブラリにより提供されているなら比較的単純なプログラムとなります。
最後に、metacity(メタシティー)はw:ウィンドウマネージャであり、上の図ではそれぞれのウィンドウの上に貼り付いており、最大化や最小化のためのボタンを提供している部分を与えるプログラムです。これはw:windowsやw:Macでは変更できない部分ですが、Unix上でGUIを提供するX Window Systemでは変更可能です。例えば、上の図中の環境でもmetacityを終了し、別のウィンドウマネージャを起動することで上に貼り付いている部分を付け変えることができます。
ウィンドウマネージャはGNOMEの機能というよりXの機能であり、古くから利用されているアプリケーションです。しかし、最近に至ってもw:compizやw:en:berylなどの先進的なウィンドウマネージャが提供されています。これらはXを拡張してウィンドウの透明化や3Dの表現などの機能を付け加えています。
ここからの議論ではある程度のC言語の知識を前提とします。また、Unixの動作や高等学校情報の内容も助けになります。これらの事柄を考えると、ここで扱う内容は大学2年以降で扱うべき内容といえそうです。
gnome-panel
編集gnome-panelの起動
編集まず、gnome-panelを起動する方法について述べます。w:LinuxなどのPC-Unix上でGNOMEデスクトップが動いているときには、gnome-panelは常に起動しています。この状態でシステムのプロセスを表示すると、"gnome-panel"という名称のプロセスが動いているはずです。プロセスを表示するには例えば
$ps ax
のコマンドを用いることができます。
「ps」は、現在動いているプロセス表示をさせるコマンドです。axは、psの表示対象について、すべて「表示しろ」という意味です。
gnome-panelが常に動いていることから、このプロセスをGNOMEデスクトップから離れて、個別に起動することはできないように見えます。しかし、実際にはgnome-panelはデスクトップを構成していると同時に1つのgtk+を用いたGUIアプリケーションでもあります。そのため、gtk+が利用できる環境なら、gnome-panelはGNOME全体を動かすこと無く個別に起動することができます。
Linux上でgtk+を用いるアプリケーションを動かすには、X Window Systemを動かす必要があります。通常GNOMEデスクトップはX上で動くので、GNOMEが動いているときにはXも動いています。ここでは実験のために一時的にGNOMEデスクトップとXを止めることにします。
Linuxを用いているときにこれを行うには、多くの場合システムの"ランレベル"を変える必要があります。"ランレベル"はシステムにおいてどのようなサービスを使用するかを定める値であり、通常3以下がGUIを使用しない環境であり、5がGUIを用いたデスクトップ環境を使う環境です。ここではシステムのランレベルを3にし、GUIを用いない設定を使います。Linuxとしてはw:Fedora Core 5を使用しました。
#/sbin/telinit 3
このコマンドによって"シェル"だけが与えられた環境へと移行します。以降では"シェル"だけを用いてコンピュータを操作する環境になるので、実際に試すときには注意してください。
上のコマンドは、Xも停止させてしまいますが、これではgnome-panelの起動もできないので、再びXを動かす必要があります。XもまたLinux上で動く1つのアプリケーションであるため、自由に停止と起動ができます。システムによってはXを起動するためのコマンドとしてstartxコマンドが推奨されているかもしれません。しかし、このコマンドは多くのXアプリケーションを起動するためここでは使用しません。その代わりに、ここではXを動かすためにxinitコマンドを利用します。xinitはXサーバと少数のXクライアントを起動するプログラムです。これについてはXプログラミングも参照してください。筆者の環境ではxinitコマンドはXサーバに加えて1つのxtermを起動しました。ここで、xtermはX上で動く端末です。
ここまででgnome-panelを起動する準備が整いました。gnome-panelが存在する場所はシステムによってまちまちですが、環境変数PATHの設定が正しければ
$gnome-panel
で見慣れたgnome-panelが表示されます。ただし、GNOMEデスクトップが動いていない場合、"ウィンドウマネージャ"が貼り付いてしまいます。ウィンドウマネージャとしてはtwmを使用していますが、これはXに付属のアプリケーションです。twmは古いアプリケーションなので見た目がやや現代的でない点が難点です。
gnome-panelを停止するには
$killall gnome-panel
などを用いてください。killallは指定された名前のプロセスを停止するコマンドです。もしくは、killコマンドを用いることでもプロセスの停止を行うことができます。kill, killallの使い分けについては、UNIX/Linux入門を参照してください。
gnome-panelの動作
編集ここからはgnome-panelの個々の機能に付いてソースコードを見ながら説明します。このためにはある程度のGUIプログラミングの知識が必要です。gnome-panelのソースコードはGNOMEのサイト[1]からGPLライセンスで提供されています。筆者が使った版はgnome-panel-2.14.3です。ここで、これ以降./の記述が出て来た場合には、gnome-panel-2.14.3/の意味を表しています。また、同じサイトからはGNOMEデスクトップの使い方に関する文書も提供されています。
また、gnome-panelのGUIに用いられているライブラリであるgtk+はCで書かれたライブラリであると同時にw:オブジェクト指向を用いたライブラリです。以降ではオブジェクト指向の用語としてクラス、インスタンス、メソッド、メンバなどの語句を使いますが、異なった言語では同じ意味の内容に異なった名前をあてていることがあるので注意してください。例えば、オブジェクト指向における"オブジェクト"は大抵インスタンスのことを指します。
各種"オブジェクト"の配置について
編集gnome-panelは基本的にはランチャーであり、各アプリケーションの起動を行います。ここで、ランチャーを使うためには、そのランチャーに、どのようなアプリケーションをどのコマンドで起動するかなどの情報を与える必要があります。GNOMEではこの目的でw:en:gconfと呼ばれるライブラリを使います。gconfはGNOMEアプリケーションの様々な設定を記録するためのライブラリです。 ここではまず設定の内容がgnome-panelに伝達されたとして、それを用いてどのように実際に表示される内容が構築されるかについて見て行きます。
gnome-panelには"オブジェクト"と呼ばれるGUI要素があります。"オブジェクト"は、メニューや時計など、gnome-panel内で操作を与えられているGUI要素の総称で、そのGUIは何らかのgtk+ウィジェットで与えられます。ここでは個々のオブジェクトの性質は後回しにして、これらを配置する部分に注目します。
gnome-panelには、実際にオブジェクトを設置できる部分が提供されています。これは、パネル内でメニューや時計などが設定されていない全ての部分を指します。もっとも実際にはメニューや時計なども1つの"オブジェクト"であるため、これらを取り去ることでパネル中のあらゆる位置にオブジェクトを配置することができます。
GUI要素を自由に配置できるgtk+ウィジェットとしてGtkFixedウィジェットがあげられます。このウィジェットはGtkContainerクラスを継承しており、いくつかの別のウィジェットをパックする事ができます。また、ウィジェット内でパックしたウィジェットの場所を指定することができます。GtkFixedを使ったサンプルとして次に例をあげます。
Gnome対応アプリの作り方
編集主にGTKというアプリケーションを使う。詳しくはGTKプログラミング(wikibooks内)を参照せよ。
glib
編集w:Gtk+はCで書かれたツールキットです。Gtk+では、w:en:glibを利用してCの枠組の中で、いわゆる「オブジェクト指向型」のプログラミングを実行しています。一見すると不思議ですが、w:継承もサポートされています。
ここでは、最初にglibについて述べ、その後にglibを使ったGUIについて述べます。 GTK+のオブジェクト指向 Cのオブジェクト指向
(このあたりの事柄はglibソースのglibx.x.x/docs/refrence/gobject 以下の文書によっています)
w:オブジェクト指向にはカプセル化、w:継承などいくつかの側面があります。単純にデータに動作を付け加えるという考え方をするなら、Cの範囲でも構造体を利用することでオブジェクト指向を実現することができます。
例えば、IDと名前を持ったmemberというオブジェクトを考えるなら、(IDに対するsetとgetも付け加えることにします)
struct member {
int id; char * name; int getID(struct member *); void setID(struct member *, int);
}
とすることで、memberの振舞いを記述できます。
次に、memberの働きを継承するemployeeを定義することを考えてみます。ここで、employeeは、memberの性質に加えて、賃金(wage)と、仕事をする(work)の2つを付け加えることにします。
この振舞いを付け加えるには、Cのキャストを利用します。まず、employeeを、
struct employee {
struct member parent; double wage; void work(employee *);
}
として定義します。parentとしてstruct memberを取っているところが重要です。
あるemployeeであるemployee1を定義して、そのIDを表示したいとしましょう。この場合には、
struct employee employee1; printf("%d",((struct member *)&employee1)->getID());
とすることで、employee1のIDを取得できます。
この振舞いを見るためには、構造体とポインタについていくつかの事柄を知る必要があります。まず、member構造体のポインタもemployee構造体のポインタも単なる数値であり、互いにキャストを行なうことで移り変われることに注目します。このことから、employee1のアドレスをstruct member * にキャストできることがわかります。
更に、構造体の要素を取りだす演算が、ポインタに指し示されたメモリアドレスより構造体内での取りだす要素の分だけずれたアドレスを計算することに着目します。->はアドレスをデリファレンスした上で構造体の要素を取りだすので、(... )->getID の部分はemployee1のアドレスからstruct member 内でのgetID の位置までずらしたアドレスを取りだします。おそらくこのアドレスは&employee1 より sizeof(int) + sizeof(char *) だけずれた値と予想されます 。(実際にはこのことは保証されていないようです。)
しかし、struct employee はstruct member から始まる構造体なので、上で示した位置にあるのは、struct member 内にあるgetID 関数です。結局ここまでの手順で、struct employee からstruct member の関数が使えたことになります。 glibとは
ここまでの手順でCを用いてオブジェクト指向プログラミングが出来ることがわかりました。しかし、上の例にはいくつか不足した部分があります。
id, name, wage はそれぞれのインスタンス間で異なる値を取るので、インスタンスごとに領域を確保する必要があります。しかし、getID, setID, work などの関数は、全てのemployeeについて共通の関数であり、それぞれのインスタンスにこの関数を与える必要はありません。これらの関数は別の場所にまとめることが望ましいやり方です。
次に、CPlusPlusで存在したnew 演算子やコンストラクタがここでは定義されていません。これらを提供しないとメモリの扱いが難しくなり不便です。
w:glibは、Cの枠組みでこれらの問題を解決するために作られたライブラリです。gtk+はglibの機能をフルに使って書かれています。
実際にはglibの機能はこれだけではなく、C++でいうw:STLに属するw:リスト、w:連想配列などの汎用的なデータ構造もglibの一部として与えられています。また、glib はgtk+ が利用できる環境では必ずインストールされているため、移植性が高いこともメリットです。
ここからはglibの使い方について述べます。glibはCを使ってオブジェクト指向プログラミングを行なうときに有効です。
glibでは、インスタンス間で共有される関数や変数を、class という枠組みで管理します。また、関数や変数を持った型のことを、オブジェクトと呼びます。例えば、aaa というオブジェクトを定義するときには、struct aaa は、aaaのインスタンスであり、struct aaa_class は、aaaのクラスです。
実際の定義では更にいくつかの慣用的記法を利用しますが、これ以上の詳細はglibのリファレンスを参照して下さい。実際にaaaの定義をするには、かなり多くのコードを書く必要があります。
/* aaa.h */
- define TYPE_AAA (aaa_get_type())
- define AAA(self) ((aaa*) self)
- define AAA_CLASS(self) ((aaa_class*) self)
- define AAA_GET_CLASS(self) (G_TYPE_INSTANCE_GET_CLASS((self),TYPE_AAA, aaa_class))
typedef struct _aaa aaa; typedef struct _aaa_class aaa_class; struct _aaa{
GObject parent; /* フィールド */
}; struct _aaa_class { GObjectClass parent;
/* オブジェクト間で共有される関数など */
}; void aaa_init (GTypeInstance *self, gpointer data); void aaa_class_init (gpointer *self, gpointer data); GType aaa_get_type ();
/* aaa.c */
- include <glib.h>
- include <glib-object.h>
- include <aaa.h>
GType aaa_get_type () {
static GType type = 0; if (type == 0) { static GTypeInfo info ={ sizeof(aaa_class),0,0, aaa_class_init,0,0, sizeof(aaa),0, aaa_init } type = g_type_register_static (G_TYPE_OBJECT, "aaa", &info, 0); return type;
} /* aaa_init(), aaa_class_init()の定義 ... 。*/
上の例は非常に長くなるようですが、実際にはaaa_get_type() やマクロの定義は全てのglibオブジェクトで共通なので、他の例からコピーすればよいでしょう。
C++の例でいえば、コンストラクタはaaa_initに対応します。ただし、コンストラクタのオーバーロードには対応していないため、実際にいくつかの引数を与えて初期化を行ないたいときには、別のset関数を利用する必要があるでしょう。
aaa_class_init ではインスタンスから使用できるメソッドを与えます。例えば、aaa_class 内にwork() という関数が与えられているなら、
AAA_CLASS(self)->work = aaa_work;
という代入によってaaaのメンバ関数を定義することが出来ます。もちろんaaa_work は別のところで定義する必要があります。
ここからは実際のプログラム中でクラスを利用する方法を述べます。
まず、glibを用いて定義されたクラスを利用する前に、g_type_init()を利用する必要があります。この関数は型システムの基本的な初期化を行ないます。(gobjectリファレンスマニュアル API Reference より)
クラス定義以外の、実際のプログラム中では、C++でいうnew演算子でaaaのインスタンスを作成します。new演算子の機能はこれらの定義を与えた後で、g_object_new() 関数によって提供されます。例えば、aaaのインスタンスを作成したいときには、
aaa * aaa1 = g_object_new(TYPE_AAA, 0);
によってaaa_initで値が初期化されたインスタンスを得ることが出来ます。aaa1からメンバ関数を利用するには
AAA_GET_CLASS(aaa1)-> work(aaa1);
としてworkを利用することが出来ます。
ここまででnew 演算子とメンバ関数の呼び出し方を説明しました。最後にC++でいうdelete 演算子の説明をします。g_objct_newで確保された領域はC++のnewの場合と同様、手動で解放しないとw:メモリリークを起こします。これを解放するには、C++ではdelete演算子を利用します。glibでは、deleteではなくg_object_unref を利用します。この関数の名前は、メモリの解放がw:参照カウントを用いて行なわれていることによります。参照カウントに関する説明はw:参照カウントを参照して下さい。
g_object_unrefは、g_objectを継承したインスタンスの参照カウントを1減らします。参照カウントはg_object_newでオブジェクトが作成されたときには1に設定されており、0になったときに確保したメモリが解放されます。