ウィンドウマネージャの国際化と言うとなんだかすごそうに聞こえますが,実はウィンドウマネージャは国際化がしやすいプログラムの最右翼です. それはなぜかと言うと,次のような理由によります.
そのため,ウィンドウマネージャの国際化は機械的な作業でやれます. 作業時間も慣れれば1つあたり1時間程度でしょう(もちろんウィンドウマネージャにもよります).
ただし,このページではまともな I18N であることより,お手軽さを優先させている点は御了承ください.
おおまかな手順は以下のようになります.
書き換える場所はほぼ機械的に決められるので,予めソースに grep をかけて当たりを付けます. 次の型,関数名をキーワードに grep を実行し,結果を参照できるようにしておきましょう.
これについては,ヘッダファイルをインクルードしている部分への
#include <locale.h>の追加と,main の先頭部分への
setlocale(LC_CTYPE, "");
の追加を行うだけです.
真面目にやるなら setlocale に失敗した場合のエラー処理もしてください.
twm などのように Xt で書かれているウィンドウマネージャの場合は,setlocale を使う代わりに,main の先頭部分に
XtSetLanguageProc(NULL, NULL, NULL);
を追加します. (XtSetLanguageProc は内部的に setlocale を呼び出すので,改めて setlocale を呼び出す必要はありません.)
ウィンドウ名の取得には,XGetWMName か XGetWindowProperty を使います.
例えば,AfterStep-1.4 のソースでは次のようなコードでウィンドウ名を取得しています. (昔むかしの twm や fvwm の時代から,この部分の書き方はほとんど変わっていません.)
if (XGetWMName(dpy, tmp_win->w, &text_prop) != 0) tmp_win->name = (char *) text_prop.value; else tmp_win->name = NoName;
これを次のように書き換えます.
XTextProperty text_prop; char** cl; int n; if (XGetWMName(dpy, tmp_win->w, &text_prop) != 0) { if (text_prop.encoding == XA_STRING) { tmp_win->name = (char *)text_prop.value; } else { XmbTextPropertyToTextList(dpy, &text_prop, &cl, &n); if (cl) { tmp_win->name = strdup(cl[0]); XFreeStringList(cl); } else { tmp_win->name = NoName; } } } else { tmp_win->name = NoName; }
ウィンドウ名を格納する領域を malloc(strdup)で確保する場合には,メモリを malloc したままにならないように注意しましょう.
ウィンドウ名の場合とほぼ同様です. 今度は #ifdef を使って,次のように書き換えてみましょう.
#ifdef I18N if (XGetWMIconName(dpy, tmp_win->w, &text_prop) != 0) { XmbTextPropertyToTextList(dpy, &text_prop, &cl, &n); if (cl) { tmp_win->icon_name = strdup(cl[0]); XFreeStringList(cl); } else { tmp_win->icon_name = tmp_win->name; } } else { tmp_win->icon_name = tmp_win->name; } #else XGetWindowProperty(dpy, tmp_win->w, XA_WM_ICON_NAME, 0L, 200L, False, XA_STRING, &actual_type, &actual_format, &nitems, &bytesafter, (unsigned char **) &tmp_win->icon_name); if (tmp_win->icon_name == (char *) NULL) tmp_win->icon_name = tmp_win->name; #endif
フォントの処理についての作業は以下の4つです.
XFontStruct * を XFontSet に変更します.
XFontStruct * の処理はそのままで,新たに XFontSet を追加する方法もありますが,個人的には置き換えてしまうほうが好みです. XFontStruct * と XFontSet で2重にフォントをロードすると,後の処理が面倒だからです. 特に,設定ファイルでカスタマイズできるウィンドウマネージャの場合,設定ファイルの読み込みの部分まで修正が必要になってしまいます.
例えば,次のように XLoadQueryFont を,自作の代替関数 XLoadQueryFontSet で置き換えます(XLoadFont も同様). 下記の「適当なヘッダファイル」は,XLoadQueryFont を呼び出すファイル全てがインクルードするものがよいでしょう(twm なら twm.h など). 「適当なファイル」は,ちゃんとリンクされるのなら,どこでも構いません.
#define XLoadQueryFont XLoadQueryFontSet XFontSet XLoadQueryFontSet(Display *, const char *);
#ifdef I18N XFontSet XLoadQueryFontSet(Display *disp, const char *fontset_name) { XFontSet fontset; int missing_charset_count; char **missing_charset_list; char *def_string; fontset = XCreateFontSet(disp, fontset_name, &missing_charset_list, &missing_charset_count, &def_string); if (missing_charset_count) { fprintf(stderr, "Missing charsets in FontSet(%s) creation.\n", fontset_name); XFreeStringList(missing_charset_list); } return fontset; } #endif
フォントセットの破棄は,XFreeFontSet を使うだけなので簡単です.
こいつは XFreeFont と引数の形が同じなので,XFontStruct * を XFontSet で置き換えてさえいれば,適当なヘッダファイルで
#define XFreeFont XFreeFontSet
を定義しておけば終わりです.
フォントセットについて,ascent, descent の数値を求めます. X はこれらの値に基づいて文字の高さを定義しているため,ほとんどのウィンドウマネージャも同様にフォント/文字列の高さを管理しています.
元のウィンドウマネージャの処理としては,
の2パターンがありますが,いずれの場合も XExtentsOfFontSet で取得した値で置き換えることになります.
修正前:
MyFont font; /* ウィンドウマネージャの自前フォント構造体 */ font->height = font->font->ascent + font->font->descent; /* font->font は XFontStruct */ font->ascent = font->font->ascent; font->descent = font->font->descent;
修正後:
MyFont font; /* ウィンドウマネージャの自前フォント構造体 */ XFontSetExtents *extent; extent = XExtentsOfFontSet(font->fontset); /* font->fontset は XFontSet */ font->height = extent->max_logical_extent.height; font->ascent = extent->max_logical_extent.height * 4 / 5; font->descent = extent->max_logical_extent.height / 5;
twm は全体の高さも管理しているので,max_logical_extent.height もそのまま使っています.
また,ここで使っている 4/5, 1/5 という数値は,あやしい決め打ちの値ですが,昔 gtk のソースを読んだときこうしてあったので,今でも何となく使っています(^_^;
ここのところは
MyFont font; /* ウィンドウマネージャの自前フォント構造体 */ XFontSetExtents *extent; extent = XExtentsOfFontSet(font->fontset); /* font->fontset は XFontSet */ font->height = extent->max_logical_extent.height; font->ascent = - extent->max_logical_extent.y; font->descent = extent->max_logical_extent.height - ascent;でもよいと思います.
XFontStruct の値が文字列表示やレイアウトなど,色々な場所で使われているはずなので,それらを全て修正します. 以下は Afterstep 1.4 の場合を元にした例です.
修正前:
XFontStruct *font; int label_h; label_h = font->ascent + font->descent + 2;
修正後:
XFontSet font; int label_h; XFontSetExtents *extent = XExtentsOfFontSet(font); label_h = extent->max_logical_extent.height + 2;
offset = XTextWidth(XFontStruct *font, char *string, int length);を
offset = XmbTextEscapement(XFontSet fontset, char *string , int length);
に置換します.
この部分については,引数の形式が同じなので,適当なヘッダファイル中で
#define XTextWidth XmbTextEscapement
のようにマクロ置換してしまえばOKです. (もちろん予め XFontStruct * を XFontSet に置き換えていなければなりません.)
1バイト文字の場合は GC にフォントを設定することが多いのですが,マルチバイト文字の場合は GC にフォントセットを設定することができません. また XFontStruct * を XFontSet に置き換えてしまうと,当然ながら GC にフォントを設定するところでコンパイルエラーになります.
そこで,GC にフォントを設定する部分を全部取り除きます. 具体的には次のような記述になるでしょう. gcm はマスクを表す unsigned long型,gcv は XGCValues * 型の変数です.
(ただし,XFontStruct * と XFontSet を別々に設定する場合には,この変更は必要ありません.)
#ifdef I18N gcm = GCForeground | GCBackground; #else gcm = GCForeground | GCBackground | GCFont; #endif gcv.foreground = fore_pix; gcv.background = back_pix; #ifndef I18N gcv.font = font->fid; #endif NormalGC = XCreateGC(dpy, Scr.Root, gcm, &gcv);
XDrawString(disp, window, gc, x_loc, y_loc, buf, len); XDrawImageString(disp, window, gc, x_loc, y_loc, buf, len);をそれぞれ
XmbDrawString(disp, window, font, gc, x_loc, y_loc, buf, len); XmbDrawImageString(disp, window, font, gc, x_loc, y_loc, buf, len);
に置き換えます. フォント指定が新しく付け加えるべき引数になりますが,XDrawString の直前部分を見れば,指定すべきフォントはまず分かると思います.
この部分は
#define XDrawString(d,w,FONT,gc,x,y,s,l) XmbDrawString(d,w,FONT,gc,x,y,s,l) #define XDrawImageString(d,w,FONT,gc,x,y,s,l) XmbDrawImageString(d,w,FONT,gc,x,y,s,l)
を定義し,
#define FONT fontset XDrawString(disp, window, gc, x_loc, y_loc, buf, len); #undef FONT
とする方法もあります. 書き換える元のコードを見て,使いやすいほうを適用すると良いでしょう.
OS によっては(例えば,昔の Linux) OS 組み込みの setlocale が使いものにならないことがあるかもしれません. そういう場合はコンパイル時に X のロカールを使います.
この場合は Makefile を修正して,
ようにします. また,これに合わせてソースの方も 「#include <locale.h>」を「#include <X11/Xlocale.h>」に置き換えます.
ただし,こういった場合も,Imakefile から Makefile を作るようになっていれば,-DX_LOCALE は自動的に CFLAGS に設定されるはずです(ロカールが動かない OS の X は,そうなるようにビルドされていると思います). したがって,X アプリなのに Imakefile を使っていない一部のウィンドウマネージャ(wm2等)以外の場合には,特に気にしなくても大丈夫でしょう.
ほとんどのウィンドウマネージャは,設定ファイルでメニューをカスタマイズできることができます. ですから,メニュー文字列に日本語を使いたい場合には,設定ファイルの処理の部分でも日本語が通るかどうか注意しなければなりません.
特に,多くのウィンドウマネージャでは,定義ファイルの処理には lex, yacc を使っていますので,ここで日本語が通るかどうかには注意を払う必要があります. 厳密な意味で I18N ではなくなるのですが,ここでは以下の点を決め打ちにしてしまうのが楽でしょう.
3番目についてですが,GNU flex, bison(lex, yacc と同等のソフトウェア)は 8bit 文字列を問題無く通しますので,OS 付属の lex, yacc の振舞いが不明の場合にはこれらを使うと良いでしょう.
また,ウィンドウマネージャによってはソース中でメニューを定義しているものがあります(wm2等). gcc の場合,ソースコードの漢字コードが EUC ならば文字列リテラル中に使ってもコンパイルを通すことができますので,それに依存するのがてっとり早いと思います.
以上の方法で I18N したウィンドウマネージャを実行する際に注意する点はただ一つ,フォント指定の部分だけです. この方法では,設定ファイルの処理部分は元のままで,フォント処理の部分を差し替えるので,基本的にはフォント名の指定をフォントセットの指定に差し替えるだけです.
具体的には(twm の場合),
TitleFont "-*-helvetica-bold-o-normal--*-120-*-*-*-*-iso8859-1" MenuFont "-*-helvetica-bold-o-normal--*-120-*-*-*-*-iso8859-1"
のような記述を
TitleFont "-*-helvetica-bold-o-normal--*-120-*-*-*-*-iso8859-1,-mnkaname-*-medium-r-normal--12-*-*-*-*-*-jisx0208.1983-0" MenuFont "-*-helvetica-bold-o-normal--*-120-*-*-*-*-iso8859-1,-mnkaname-*-medium-r-normal--12-*-*-*-*-*-jisx0208.1983-0"
のように書き換えます. (もしかすると,もっと多くのエンコーディングについてのフォントを指定しなければならないかもしれません.)
このようにして作成したパッチの例を置いておきます. obsolete なものもありますが,少しでも参考になれば幸いです.