はじめに
プログラミングをしているとLinuxの日本語入力には一貫性がないことに気付きます、Mozc、Fcitx、Gtk、Qtなどの関係や呪文のような環境変数に悩まされます。
今回、サンプルプログラムを作成することで日本語入力の流れを追っていきたいと思います。
使用OS:Debian11(bullseye)
ユーザーはブラウザでSNSに日本語メッセージを入力したり、オフィースソフトのワープロを使って日本語文章を書いたりします、これらアプリ層のソフトウェアを作成するプログラマは、大抵はツールキット層の機能を使って日本語入力のプログラムを書きます。なぜなら、直接インプットメソッド層の機能を使うより、保守性に優れているからです、インプットメソッドが切り替えられたときのことを考えなくてもよくなるからです。
ツールキット別サンプルプログラムの作成
それでは、それぞれのツールキットを使ったサンプルプログラムを作成していきます。
Gtk2を使ったサンプルプログラム
古くからあるツールキットで、いまだにGtk2を使ってるソフトウェアも多く、インターネット上にサンプルプログラムが多数見つかります。
パッケージのインストール
sudo apt install libgtk2.0-dev
ソースコード
#include <gtk/gtk.h>
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *align;
GtkWidget *view;
GtkTextBuffer *buffer;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "example_gtk");
gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
gtk_container_set_border_width(GTK_CONTAINER(window), 15);
view = gtk_text_view_new ();
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW (view));
gtk_text_buffer_set_text (buffer, "テキスト", -1);
align = gtk_alignment_new(0, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(align), view);
gtk_container_add(GTK_CONTAINER(window), align);
gtk_widget_show_all(window);
g_signal_connect(G_OBJECT(window), "destroy",
G_CALLBACK(gtk_main_quit), NULL); ○
gtk_main();
return 0;
}
コンパイル
gcc example_gtk.c -o example_gtk -w `pkg-config --cflags --libs gtk+-2.0`
実行
環境変数でIMが切り替えられることを確認するために、まず最初に、2つのIMを起動してみてください。方法は「Debian系で複数のIM(インプットメソッド)を立ち上げる」で紹介しています。
./example_gtk
ウィンドウが現れ"テキスト"の文字が表示されるはずです。この部分に漢字を入力してみてください。環境変数(GTK_IM_MOULE)で指定したIMで日本語入力が行われます。
次に、一度このサンプルアプリを終了し、export GTK_IM_MODULE=uim (もしくは=fcitx)と別のIMに変更後、再度、./example_gtkを実行して漢字を入力してみてください。操作性が変わっていることに気づくと思います。
Gtk4を使ったサンプルプログラム(未執筆)
Gtk4はGtk2の改良版です。Debian11ではGtk4には対応していないみたいなので割愛します。
Qt5を使ったサンプルプログラム
QtはGtkと双璧するツールキットです。
パッケージのインストール
sudo apt install qtbase5-dev
ソースコード
#include <QApplication>
#include <QGridLayout>
#include <QLineEdit>
#include <QWidget>
class Ledit : public QWidget
{
public:
Ledit(QWidget *parent = nullptr);
};
Ledit::Ledit(QWidget *parent) : QWidget(parent)
{
auto *edit = new QLineEdit(this);
auto *grid = new QGridLayout();
grid->addWidget(edit, 0, 0);
setLayout(grid);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Ledit window;
window.setWindowTitle("example_qt");
window.show();
return app.exec();
}
コンパイル
gcc example_qt.cpp -o example_qt -lstdc++ -fPIC `pkg-config --cflags --libs Qt5Widgets`
実行
./example_qt
Qtの環境変数はQT_IM_MODULEです。
(留意、QT_IM_MODULE=fcitxだとうまく日本語入力できたのですが、export QT_IM_MODULE=uimすると日本語入力ができなくなりました。GtkやXIMではうまく動作したことを考え、これ以上は当文章の範囲外と考え深く追求しません)
SDL2を使ったサンプルプログラム
GtkやQtは業務アプリなどGUIの形式が単調で良い場合に使われる一方、SDL2はそんな単調なGUIを必要としない場合に使われます。GtkやQtと比べプログラムサイズが小さく、依存するパッケージが少なくて済みます。
パッケージのインストール
sudo apt install libsdl2-dev
ソースコード
#include <SDL.h>
int main(int argc, char** argv)
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS);
SDL_Window* window = SDL_CreateWindow("example_sdl", 50, 50, 300, 200, SDL_WINDOW_OPENGL);
SDL_StartTextInput();
SDL_bool done = SDL_FALSE;
while (!done) {
SDL_Event event;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
done = SDL_TRUE;
break;
case SDL_TEXTEDITING:
printf("text edit: %s %d %d\n", event.edit.text, event.edit.start, event.edit.length);
break;
case SDL_TEXTINPUT:
printf("text input: %s\n", event.text.text);
break;
}
}
SDL_Delay(20);
}
SDL_StopTextInput();
SDL_Quit();
return 0;
}
コンパイル
gcc example_sdl.c -o example_sdl `pkg-config --cflags --libs sdl2`
実行
./example_sdl
ウィンドウの背景も変えていないので、とても分かりづらいですが新しいウィンドウが現れています。GtkやQtのようなウィジェットがないので入力した文字列はターミナルにprintf()で表示される作りです。
SDL2の環境変数について
SDL2の環境変数はSDL_IM_MODULEです。
export SDL_IM_MODULE=fcitxすることでfcitxでの日本語入力ができたのですが、少々風変わりだったのでソースコードを眺めてみました。
https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_ime.c の抜粋です。
static _SDL_IME_Init SDL_IME_Init_Real = NULL;
InitIME()
{
#ifdef HAVE_FCITX
const char *im_module = SDL_getenv("SDL_IM_MODULE");
const char *xmodifiers = SDL_getenv("XMODIFIERS");
#endif
/* See if fcitx IME support is being requested */
#ifdef HAVE_FCITX
if (!SDL_IME_Init_Real &&
((im_module && SDL_strcmp(im_module, "fcitx") == 0) ||
(!im_module && xmodifiers && SDL_strstr(xmodifiers, "@im=fcitx") != NULL))) {
SDL_IME_Init_Real = インスタンス; ※省略 /* IMをFcitxにする設定 */
}
#endif /* HAVE_FCITX */
/* default to IBus */
#ifdef HAVE_IBUS_IBUS_H
if (!SDL_IME_Init_Real) {
SDL_IME_Init_Real = インスタンス; ※省略 /* IMをIBusにする設定 */
}
#endif /* HAVE_IBUS_IBUS_H */
}
ソースコードを見ると、環境変数SDL_IM_MODULEがある場合は値が"fcitx"か確認しています。ない場合は、環境変数XMODIFIERSの値が"@im=fcitx"か確認しています。if文の条件がtrueになる場合、fcitxをIMにする処理をします。
if文がfalseになる場合は、IBusをIMにする処理をしています。
以上のことから、SDL2はfcitxもしくはIBusにしか対応していないことがわかります。
XIM(X11標準)を使ったプログラム
XIMは古くからある日本語入力方法でX11に最初から入っています。on-the-spotでの日本語入力ができないみたいで利便性に劣ります。互換性維持のために存在し、新しいアプリの開発には使われないと思います。
以前に投稿した「XIM(X Input Method)日本語入力の流れ」にサンプルプログラムがあります。また、環境変数についてより深く言及しています。
(XIMの環境変数はXMODIFIERSです。値に特徴(クセ)があり、先頭に"@im="を付ける必要があります(例、@im=fcitx, @im=uim))
なぜIMサーバとIMEに分かれているの?
この問に答えているサイトを見つけることができませんでした。思うにLinuxで日本語入力がしたいだけのユーザにとっては別れているメリットはないので、Linuxの日本語入力を考えたプログラマの都合であることは間違いないでしょう。
どのIMがいいの?
個人的にはfcitx-mozcが一番だと感じていますが、変換精度はけして良いとは思えません、誤変換ばっかりです。ただ安定して使えるからfcitx-mozcを使っています。
Mozc,libkkc,Anthyの違いについて触れているブログがあったのでリンクを貼っておきます。
「Fedora34でデフォルトIMEがAnthyになるですって!?? 〜OSSかな漢字変換再考〜」
用語について
IMEには異なる意味があり、インプットメソッドエディタ(IME)ならMS-IMEなどのWindowsでの用語だと思われます。LinuxのIMEはインプットメソッドエンジン(input method engine)だと思われます。当文章のIMEはインプットメソッドエンジンの意味で使っています。
当文章ではIMサーバとしましたが、ウェーブ上では同じ意味の用語にIMフレームワークを用いている場合が多いように思えます。また、別の言い方としてIMフロントエンドとしているウェーブサイトも見かけます。
インターネット上の説明を見ていると、上記の意味以外にも、当文章でIMとしている意味でIME(インプットメソッドエディタ)を使い、当文章でIMサーバーとしている意味でIMF(インプットメソッドフレームワーク)を使い、当文章でIMEとしている意味はIMFの内部機能と説明しているサイトもありました。
まとめ
アプリ層のプログラマは、自分が使いたいツールキット(ライブラリ)を選びます。実際に使われるIMサーバやIMEは、ユーザが環境変数とIMサーバの設定で自由に変更することができ、アプリ層のプログラマは気にする必要はありません。
書き終えてみると、当文章はツールキット(ライブラリ)の種類とその使い方。そしてツールキット(ライブラリ)別の環境変数についての説明が主となりました。