fcitx的架构比较简单,输入法的宿主进程称为输入法的客户端,输入法框架从客户端接受按键消息,然后对按键消息进行处理最后向客户端输出一个处理后的字符串。
fcitx处理键盘事件分为四个阶段:PreInput,DoInput,PostInput和处理热键。我们的输入法在DoInput这个阶段被调用。
fcitx的插件被分为四个类别:Frontend,input Method,Module,和User Interface,Frontend负责从客户端程序接收按键消息,然后将消息转发给fcitx框架,Input Method负责将按键消息转换成对应的语言字符串,Modules负责通过注册键盘钩子处理对应的事件,User interface 负责在屏幕上显示对应的元素,也就是对应的皮肤。
Frontend模块Frontend库负责与客户端程序进行交互,接收客户端发送过来的按键消息,并将处理后的字符串发送给客户端程序。
现在的在Frontend需要实现13个函数分别如下所示:
//创建新的Frontendvoid* (*Create)(struct _FcitxInstance*, int frontendindex);//销毁Frontendboolean (*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);//更新用户侧边的UIvoid (*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通信可以用来实现这种方式。
普通模块ModuleEvent 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来进行定义。
typedef struct _FcitxUIStatus {char name[MAX_STATUS_NAME + 1];char shortDescription[MAX_STATUS_SDESC + 1];char longDescription[MAX_STATUS_LDESC + 1];void (*toggleStatus)(void *arg);boolean (*getCurrentStatus)(void *arg);void *priv;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 TypeDescription=Description StringDefaultValue=value[DescriptionFile]LocaleDomain=Domain
类型可以是
"File", "Font", "Enum", "String", "I18NString", "Boolean", "Color", "Integer".
DefaultValue是可选的,但是如果配置文件需要自动生成的话,最好添加缺省的值。
所有的类型都会在配置工具中以响应的形式显示,Boolean显示成CheckBox,字符串显示成文本编辑框。