ウィンドウマネージャの国際化のやりかた

ウィンドウマネージャの国際化と言うとなんだかすごそうに聞こえますが,実はウィンドウマネージャは国際化がしやすいプログラムの最右翼です. それはなぜかと言うと,次のような理由によります.

X には最初から国際化機能が備わっている
X は標準で国際化機能を持っているので,これを使いさえすれば良いのです. (ただ,1バイト文字圏の人が書いたプログラムは,国際化機能を使っていないことが多いだけ….)
ウィンドウマネージャには複雑な文字列処理は必要ない
ウィンドウマネージャはせいぜい「クライアントからウィンドウ名やアイコン名をもらってきて,ウィンドウのタイトルバーに表示する」程度のことしかしません. いろいろな種類の文字コードを編集する Emacs みたいなエディタと比べれば,何もしないも同然ですよね?

そのため,ウィンドウマネージャの国際化は機械的な作業でやれます. 作業時間も慣れれば1つあたり1時間程度でしょう(もちろんウィンドウマネージャにもよります).

ただし,このページではまともな I18N であることより,お手軽さを優先させている点は御了承ください.

ウィンドウマネージャを日本語化する手順

おおまかな手順は以下のようになります.

  1. grep を使って書き換える場所の見当を付ける
  2. 国際化機能を有効にする(setlocale)
  3. ソースの書き換え
    1. ウィンドウ,アイコンの名前の取得
    2. フォント処理
  4. 作業の際の注意点
    1. ロカールの問題
    2. パーザジェネレータの問題
  5. 設定ファイルの記述

grep を使って書き換える場所の見当を付ける

書き換える場所はほぼ機械的に決められるので,予めソースに 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 で取得した値で置き換えることになります.

自前のフォント構造体を持っているウィンドウマネージャの場合

twm の場合を少し修正した例です.

修正前:

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 をそのまま使っているウィンドウマネージャの場合

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 に置き換えていなければなりません.)

文字列の描画

GC の設定

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 なものもありますが,少しでも参考になれば幸いです.


Back to the top page.
FUJIWARA Teruyoshi <tf@dsl.gr.jp>