当按键被 Fcitx 的前端接收到之后,首先它需要找到对应的输入上下文,然后创建对应的按键事件结构:
首先对于一个按键来说,我们先要区分和按键相关的好几个不同的值的含义:Code,State,Symbol。Code 表达的是这个按键对应的物理按键,例如键盘上的 A,不论你在什么情况下按 A(Caps Lock / Shift),产生的 Code 是一样的。State,表示了按键发生时键盘的状态,包括了锁定键的状态,比如 Num Lock 和 Caps Lock,还有各种修饰键(Modifier)的状态是否被按下。当然,对于 32 个 Bit 来说,键盘并没有那么多的按键,因此有一些位在 Fcitx 中也被挪作他用,例如 https://github.com/fcitx/fcitx5/blob/c492be884540b55ee590d0e3312c4889c8bf4300/src/lib/fcitx-utils/keysym.h#L51 这个就是新加入的按键是否是重复键的。
Symbol 则是表达了经过键盘布局转换之后得到的符号,一般来说,输入法的逻辑是直接和 Symbol 打交道的,而不关心从 Code 到 Symbol 之间的转换。Fcitx 的 Symbol 对应的数值是采用和 X11 Symbol 一样的值来简化判断,这样在大部分情况下都不需要重新进行映射。
当创建好对应的 KeyEvent 结构之后,就会把他放到整个 Fcitx 的事件处理当中。这里的事件仅表示和 Fcitx 主体逻辑相关的事件,都是抽象的和 Fcitx 自身逻辑相关的(例如界面是否需要更新 / 发生了按键 / 提交文本),不可和 Fcitx 的事件循环(仅和 IO 相关)混为一谈。
具体来说,则总是通过 Instance 类的这个函数来处理一个新的事件 https://github.com/fcitx/fcitx5/blob/c492be884540b55ee590d0e3312c4889c8bf4300/src/lib/fcitx/instance.h#L66
里面具体处理分为三个阶段,因为输入法是核心,所以事件处理的顺序是围绕着 Engine 构建的。总共分为“在输入法之前,输入法,在输入法之后”三个阶段。当然,内部还有保留的 ReservedFirst 和 ReservedLast 用于保证某些特别的事件会总在最初或者最后被执行,一般用户不能使用这两个阶段。
需要关注某些事件的模块,可以通过 https://github.com/fcitx/fcitx5/blob/c492be884540b55ee590d0e3312c4889c8bf4300/src/lib/fcitx/instance.h#L70 来注册回调函数,这样就会在对应的阶段被调用。
所有的事件都有两个属性:accepted 和 filtered。accepted 控制的是之前 postEvent 的返回值,而 filtered 则控制了事件的处理流程,当一个事件被 filter 之后,它之后的回调函数则都不会被调用。通常这两个是同时设置的,因此有一个 filterAndAccept 来同时进行这两个操作。
这里事件里面,按键事件是最特别的一种,只有它可以被 filter,其他大部分事件则都不可能被 filter。这主要是为了保证逻辑上这些事件处理的一致性,而避免导致问题。
其中,Fcitx 自身的逻辑,也都是构建在这些 API 之上的。例如切换输入法的按键等。当按键一个个经过不同的 handler 处理之后,得到的结果将根据不同的 frontend 返回给不同的程序。对于按键这个事件本身而言,也只有两种结果,处理或者不处理。从接口的角度来说,实际上并不那么需要设计成处理 / 不处理,而只需要在不处理的时候生成一个按键事件给程序就可以了。只是在实践中,不同的 Toolkit 对于生成事件的态度差别太大,因此维持原始事件的数据结构的状态是一个保证许多功能正常工作的方式。因此返回的结果也才变成了“处理/不处理”这样的情况。
在 Fcitx 5 中,系统的键盘布局和分组是绑定的,如果在分组中使用其他的布局的话,会中间再经过一个只对 Fcitx 自身可见的转换。这也就是为什么 KeyEvent 中有三个不同的按键结果:
1、OrigKey:来自原始的系统键盘转换的结果
2、RawKey:经过 Fcitx 内部布局转换的结果
3、Key:Key 经过 Normalize 之后的结果。当然,这个 Normalize 并没有什么神秘的,而是方便输入法实现自己的功能。例如,把按键的 Modifier 过滤为只有几个常见组合键的状态(去掉 CapsLock / NumLock 等)。对于输出字符的按键,则去掉 Shift(避免 CapsLock 下的 A 和 Shift + a 判断需要不同的代码),简单来说就是方便输入法实现的小功能。
输入法或者其他模块可以根据自身逻辑处理按键,并根据需要更新界面信息。和 Fcitx 4 不同的是,每个输入上下文的状态是独立的,其中也包括了界面相关的信息。这在 Fcitx 4 中则是全局共享的。这也主要是为了支持在多个显示服务器下可以采用多个焦点,不互相冲突。
界面根据对应输入上下文的显示服务器,则会选择对应的界面进行显示。例如 Fcitx 5 可以支持同时连接不同的 X 服务器,界面就会根据具体的 X 服务器在对应的服务器上进行渲染。(主要指 classicui 的实现支持这个功能)。