Waylandで日本語入力への道: 下調べ編
Kernel/VM Advent Calendarの何日目かの記事です. このカレンダーは btrfsやext4・XFSなどにも採用されている最先端のファイルシステム技術である delayed allocationを採用しています. したがって人々が本当に記事を書くぞ!となった時に, 随時 allocされるため, 非常に効率がよくなると言われています.
さて, Linuxデスクトップ元年と言われた今年2023年もそろそろ終わりそうです. 元年が終わるまでには片付けたいことがあります. そうwaylandへの移行です. 長年のXを片付けて, waylandで新しい気持ちで新年を迎えていきたいものです.
waylandへの移行にあたって問題になるのが日本語入力です. といっても, fcitx5を使えばだいたいいいわけですが…本ブログの読者のみなさまは高い漢字入力の意識をお持ちでしょうから, 自作のIMEが動いてくれないことには困ってしまうよ…ということがあるでしょう. そこで本記事と今後の記事では, fcitx5用にIME addonを実装していきます. Rustで.
Anthyの調査
fcitx5 addonを自作するにあたって, まずは既存のaddonを調査します. fcitx5-anthyを見てみましょう.
ファイル名を見て, このへんかなということで, src/engine.cpp を見ます. アドオンの定義はマクロでやりがちだろうなあと思いなが探すとこんなコードがあります.
fcitx5-anthy/src/engine.cpp at master · fcitx/fcitx5-anthy · GitHub
FCITX_ADDON_FACTORY(AnthyFactory)
これが以下のマクロで展開されて, こうなります.
extern "C" { FCITXCORE_EXPORT ::fcitx::AddonFactory *fcitx_addon_factory_instance() { static AnthyFactory factory; return &factory; } }
'fcitx::AddonFactory*' を返す fcitx_addon_factory_instance() という関数を作っておけば, fcitx5本体がこの関数を呼んでくれるという感じです.
AnthyFactoryはfcitx::AddonFactoryを継承していて, create()がoverrideされています.
class AnthyFactory : public fcitx::AddonFactory { fcitx::AddonInstance *create(fcitx::AddonManager *manager) override { fcitx::registerDomain("fcitx5-anthy", FCITX_INSTALL_LOCALEDIR); return new AnthyEngine(manager->instance()); } };
fcitx::AddonFactoryは, こんな感じの簡単なクラスです.
class FCITXCORE_EXPORT AddonFactory { public: virtual ~AddonFactory(); /** * Create a addon instance for given addon manager. * * This function is called by AddonManager * * @return a created addon instance. * * @see AddonManager */ virtual AddonInstance *create(AddonManager *manager) = 0; };
ということで, まずは fcitx::AddonFactoryを継承したクラスを作って, それを関数から返しましょう. Rustで.
bindgenの栄光と挫折
RustでC/C++とのffiをやるにあたっては, bindgenを使うとbindingを自動生成してくれるのでべんりです. fcitx4のときもこれを使ってRustでaddonを書きました. 今回も fcitx::AddonFactoryの構造をrust側にとりこむためにbindgenを使いましょう. きっとサクッといきますよ.
wrapper.hppにfcitxのヘッダをincludeするコードを書いて, とりあえず以下のようにbuild.rsを書きます.
use std::env; use std::path::PathBuf; use pkg_config; fn main() { println!("cargo:rustc-link-lib=Fcitx5Core"); println!("cargo:rustc-link-lib=Fcitx5Config"); println!("cargo:rustc-link-lib=Fcitx5Utils"); println!("cargo:rerun-if-changed=wrapper.hpp"); let fcitx5 = pkg_config::Config::new() .atleast_version("5.1.5") .probe("Fcitx5Core") .unwrap(); let builder = fcitx5 .include_paths .iter() .fold(bindgen::Builder::default(), |b, p| { b.clang_arg(format!("-I{}", p.to_str().unwrap())) }); let bindings = builder .header("wrapper.hpp") .allowlist_item("fcitx::AddonFactory") .opaque_type("std::.*") .vtable_generation(true) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); }
fcitx5のヘッダは/usr/include/Fcitx5/Coreの下などにあるので, pkg_config でそのパスをとってきて, コンパイラの引数を足しておきます. また, 生成対象を fcitx::AddonFactory にしぼり, "std::*"下の全ては透過的にします. こうすると, たとえば "pub type std_string = [u64; 4usize];" のようにサイズだけあわせるようになり, 中の詳細については生成されません. 逆にこうしないと, bindgenの生成が失敗しがちです.
これで, fcitx::AddonFactory がrust用に以下のように…
#[repr(C)] #[derive(Debug)] pub struct fcitx_AddonFactory { pub vtable_: *const fcitx_AddonFactory__bindgen_vtable, }
あ, はい vtableですね. fcitx::AddonFactory はvirtualな関数を持つので vtableを持ちます. Rustで「継承したクラス」を表現するには, このvtableを適切な関数で埋めてあげればいいわけですね. 幸い bindgenで ".vtable_generation(true)"にしているのでうめるべき関数の入った structが "fcitx_AddonFactory__bindgen_vtable" として生成されているはず…
が, このようなただのvoidになってんですねえ
#[repr(C)] pub struct fcitx_AddonFactory__bindgen_vtable(::std::os::raw::c_void);
なんでだろ〜というわけですが, 以下のコメントにあるように, bindgenは他のクラスを継承しているもの・virtualなデスクトラクタを持つものにはvtableを作らないという話です. まあこのへんプラットフォーム依存で難しいのでしょうがないとこですかね.
じゃあこれどうやって fcitx::AddonFactoryを作るねんとなるわけですが…
次回: Waylandで日本語入力への道: vtableを作ろう編
gentoo.hatenablog.com