fcitx的架构比较简单,输入法的宿主进程称为输入法的客户端,输入法框架从客户端接受按键消息,然后对按键消息进行处理最后向客户端输出一个处理后的字符串。
fcitx处理键盘事件分为四个阶段:PreInput,DoInput,PostInput和处理热键。我们的输入法在DoInput这个阶段被调用。
fcitx的插件被分为四个类别:Frontend,input Method,Module,和User Interface,Frontend负责从客户端程序接收按键消息,然后将消息转发给fcitx框架,Input Method负责将按键消息转换成对应的语言字符串,Modules负责通过注册键盘钩子处理对应的事件,User interface 负责在屏幕上显示对应的元素,也就是对应的皮肤。
Frontend模块
Frontend库负责与客户端程序进行交互,接收客户端发送过来的按键消息,并将处理后的字符串发送给客户端程序。
现在的在Frontend需要实现13个函数分别如下所示:
//创建新的Frontend
void* (*Create)(struct _FcitxInstance*, int frontendindex);
//销毁Frontend
boolean (*Destroy)(void *arg);
//创建新的输入上下文
void (*CreateIC)(void* arg, FcitxInputContext*, void* priv);
//Frontend通过私有值的回调函数检查输入法上下文
boolean (*CheckIC)(void* arg, FcitxInputContext* arg1, void* arg2);
//销毁输入法上下文
void (*DestroyIC) (void* arg, FcitxInputContext *context);
//frontend激活输入法对客户端的回调
void (*EnableIM)(void* arg, FcitxInputContext* arg1);
//frontedn关闭输入法对客户端的回调
void (*CloseIM)(void* arg, FcitxInputContext* arg1);
//frontend 提交字符串的回调
void (*CommitString)(void* arg, FcitxInputContext* arg1, char* arg2);
//frontend 后续的回调函数
void (*ForwardKey)(void* arg, FcitxInputContext* arg1,
FcitxKeyEventType event, FcitxKeySym sym, unsigned int state);
//设置窗口偏移的回调函数
void (*SetWindowOffset)(void* arg, FcitxInputContext* ic, int x, int y);
//获得窗口的位置
void (*GetWindowPosition)(void* arg, FcitxInputContext* ic, int* x, int* y);
//更新预先屏的字符串的回调
void (*UpdatePreedit)(void* arg, FcitxInputContext* ic);
//更新用户侧边的UI
void (*UpdateClientSideUI)(void* arg, FcitxInputContext* ic);
Frontend需要管理自己的输入上下文,一个客户端至少有一个包含私有数据的输入上下文。我们可以通过CreateIC和DestroyIC来创建和销毁输入上下文的数据。输入上下文通常包含有一个私有的唯一ID,通过这个ID我们可以来查找特定的输入上下文。
一些客户端的输入上下文含有状态值,通过状态值我们可以判断输入上下文是否被激活了。fcitx可以通过EnableIM和CloseIM接口来激活或者关闭某个输入山下文。
当fcitx想向客户端窗口提交字符串或者发送按键消息的时候,可以调用CommitString和
ForwardKey. SetWindowOffset和GetWindowOffset用来设置和获取客户端的光标位置。
当输入上下文支持CAPACITY_PREEDIT 和 CAPACITY_CLIENT_SIDE_UI属性的时候,在选定的输入字符串变化的时候或者UI元素更新的时候我们可以通过UpdatePreedit和UpdateClientSideUI回调函数来更新对应的内容。
InputMethod模块
输入法模块是Fcitx中最重要的模块,它会处理按键消息并更新输入上下文和候选信息。每一个Input Method插件可以注册一个或者多个输入法。
当切换到对应的输入法的时候,初始化函数会被调用,ResetIM用来重置输入法。DoInput函数用来处理按键消息,如果返回值状态是IRV_DISPLAY_CANDWORDS,GetCandidates会被调用。
Priority优先级的值控制着输入法的优先级,每一个输入法自己掌控自己的优先级,如果一个输入法的值小于或者等于0输入法会注册失败。
Fcitx自从4.1.0版本之后,修改了候选词的处理逻辑,每个输入法模块应该提供完整的候选词,而不是单独的一页候选词。翻页函数通过fcitx来实现而不是每个输入法模块。
对于大多数CJK输入法,它们不需要实现单独的标点功能,因为已经有一个共享标点实现。如果输入法需要类似的功能,则不应在输入法中实现,而应在单独的模块中实现。
有些人可能认为将输入法做到一个独立的进程中,比做到共享库中的更好一些,但是目前fcitx只支持共享库的这种模式。所以如果想做成独立的进程,没有简单的方法,不过dbus通信可以用来实现这种方式。
普通模块Module
Event Module
Fcitx中的Event Module通过使用fd来通知消息主循环是否有新的消息。
typedef struct _FcitxModule
{
//模块的创建主函数
void* (*Create)(struct _FcitxInstance* instance);
//设置消息主循环的fd
void (*SetFD)(void*);
//处理主循环中的消息
void (*ProcessEvent)(void*);
//模块的析构函数
void (*Destroy)(void*);
//重新加载配置
void (*ReloadConfig)(void*);
} FcitxModule;
从多个fd中选择后,将调用ProcessEvent,模块将处理自己的事件。
模块需要在FcitxInstance中更新rfds、wfds、efds,并在FcitxInstance中设置maxfd成员.。模块处理完所有事件后,所有三个fd都会通过SetFD函数设置成FD_ZERO。
除基于事件的模块外,所有其他misc模块都将使用内置钩子来干扰关键事件处理。
目前,有以下可用的钩子来干扰关键事件处理。
//注册重置输入过程中调用的钩子
void FcitxInstanceRegisterResetInputHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册输入法激活的时候的钩子
void FcitxInstanceRegisterTriggerOnHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册输入法失效的时候的钩子
void FcitxInstanceRegisterTriggerOffHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册获得输入焦点的时候的钩子
void FcitxInstanceRegisterInputFocusHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册失去输入焦点的时候的钩子
void FcitxInstanceRegisterInputUnFocusHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册输入法切换的时候的钩子
void FcitxInstanceRegisterIMChangedHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册输入法更新候选词的时候的钩子
void FcitxInstanceRegisterUpdateCandidateWordHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//fcitx注册更新输入法列表的时候的钩子
void FcitxInstanceRegisterUpdateIMListHook(struct _FcitxInstance* instance, FcitxIMEventHook hook);
//注册输入状态变化的时候的钩子
void FcitxInstanceRegisterICStateChangedHook(struct _FcitxInstance* instance, FcitxICEventHook hook);
//注册UI状态变化的时候的钩子
void FcitxInstanceRegisterUIStatusChangedHook(struct _FcitxInstance* instance, FcitxUIStatusHook hook);
//注册钩子的调用方法
FcitxIMEventHook imchangehook; //fcitx输入法事件的钩子
imchangehook.arg = imInstance; //回调函数的参数一般是指针
imchangehook.func = IMeChangeCallback; //输入法切换的回调函数
//注册输入法切换的回调钩子函数
FcitxInstanceRegisterIMChangedHook(fcitxInstance, imchangehook);
按键事件处理分为4个阶段。在输入法的DoInput函数之前调用PreInput。如果按键事件还没有得到处理,PostInput将在输入法的DoInput函数之后调用。热键最后被处理,但即使输入上下文状态为ENG,它也不会被阻止。这是预输入和后输入的主要区别。如果一个插件只有一个需要切换的状态,它应该使用热键而不是输入过滤器。
User Interface
和Frontend、InputMethod、Module类型的模块不一样,User Interface模块只允许有一个运行。如果没有特殊需求,不建议实现一个新的User Interface模块,Fcitx中已经存在了三个用户皮肤插件,分别是fcitx-classic-ui、fcitx-kimpanel-ui(基于dbus)以及fcitx-light-ui。
如果要实现一个自定义的User interface模块的话,最好使用一个和kimpanel兼容的协议,而不是写一个fcitx协议。
Fcitx对用户交互窗口的元素进行了抽象,一般来说,一个输入窗口分为四个部分
User Interface中的一个元素被称为Status,它定义了一个按键,输入法的图标也可以通过User Interface来进行定义。
/**
* @brief Fcitx Status icon to be displayed on the UI
**/
typedef struct _FcitxUIStatus {
/**
* @brief status name, will not displayed on the UI.
**/
char name[MAX_STATUS_NAME + 1];
/**
* @brief short desription for this status, can be displayed on the UI
**/
char shortDescription[MAX_STATUS_SDESC + 1];
/**
* @brief long description for this status, can be displayed on the UI
**/
char longDescription[MAX_STATUS_LDESC + 1];
/**
* @brief toogle function
**/
void (*toggleStatus)(void *arg);
/**
* @brief get current value function
**/
boolean (*getCurrentStatus)(void *arg);
/**
* @brief private data for the UI implementation
**/
void *priv;
/**
* @brief extra argument for tooglefunction
**/
void* arg;
} FcitxUIStatus;
还有一个User Interface元素是Menu,Menu元素现在只有fcitx-classic-ui模块和fcitx-light-ui模块支持,由于kimpanel的限制,现在kimpanel不支持fcitx风格的菜单。
如果模块需要注册菜单或者状态图标的话,可以通过RegisterStatus和RegisterMenu来进行注册。如果一个User Interface模块需要支持状态图标和菜单的话,应该实现UpdateStatus,RegisterStatus和RegisterMenu三个回调函数。
Fcitx的文件和目录
Fcitx的文件放在两个目录下,一个是系统目录一个是用户目录
系统目录: /user/share/fcitx
用户目录:~/.config/fcitx
为了能让fcitx的配置工具自动找到fcitx的插件和配置文件,我们需要将对应的文件正确命名并放置在对应的目录下。
每个插件都有一个配置文件,该文件被安装在/usr/share/fcitx/addon目录下,这个文件定义了插件的名称、类别、库名称、类型和优先级。插件的配置文件名称应该为:addon-name.conf。
如果插件需要被各种配置项进行配置,它需要一个配置描述文件,该文件应该被安装在
/usr/share/fcitx/configdesc/目录下,文件名称应该为[addon-name].desc。相应的配置文件应该被防止在/usr/share/fcitx/conf/目录下,文件名称应该为[addon-name].config
如果只有一个数据文件的话可以放置在/usr/share/fcitx/data目录下,如果有多个文件的话最好放置在一个单独的目录下。
配置文件的描述文件的格式如下:
[GroupName/OptionName]
Type=Option Type
Description=Description String
DefaultValue=value
[DescriptionFile]
LocaleDomain=Domain
类型可以是
"File", "Font", "Enum", "String", "I18NString", "Boolean", "Color", "Integer".
DefaultValue是可选的,但是如果配置文件需要自动生成的话,最好添加缺省的值。
所有的类型都会在配置工具中以响应的形式显示,Boolean显示成CheckBox,字符串显示成文本编辑框。