The Road to Fcitx 5: 7 Get event order right

I didn’t expect this to be a thing that is only fixed recently. And the fix itself goes through a little bit back and forth. So I’d just like to share it since it is an interesting experience.

Between the communication of application and input method server. A common thing is application send a key event to input method and wait for the handling result of this event. Fcitx allows multiple different mode for it, including sync and async mode. By default it is async mode to avoid any block on the application side. But since we also support sync mode, we should keep two implementation have the same effect.

But in reality, we do behave the same under most circumstance, except when you bypass the key event and do some other thing at the same time. The reason behind this is actually easy to understand. Check the pseudo code below.

DBusCall call = inputContext->processKeyEvent();
call.waitForFinished();
DBusCall call = inputContext->processKeyEvent();
call.onFinished(processKeyEventCallback);

Imagine the input method issues commitString request at the same time. In the first sync case, the key event will be handled before the commitString, while the latter one will handle commitString before the key event.

If the key event is filtered, then there is no difference, but if the key is not filtered, then we will get two different result. For example, keyboard engine will show spell check within the preedit, but when you press space, the preedit will be commit to client and also the space key. You may say you can solve it by just combining the space key within the commit string request. Then what about a “left” key that doesn’t produce text? One way to solve it is to piggyback a rich result value instead of just a boolean value, but that would require more code change to existing interface. So in order to resolve this, we will force the input method to use the forward key interface to send the key back. Luckily, all event can be handled by the same way. The trick can be done within the last reserved event handler for key event.

Here’s the things changed with in the fcitx framework.

  1. Engine can safely assume any commitString is handled before the key event. If engine want it the other way, it can use forward key request itself. This keeps the default behavior for all input context.
  2. DBus based input context will block all the request sending the client until the request is returned. This ensures processKeyEvent is returned before the following request.
  3. If there is any pending request, mark processKeyEvent request as true, and use forwardKey to re-send the key event to the client.
  4. Within the input context code, tries to use the same data to forward key event if possible to avoid any conversion error.

Posted in fcitx development | Tagged , | 2 Comments

如何现在就在 Arch Linux 用上 Fcitx 5

一开始想了想要不要在标题写 Arch Linux,觉得还是必要的,因为目前只有 Arch Linux (和 Debian)出于套近乎的关系有了 Fcitx 5 全家桶。

1、需要安装的包

IM Module:fcitx5-gtk / fcitx5-qt

拼音和码表:fcitx5-chinese-addons

其他输入法可以参照 fcitx 4 的对应包名安装(例如 fcitx5-chewing 等)

配置工具:kcm-fcitx5(内含 fcitx5-config-qt 对于依赖纠结的人请去拍打 @felixonmars 拆包)

和 Fcitx 4 有一些文件冲突,不能同时安装。

2、环境变量和启动

和 Fcitx 4 一致即可,如果要多说几句的话那就是:

~/.xprofile (X11) 或者 ~/.bash_profile 或者 ~/.profile (SDDM 在 bash_profile 存在时只会读取 bash_profile)

export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx

目前暂时没有自带 XDG 的自动启动文件,需要自动启动的话暂时可以选择 cp /usr/share/applications/fcitx5.desktop ~/.config/autostart/

3、配置

目前没有对拼音和码表的数据的迁移工具,需要重新训练,但应该用起来已经不错了。拼音的 bug(如果有)应该会比码表少一些。

当然如果你使用了别的并没有改变库的其他输入法数据基本可以直接拿过来用。路径会有所不同,在 fcitx 5 中路径将变为更加标准的 ~/.local/share/fcitx5 ,配置文件还是主要存放在 ~/.config/fcitx5。

默认启动后并不会添加其他输入法,可以通过配置工具进行添加。

4、功能

关于键盘布局方面有较大区别,系统键盘布局现在和一个新的「分组」的概念绑定,同一个分组里的键盘布局是直接通过 xkbcommon 进行模拟,而不是采用修改系统键盘布局的方式。因此也不再有针对 ~/.Xmodmap 的支持,如果需要修改常见的设置(例如交换 Ctrl Esc)建议使用 xkb option 进行修改。

拼音相比原来的「自带拼音」有不少新增的功能,例如多词库,Emoji等,可以通过配置工具那边自行浏览。

Posted in fcitx development | Tagged , | 2 Comments

QML 的 Repeater 和 Loader 组合使用问题

Repeater,在对于需要生成多个 Item,但又不适合 ListView 的情况是很适用的。对于 Repeater 需要嵌套不同种类的 Delegate 的情况,有一些不同的解决方案。

例如,你针对不同的数据需要生成不同的 Item A 或者 Item B,那么一种选择就是把他们都作为子 Item 放到 Delegate 里面,然后选择性的进行 visible 的设置,来达到只现实一个的目的。这样带来的缺点也是显而易见的:

  • 需要真的实际创建多个并不使用的 Item
  • 对不同的 Item 设置值的时候,可能并不一定会有适合当前不显示的这个 Item 的值。例如,某项属性并不见于 Type B中,在实现的时候却不得不放进一个 dummy value 来避免程序错误。

而另一种选择就是,会选择 Repeater 嵌套一个 Loader ,用 loader 来动态的选择加载某个源文件,或者 Component,通过使用 Loader 的 source / setSource / sourceComponent 来实现。

但是如果你要进行多层次的嵌套,也即在 Loader 里面再套一个 Repeater,就会出现一个奇妙的现象。

举两个例子。

import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.10

GridLayout {
    columns: 2

    Repeater {
        model: [[1], [2], [3, 4, 5], [4], [5, 6]]
        delegate: Component {
            id: comp
            Repeater {
                model: modelData
                Label {
                    text: modelData
                }
            }
        }
    }
}

