Linux应用从输入法获取中文(或者其他文字,这里就以中文代替了)的方法有多种。一种最原生的方法是XIM,但最常用的方法是Qt IM Module和Gtk IM Module。
XIM是X protocol定义的X应用从输入法获取中文的方法。它的大致流程分两步:
- 从系统获取键盘事件,得到一个key event
- 调用Xutf8LookupString,把key event传给输入法,从输入法获取转换后的中文
不是每次调用Xutf8LookupString都有中文吐回来的.
Xutf8LookupString的背后是基于DBus的进程间通信。具体在实现输入法的章节介绍。
一个简单的X应用获取输入法的代码如下:
https://github.com/zhangfuwen/x11-examples/blob/master/input_methods/fakeapp_main.c
这是Qt框架提供的标准输入法方法。Qt也支持从XIM的方式获取中文,但Qt5开始不再支持XIM。这里抛开XIM不提,只说Qt IM Module。
它实际是Qt的一个plugin。这个plugin是Qt开发的专用概念,具体的输入法,我们应该实现一个qt input context plugin。
关于这种plugin的描述:QInputContextPlugin Class Reference
关于这种plugin的实现方法博客:https://www.kdab.com/qt-input-method-virtual-keyboard/
关于这种plugin的一个示例代码:GitHub - michitux/qt-im-module-onboard: Qt Input Method for Onboard
它的基本原理就是Qt应用在启动时,根据 QT_IM_MODULE
环境变量得到一个输入法名字,然后Qt就会去 LD_LIBRARY_PATH
中寻到提供这个名字的plugin的so,然后加载它。这个so库里提供了一个工厂方法,调用工厂方法可以创建一个QInputMethod类的实例,这个类就包含了输入法的各种操作。应用程序需要输入中文时,就会调用QInputMethod的方法来获取文字。
QInputMethod的定义如下:(QInputMethod Class)
输入法的开发者在实现这个类的时候,可以直接查表返回对应中文,也可以通过进程间通信的方式从另一个进程获取中文.通常大家都是通过dbus做进程间通信,从而获取中文的.
这里具体实现不谈,主要想阐述的是,输入法开发者可以开发一个qt plugin,然后丢到linux系统的 LD_LIBRARY_PATH
下,再export一下 QT_IM_MODULE
环境变量,从而让QT开发的所有应用都使用你的这个输入法。
SCIM输入法就采用了这种方式:https://github.com/heroxbd/scim/commit/a99ca5e3e13fd8d278841ebd627d5be4d30956f6
Gtk与Qt类似,也有InputContext和InputMethod的类。
Gtk的im module统一放在 /usr/lib/x86_64-linux-gnu/gtk-4.0/4.0.0/immodules
。
➜ immodules pwd
/usr/lib/x86_64-linux-gnu/gtk-4.0/4.0.0/immodules
➜ immodules ls
giomodule.cache libim-fcitx5.so libim-ibus.so
➜ immodules readelf -sW libim-ibus.so | grep gtk_im
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_simple_add_table
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_filter_keypress
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_cursor_location
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_filter_key
30: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_simple_new
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_get_type
63: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_reset
66: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_focus_out
83: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_use_preedit
103: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_surrounding_with_selection
108: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_simple_get_type
115: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_focus_in
118: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_client_widget
➜ immodules readelf -sW libim-fcitx5.so | grep gtk_im
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_filter_keypress
29: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_reset
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_focus_out
65: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_get_preedit_string
74: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_cursor_location
96: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_use_preedit
104: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_focus_in
117: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_get_type
118: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_set_surrounding
127: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gtk_im_context_simple_new
这个仓提供了fcitx5的gtk-im-modules的原代码,里面包括gtk-2, gtk-3, gtk-4的版本:
https://github.com/fcitx/fcitx5-gtk
这个网页则提供了一些编码上的要点:Input methods in GTK+ 4
可以看到linux系统下输入法还是比较混乱的,Gtk和Qt两个著名的开放框架各自有自己的输入法框架,还有一些其他的开发框架如SDDL, GLFW等,也需要输入法,它们或是自己也定义了一个框架,或者使用XIM接口。
这种混乱给输入法的实现带来了很多的困难,fcitx输入法需要实现各种不同的frontend:
https://github.com/fcitx/fcitx5/tree/master/src/frontend
这种混乱给应用或应用开发框架也带来了困难,因为应用不知道用户使用什么输入法框架(fcitx还是ibus,还是其他),又使用哪种输入法接口(xim, qt im module还是gtk im module)接入.
这种互相间的不确定性,导致有时候需要采用一些手段,才能让输入法好用。
Gtk和Qt支持本身的im module接口,但是它们几乎都提供了XIM的桥接模块,所以用户如果设置输入法为XIM,几乎总能工作。
QT_IM_MODULE=xim
GTK_IM_MODULE=xim
XMODIFIERS=@im=xim
这样的配置的前提是ibus, fcitx等输入法几乎都实现了xim前端.
fcitx5也实现了ibus的接口,所以可以作为一个伪装的ibus daemon,提供ibus输入法服务。
Fcitx 5 开发(一)一个按键事件的前半生:由程序到输入法
看这个吧。
实现一个输入法有多种方法,可以实现为一个ibus或fcitx等级的输入法,也可以实现为ibus或fcitx的一个engine。
实现为ibus或fcitx本身难度较大,原理可以参考上一节[输入法前端]。
实现为ibus engine我这里有一个示例代码可以参考:
https://github.com/zhangfuwen/x11-examples/tree/master/input_methods/fake_ime
这个代码在用户输入空格的时候就会返回四个固定的汉字,代码过于简单,就不多介绍了.
我没有实现过fcitx engine,网上请教了fcitx5的开发者wengxt, 他给了一些参考资料:
注册附加组件 https://codedocs.xyz/fcitx/fcitx5/classfcitx_1_1AddonInstance.html engine https://codedocs.xyz/fcitx/fcitx5/classfcitx_1_1InputMethodEngine.html 也没更好的文档,大概也得参考已有的实现
https://github.com/fcitx/fcitx5/discussions/364
至此,关于输入法相关的疑问我基本都有了一个答案,暂时没有新问题就不进一步研究了,欢迎有问题的同学提出问题,大家一起研究.