ibus输入法开发记录:(二)引擎engine

    技术2025-08-18  13

    ibus输入法开发记录:(二)引擎engine

    引擎engine介绍引擎类构造引擎接入、初始化和销毁宏定义G_DEFINE_TYPE引擎注册:class_init引擎初始化init和销毁destroy引擎使用引擎接入 相关文章

    引擎engine介绍

    ibus的引擎(engine)是提供输入功能的核心。对于用户而言,一个engine就是一个可选择使用的输入法,如下图所示: 列表中安装的输入法实际上有英语、SunPinyin、Pinyin(和Bopomofo)三个组件(component),但总共有四个输入引擎。Pinyin和Bopomofo实际被包含在一个组件之中,即属于同一个可执行程序。

    将多个引擎打包到一个组件中可以根据需求增强引擎间一些共享资源的关联,但引擎engine才能够被用户认知为一个独立的输入法

    除了多引擎之外,另一种多输入法的实现方法可以参考ibus-rime的实现,它在一个组件可执行程序内仅实现一个输入法引擎engine,但可以通过键盘选择切换实现方案,这些方案作为不同的动态库,导出通用功能接口被engine所使用,方案内部处理具体的按键事件并返回处理结果,使一个输入引擎(engine)内能够通过自定义按键操作动态地切换拼音、五笔等输入方案

    引擎类构造

    GTK中实现了GObject对象系统,它使C语言的语法能够模拟C++的面向对象行为,使C语言中可以也做到例如类的继承、构造等操作,十分复杂和巧妙。构建ibus的自定义引擎,需要实现两个继承于“基类”的结构体(类):

    typedef struct _IBusMyIMEEngine IBusMyIMEEngine; typedef struct _IBusMyIMEEngineClass IBusMyIMEEngineClass; struct _IBusMyIMEEngine { IBusEngine parent; /* members */ IBusLookupTable *table; IBusPropList *props; MY_IME *ime; }; struct _IBusMyIMEEngineClass { IBusEngineClass parent; };

    IBusMyIMEEngineClass继承自IBusEngineClass,其内部实现ibus需要引擎自定义实现的输入接口,包括键盘事件、焦点变化、上下翻页、候选词点击选择、属性(状态栏)点击事件以及引擎销毁destroy等在该引擎中的具体行为。当相应的事件产生时,ibus将调用当前引擎的相应方法并将事件内容作为参数传递到方法内部,由我们的自定义实现对事件进行处理并将结果返回。IBusEngineClass中的常用接口包括如下:

    接口描述destroy引擎实例销毁process_key_event键盘事件处理focus_inIBus(包括托盘图标)获取到焦点focus_outIBus失去焦点page_up输入面板的“向上翻页”功能被选择page_down输入面板的“向下翻页”功能被选择property_activate属性(包括托盘下拉菜单和状态栏按钮)被选择set_cursor_location光标位置改变candidate_clicked输入面板的候选词被点击

    其他接口及参数详见ibus手册:https://ibus.github.io/docs/ibus-1.5/IBusEngine.html#IBusEngineClass

    IBusMyIMEEngine继承自IBusEngine,结构体内除了第一个成员必须为父亲IBusEngine,其余成员均可作为当前引擎的成员变量自由定义,如当前引擎的属性、候选查询表、使用的内核及当前状态等等。在事件产生、引擎接口被调用时,当前IBusMyIMEEngine的地址将作为参数被传回,我们可以自由访问其内部的成员变量。

    引擎接入、初始化和销毁

    宏定义G_DEFINE_TYPE

    GTK中通过宏定义G_DEFINE_TYPE实现GObject对象与其初始化函数class_init和init的绑定,实现类似于对象与构造函数的效果,这两个函数中分别实现对IBusMyIMEEngineClass和IBusMyIMEEngine内容的初始化。

    G_DEFINE_TYPE的调用如下:

    G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);

    该宏定义为自定义GObject对象自动生成注册到对象系统所必要的初始化函数,展开后代码大致所示:

    static void my_object_init(MyObject * self); static void my_object_class_init(MyObjectClass * klass); static gpointer my_object_parent_class = ((void *) 0); static gint MyObject_private_offset; static void my_object_class_intern_init(gpointer klass) { my_object_parent_class = g_type_class_peek_parent(klass); if (MyObject_private_offset != 0) g_type_class_adjust_private_offset(klass, &MyObject_private_offset); my_object_class_init((MyObjectClass *) klass); } __attribute__ ((__unused__)) static inline gpointer my_object_get_instance_private(const MyObject * self) { return (((gpointer) ((guint8 *) (self) + (glong) (MyObject_private_offset)))); } GType my_object_get_type(void) { static volatile gsize g_define_type_id__volatile = 0; if (g_once_init_enter(&g_define_type_id__volatile)) { GType g_define_type_id = g_type_register_static_simple( ((GType) ((20) << (2))), g_intern_static_string("MyObject"), sizeof(MyObjectClass), (GClassInitFunc) my_object_class_intern_init, sizeof(MyObject), (GInstanceInitFunc) my_object_init, (GTypeFlags) 0); } return g_define_type_id__volatile; };

    get_type函数中的内容较为复杂,因此这里描述时做了适当简化,它实为GObject对象实例化的接入口,提供了注册和管理用户自定义引擎对象类型的技术实现,使GObject对象系统能够调用到自定义对象的class_init和init函数。一般GTK程序还将导出get_type函数的定义,作为“构造函数”供其他模块调用,从而完成类的初始化:

    #define IBUS_TYPE_MYIME_ENGINE \ (ibus_myime_engine_get_type()) GType ibus_myime_engine_get_type(void);

    引擎注册:class_init

    在G_DEFINE_TYPE的宏定义展开中定义了my_object_init和my_object_class_init两个函数,对我们的输入法引擎实现,其被展开如下:

    G_DEFINE_TYPE(IBusMyIMEEngine, ibus_myime_engine, IBUS_TYPE_ENGINE);static void ibus_myime_engine_init(IBusMyIMEEngine* self); static void ibus_myime_engine_class_init(IBusMyIMEEngineClass * klass);

    因此在代码中,我们需要实现这两个函数。

    ibus_myime_engine_class_init用于初始化IBusMyIMEEngineClass,它在引擎类向ibus注册时被调用,在我们的输入法组件生命周期中仅调用一次。一般ibus_myime_engine_class_init的内部实现如下:

    static void ibus_myime_engine_class_init(IBusMyIMEEngineClass *klass) { INFO("ibus_myime_engine_class_init"); IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass); IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass); // 初始化引擎销毁接口 ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_myime_engine_destroy; // 初始化引擎其他必要接口 engine_class->process_key_event = ibus_myime_engine_process_key_event; engine_class->focus_in = ibus_myime_engine_focus_in; engine_class->focus_out = ibus_myime_engine_focus_out; ... }

    ibus_myime_engine_init用于初始化与我们实现的自定义输入法有关的数据结构,如IBusMyIMEEngine内部成员,或输入发使用的字词表。详见下一节的描述。

    引擎初始化init和销毁destroy

    ibus_myime_engine_init和ibus_object_class->destroy分别用于初始化和销毁IBusMyIMEEngine,初始化函数在用户切换进入当前输入法引擎时调用,销毁函数则在用户切换离开当前输入法引擎时调用,与初始化函数的调用一一对应。

    值得注意的是,具有对应关系的init和destroy函数参数的IBusMyIMEEngine地址是相同的,且destroy始终发生在init之后;然而,非对应关系的init和destroy之间的顺序关系是不可预知的。通过以下两种情形举例说明:

    用户在托盘菜单选择输入法A,然后选择输入法B。此时,进入A时会调用A_init,离开A时会调用A_destroy;进入B时会调用B_init,离开B时会调用B_destroy。A_init必定在A_destroy后且两者参数的engine地址一致;然而B_init则有可能发生在A_destroy前。用户当前已选择输入法A的情况下再次从托盘菜单点击选择输入法A。此时,ibus的逻辑会先销毁A(调用A_destroy)和再次创建A(调用A_init),但是新的A_init可能发生在A_destroy之前,如下图所示。此时两次调用中参数engine的地址不一致,A_destroy匹配的是前一次A_init。这对正常的ibus切换逻辑没有影响,但倘若我们直接简单地认为init就是创建destroy就是销毁,并在里面直接初始化和销毁一些非engine内部的公共资源,就可能会引起输入法的崩溃。

    以上现象是从ubuntu16.04,ibus1.5.11观察到的。除了情形2中的切换方法,在系统登录、ibus第一次启动时,引擎也可能发生情形2这样的行为。在我的场景中,为了导出IBusMyIMEEngine供外部使用,在init中为全局变量赋值并在destroy中置空,这一做法使输入法变得极不稳定。后续使用的实现方法将在后续博客中说明,若能有更好的、更稳定的实现方法,欢迎共同探讨。

    引擎使用

    当用户切换当前使用的ibus输入法时,ibus将会同步切换当前的engine_class,从而使事件能够正确传递到当前的引擎实现中。以process_key_event为例,ibus_myime_engine_process_key_event的实现可以如下:

    static gboolean ibus_myime_engine_process_key_event(IBusMyIMEEngine *engine, guint keyval, guint keycode, guint modifiers) { INFO("keyval=%d(%x), keycode=%d(%x), modifiers=%d(%x)\n", keyval, keyval, keycode, keycode, modifiers, modifiers); gboolean ret = engine->ime->process(keyval); return ret; }

    此外,ibus也提供ibus_engine_commit_text、ibus_engine_update_lookup_table等方法用于结果上屏或输入面板更新,详情可查询ibus手册,此处不再赘述

    引擎接入

    ibus引擎在组件初始化时通过ibus_factory_add_engine(factory, "myime", IBUS_TYPE_MYIME_ENGINE);向ibus完成添加,它将输入法引擎实现与定义于xml文件中的engine name相关联。第三个参数即为上文介绍的ibus_myime_engine_get_type,用于ibus获取初始化引擎的方法。这一方法也是ibus在初始化和用户切换输入法时调取ibus_myime_engine_class_init和ibus_myime_engine_init的重要入口。

    相关文章

    ibus输入法开发记录:(一)概览 ibus输入法开发记录:(二)引擎engine ←你在这里 ibus输入法开发记录:(三)属性菜单IBusProperty和配置IBusConfig

    Processed: 0.012, SQL: 9