上面是一个不使用 Loader 的嵌套 Repeater,下面则是一个使用 Loader 的嵌套 Repeater。

import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.10

GridLayout {
    columns: 2

    Repeater {
        model: [[1], [2], [3, 4, 5], [4], [5, 6]]
        delegate: Loader {
            id: loader
            sourceComponent: comp
            Component {
                id: comp
                Repeater {
                    model: modelData
                    Label {
                        text: modelData
                    }
                }
           }
       }
    }
}

这两个代码最终的显示效果,是不一样的。前者可以正常显示表格,就仿佛内层的Repeater 的元素就是在 GridLayout 里一样。

而后者所有的元素都挤在了一起。

原因大概是因为使用了 Loader 的情形下,Loader 本身在层次关系中占有了一层,就像 一个普通的 Item。Loader 内部的 Repeater 并不能跨越 Loader 本身把内部的 Item 添加给 GridLayout。事实上,如果你在代码中把 Loader 直接改成 Item 就可以获得后图一样的效果,可以印证这一点。

只能说并没有一个好办法能够解决这样的问题,最好的解决办法,还是直接把 Model 扁平化只使用一层 Repeater 和 Loader 的组合。

Posted in Qt | Tagged | Leave a comment

The Road to Fcitx 5: 6. Addon Loader

Due to a frequently request about adding scripting support to Fcitx, I tried to make Fcitx 5 able to be extended in a different way.

During Fcitx 4 times, the addon must be written with Shared library and those assumptions are hard-coded. In Fcitx 5, I added an another abstraction called Addon Loader, which is responsible for the actual addon loading.

The builtin addon loader enables addon to be statically linked into Fcitx, which avoids the inconsistency of library and addon version, and it makes some important addon always available. And the shared library addon loader just loads the addon like the old days.

And starting from today, there can be a new addon type Lua to be used to extend Fcitx. In old Fcitx 4 days, lua addon is yet another shared library addon, instead of an “lua” typed addon. With the new addon loader mechanism, lua addon loader will do all the C++ coding work, and people can simply write pure lua addon and enable/disable them just like any other addons.

In order to add support of the Google Pinyin’s API back, fcitx5-lua also comes with a imeapi addon, which is purely lua (Though, I did wrote some code specifically to it to support all APIs, because the full Google Pinyin API contains some weird helper function like utf8 to utf16 conversion.).

So either, you can put a old days lua file to share/fcitx5/lua/imeapi/extensions , so imeapi will load it for you. Or you can write your own standalone lua Addon, by having a regular addon config file.

Right now, due to missing the real use case for lua addon, the API exposed by lua addon loader is quite limited to only covers imeapi’s use case. Documentation of existing API can be found at https://fcitx.github.io/fcitx5-lua/index.html. In the future, based on the use case we want to support more API will be added to it.

Right now, imeapi is designed to bridge the api to quickphrase and pinyin in chinese addons (register_trigger + candidate string will only works for pinyin).

Also, this lua addon loader enables to invoke any lua functions from C++ side. The arguments and return values are passed with RawConfig in Fcitx to avoid any ABI issues. In this case, Pinyin just simply invokes a function in imeapi to send over the candidates string. In old Fcitx 4 days, such thing are usually done by addon actively scan the candidate list, these kinds of style is not encouraged anymore in Fcitx 5. Instead, if certain functionality can be extended, it should be exposed by the addon itself (E.g. quickphrase now supports to be extended by other addons, and that’s how it works with luad addon lodaer),

As for extending addon loader, while there is no plan to add other addon type, code contribution are welcomed for things like Python, etc. Or even a “Type=DBus” addon that simply bridges all the call by dbus to another process.

Posted in fcitx development | Tagged , , | Leave a comment

用拼音输入 Emoji

从技术上来说,没有什么难的,就是挂一个额外的词库。唯一难的就是词库从哪来的问题。因为开源软件总是绕不开一个 License 的问题。

从哪来这件事,Unicode 的 CLDR 提供了一个数据来源,根据语言给不同的颜文字标注了各个语言的文字。所以 Emoji 到汉字的映射就是可以从这里来了。到这里,我们其实可以选择直接像实现笔画输入或者英文输入那样直接匹配插入,但是这样当然会带来一个问题,就是很难把输入的词汇频率和这种外部挂接的候选词组合起来。另一方面,也就无法让句子中混合输入 Emoji 和汉字。所以这里就必须把 Emoji 做成一个从拼音映射过去的词库。然而,如果简单的使用 CLDR 的标记的话,就势必会出现类似现在某个挂载了 Emoji 的 Rime 一样,一个词无数个 Emoji 候选。所以还需要小心的对这些数据进行过滤。所以这部分主要就是我手工来进行修改的,总之有以下几个原则。

1、单个 Emoji 不要对应太多不同的意思,避免“联想”含义的意思。
2、避免动词,例如有个🪓的Emoji,在数据里面还有“砍”这样的数据。
3、避免单字对应。出于拼音输入的特殊性,如果单字对应 Emoji 过多,可能会影响正常输入。
4、避免某个读音有大量的 Emoji 对应。
5、避免同一个Emoji的对应汉字互相包含。例如摩托和摩托车都对应了🏍,但是如果它们同时出现在数据中,对于用户来说是无法区分🏍是对应了摩托两个字还是摩托车这三个字的拼音,所以要避免这样的情况发生。

虽然这样有不小的手工工作量,但是为了输入效果应该来说是值得做的。

示例:

Posted in fcitx development | Tagged , | Leave a comment