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

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

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

引擎engine介绍

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

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

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

引擎类构造

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_in IBus(包括托盘图标)获取到焦点
focus_out IBus失去焦点
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_initinit的绑定,实现类似于对象与构造函数的效果,这两个函数中分别实现对IBusMyIMEEngineClassIBusMyIMEEngine内容的初始化。

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_initinit函数。一般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_initmy_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_initibus_object_class->destroy分别用于初始化和销毁IBusMyIMEEngine,初始化函数在用户切换进入当前输入法引擎时调用,销毁函数则在用户切换离开当前输入法引擎时调用,与初始化函数的调用一一对应。

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

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

以上现象是从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_textibus_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_initibus_myime_engine_init的重要入口。

相关文章

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

你可能感兴趣的:(C/C++,linux,ibus)