去除 Fcitx 5 码表的 PUA 字符

首先来讲一些历史,在更加久远的 Fcitx 3 的时代,Fcitx 的码表文件是使用 gb18030 存储的。在 Fcitx 3 -> Fcitx 4 的过程中,所有的文件都被替换为了 UTF-8,但是受限于当时的 glibc,出现了很多转换的结果是 PUA 区的情况(Private Use Area)。从根本上来说,导致这个的原因是 Unicode 标准仍未收入这些文字,因此采用 PUA 存储这些文字的权宜之计。

Unicode 有一些历史文档记载这些,但是并不是所有我们遇见的字符都记载其中 https://www.unicode.org/L2/L2004/04161-hkscs-gb-pua.pdf

但是时过境迁,现在已经过去十几年了,因此这些 PUA 的字符也都获得了对应的码位。也就是说,是时候把他们重新拉出来更新了。

对于 fcitx5-chinese-addons 当中的码表(实际上是 libime 这个包),这倒不是一件难事。只需要把 fcitx3 的文件找回来,然后在新的 glibc 的系统上重新转换一次即可。为了验证当年的流程,我还特地安装了一个 debian 5 的系统重现当年的转换。

而这也确实证实了,当年确实是这样得到那些 PUA 的字符的。

而对于 fcitx5-table-extra 来说,这就有些难办了,他们当中的文件我们并没有原始的文件。这该怎么办呢?kingysu 发现 AR PL UMing HK 和 AR PL UMing CN 能够正确显示其中的大部分字符,而这正式取决于他们原始的文件的码是 BIG5-HKSCS,还是GB18030。

我们一开始的想法是全手动处理,一个个根据字形去查找对应的文字,据此我们也用了 https://www.qhanzi.com/index.zh-CN.html 一些手写中文识别的文字。还有用汉典 zdic 利用偏旁,仓颉,进行查询。

对于一些我们用以上方法都没找到的字符,我本来是想试试看用 fontforge 打开看看能不能直接搜索字形,然而我发现 fontforge 实际上对于 AR PL UMing 的字体可以显示他们实际引用的 unicode 的码。

而 Rocka 提示我们,可以直接用 python-fonttools 的 ttx 直接导出字体的信息。实际上这里导出 glyf 表之后,可以获得形如

的文件。uniXXXX.H 是 Big5 的 PUA,uniXXXX.C 是 GB18030 对应的 PUA。剩下的工作就是直接写一些只用一次的垃圾小脚本把这些信息提取出来即可。因为写 Python 脚本的突出一个糙就不展示了。

然后我们就得到了一个这样的映射,在 Uming 字体看看起来完全一致,但是实际上是不一样的 Unicode 哦。

如果用 Fcitx 的 Unicode 功能(复制然后Ctrl Shift Alt U)就可以容易地看出区别。

剩下就是把他们完全替换成正规的 Unicode 字符即可。

https://github.com/fcitx/fcitx5-table-extra/commit/0b3e04256b5b49c72a04e88da80bf34e5bba4b3f

Posted in fcitx development | Tagged , , , | 4 Comments

我和 Fcitx 的 14 年

人的一生能有多少个 14 年?

开端(2009)

commit 95f13144a52d409da387af4f1a25aa01bc6bb653
Author: Weng Xuetian <wengxt@gmail.com>
Date:   Sat Jan 16 06:17:12 2010 +0000

   Add Kimpanel Support

这可能是一切的开始。其实最早给 Fcitx 的贡献,就是 3.6.3rc 之后给 Fcitx 增加 Kimpanel 的支持。2008 年 KDE 4 刚刚发布,之后因为一些偶然的契机,发现有人为了 KDE 界面的集成,为 KDE 和 Fcitx(也包括scim 和 ibus)增加了一个基于 DBus 的面板协议。当时 Fcitx 的相关的代码因为 Fcitx 并没有一个插件架构,因此是保存在其中的一个分支当中的。

当年的这个时刻我竟然也写了博客记录,当我找到它的时候,它夹在两篇略羞耻恋爱感想的中间……

当我发现记录一切缘起这篇博客是在 102 页的时候我也是吃惊不小的。

Fcitx-dbus-svn ON AUR
Posted on October 25, 2009 by csslayer
由于我现在一直使用KDE桌面,某日曾经发现了个好东西:kimpanel
这个Plasmoid帮助Plasma和linux当中的输入法实现了一个统一的界面,目前linux下面流行的三个输入法fcitx, scim, ibus都由作者实现了一个后端。由于某些原因,Fcitx界面和程序是不分离的,因此只能用修改源码的方式使其支持kimpanel。kimpanel通过dbus和输入发进行交互活动,从而完成界面的统一。
fcitx-dbus的原作者貌似很久都没有维护了,因为最近fcitx修改了大量的bug,因此原有的fcitx-dbus还保留着这些bug……由于Archlinux的AUR上的fcitx-dbus-svn的包是我打的,因此我把我的port给放到AUR上了……原作者和各位用户不要伤心啊……?
貌似kimpanel还有一些问题,和fcitx的支持不是那么好,因此我(就算是为了我自己)也修改了一部分的bug……
跑去fcitx的issue发了个贴,暂时没人理我……又和fcitx-dbus的作者发了个信……也没理我……囧

总而言之,我就莫名地接手了这个项目的 fcitx 的部分,然后谁也不知道这埋下了一颗怎样的种子……关于我这一年的当时的生活……和我现在的老婆谈恋爱一年多了。大四刚刚开始的样子。

不断的维护和第一次大版本的飞跃(2010)

总的来说,这部分时间的提交历史我意外发现了一个历史悠久的噩梦,就是托盘图标。这部分如果展开讲的话,会涉及太多太多技术的细节,不过这时我回顾起来我才发现,我大概是真的不懂完全在盲人摸象一般修复那个托盘的透明问题。这个问题可能乃至到了 Fcitx 5 的时代仿佛鬼魅一般归来过……

但总之这也是给 Fcitx 开发代码的一个侧面,由于依赖的精简和 Toolkit 的缺位,很多基础的功能都是从更加底层缺少文档的协议实现起的。想象一下,在 Qt 中只需要 new 一下就可以创建的托盘图标,在 Fcitx 5 中却有 1.5k 的代码,实现了两种完全不同的方式…Toolkit 实在是替你负重前行了太多。

第一个大版本究竟是为了什么呢?现在的我可以说也是记忆模糊了。但那时我维护了一个内码是从 gbk 转到 utf8 的分支。在 3.6.4 的发布之后它也合并到了主干上。一方面,从这里的提交文件可以看出,这里为了当初 Fcitx 停止开发的其中两个原因「中文配置文件」和「用GB存储数据」以我个人的想法进行了修改……这里用描述型文件来生成界面的想法已经初具雏形……不过即使到了 Fcitx 5 的时代,Fcitx 3 的中文码表格式也依然可以兼容使用……但因为 dbus 强行要求 utf8 编码字符串,因此将整个内核转换成 utf8 其实也是一种势在必行的修改。

另一方面 fcitx-utf8 分支的重要功能之一……皮肤支持也不得不引入了更多的依赖,为了加载 png 文件而使用了 cairo。这里我还记得因为使用 cairo 而相关的字体问题。cairo 的文字绘制是没有 fallback 功能的,所以我又不得不引入了更高级的文字排版库 pango。

时隔一年之后,终于 Fcitx 到达了 4.0-rc1,回顾当年的发布公告,很多现在保留的功能也都是在整个版本发布的,例如搜狗词库转换。

另外里面的一个功能就是输入法可以由插件加载了,虽然只有 sunpinyin,但是也是可扩展性方面迈出的一大步。

https://groups.google.com/g/fcitx-announce/c/BBueYZSRXiY

* 皮肤支持,采用cairo做绘制,pango负责字体,界面支持真透明;
* 拼音支持多个词库:利用createPYMB创建的词库放在~/.config/fcitx/pinyin下面即可;
* 额外的输入法支持(目前只有fcitx-sunpinyin);
* 基于gtk的图形配置工具fcitx-config(并不包含在fcitx源码包中);
* 右键的菜单支持。

配置界面采用 gtk 其实在当初也是仔细思考过的……就像前几个月的 libime 用个 boost 都有人 BB 半天的事情。

在这个时期,每一个额外依赖的引入我都是和自己内心做了巨大的斗争的。

第一次大重构(2011)

4.0 没有几个小版本号,我就开始思考 4.1 要进行大规模的重构的事情了……一方面,同时存在的两套界面,多个输入法也让那时的代码开始有点承受不住发展的方向了。从 4.0 到 4.1 有好几个大变化,一个是沿用至今的插件架构,另一个就是构建系统从 autotools 转到了 cmake。

autotools 的并行编译手写实在是很容易出问题……和语法更加清晰的 cmake 相比差的很远。我还记得那一段时间我花了很长的时间构思这个虽然现在看起来还十分不成熟的架构,我从学校的角落的门走出去去买肯德基的时候还在边走边思考。

最终我拿出来的方案就是一切功能皆插件,以及一个插件间互相调用避免链接的依赖关系网。

Why fcitx need refactor?

这里 GNOME 已经给了 fcitx 第一个闷棍,就是输入法界面不能显示于 gnome shell 界面上方。当然当初我也并没有觉得什么。不过也为了后来的一些事情埋下了种子……

为了解决 GTK 的问题我也实现了 im module。我还记得和 sunpinyin 的一个开发者在 bugzilla.gnome.org 上为他们的 xim 支持问题争论的事情(sunpinyin 有一个纯 xim 实现 xsunpinyin)。im module 其实在当初也是被逼上梁山的产物,因为还有 xim 在 firefox 中的坐标 bug 也是有十余年历史才修复了一半?

毕业,和 GNOME 的仇怨,和迷茫(2011-2012)

在这一年发生了很多事情,一方面,生活上毕业,申请学校,和被迫干不想干的事情的迷茫困扰着我。我其实早在这一年大概就意识到,我不是一个喜欢学术研究的人。我喜欢的是工程方面的工作,从小当我第一次玩到 LEGO 的时候,我就觉醒了这种构建积木的快乐。

但是女朋友毕竟已经出国了,虽然想来我多少骨子里是一个很被动的人,在很多事情上都被生活推着走。那会儿也没有想明白,出国了之后能干什么。

另一件事就是 GNOME 3.6 集成 ibus 给我巨大的打击。基本上,从我刚接触开源软件开始,就有那么一些幻想的滤镜,大家是友好的,是想要一起做成什么事情的幻觉。

https://www.csslayer.info/wordpress/kde/fcitx-kde/

https://www.csslayer.info/wordpress/fcitx-dev/why-gtk-apps-are-likely-to-suck-on-input-method/

我得承认我是迷茫了很久,从 https://www.csslayer.info/wordpress/fcitx-dev/why-fcitx/ 一直到 https://www.csslayer.info/wordpress/fcitx-dev/re-ask-why-fcitx/

我还能记得在夜晚学校的公交车上因为 GNOME 而气到胸闷的时候(这件事是在我留学的学校),所以真的,我头一次对我投入这么多感情给 Fcitx 而感到后悔。

但总而言之,后来的故事就是我硬生生是被 GNOME 逼出了逆反心理。

「凡高大者,我无不蔑视。」

生活的问题,孩子的出生,和逃避现实(2012-2016)

前面我也说过了,我在生活的很多问题上其实都没有认真的思考过,而且我还是那种不知道放手的人。就像我不肯放手 Fcitx 那样。其实回过头来看,很多事情也许放手会变的很不一样,例如我应该早点 Quit PhD。

2013 年我结婚了,但是学校方面的生活也没有因此而步上正轨,研究生最后一年的自我堕落终于还是找上了我。在留学之前我想的其实很好,尽管申请学校方面是靠了很多的贵人的相助,但是那会儿我幻想着还是能够抛弃因为提不起劲头而感到一事无成的自己从头开始。

然而事情显然并没有这么好。

当你给你自己找到一个借口的时候,例如孩子的出生,就更难以继续那些原本就不想做的事情。然而不想要放手的优柔寡断又开始不停的作祟,导致陷入了「我还想试一下」,「但是我的身体只想要摆烂」这样奇怪的死循环中。

如果继续说下去似乎和 Fcitx 就没多大关系了,这段时间 Yichao Yu 写了很多的功能,有许多我其实至今都没有仔细看和了解过,例如著名自检脚本 fcitx diagnose 就基本上完全来自他的手笔,里面 shell script 的花活至今我几乎从没认真研究过。

然后,就是你写得越多,迷茫就越多。当你积累的经验增长到一定程度的时候,就会回头发现从 Fcitx 3 时代起缝缝补补的马车已经很难承担更多的任务了。而且还是自己在那个青涩时代写出来的代码,充斥着大量脑袋一热的设计和经验不足而留下的难以扩展的问题。

想要推倒重来的念头在 2013 年就产生了,而其结果也就是夭折的 https://github.com/fcitx/fcitx-ng/ 。当你坚持想用 C 写些什么的时候,语言本身的负担就越来越沉重,终于到自己也再也推不动的时候,也是借着 C++1x 的东风,想要试一试全新的 C++ 的体验。然后终于有了 Fcitx 5 开端。

Fcitx 5 – 缓慢而全新的开始(2017-2020)

直到 2016 年年中,它才开始短暂的恢复活力。毕竟自己的第一孩子已经半岁,偷闲摸鱼的时间也算增长了不少。因为 fcitx 5 而诞生的项目有三个,xcb-imdkit,fcitx5,libime。xcb-imdkit 完成的最早,然后 fcitx5 和 libime 同时推进。Fcitx 5 因为 kdbus 的东风而决定尝试用 sd-bus 作为 dbus 实现 sd event 作为 event loop,不过后来又加回了 libevent(最新的 master 换为了 libuv) + libdbus。

拼音的首个里程碑大概是 2017 年 4 月,然后 2017 年 10 月左右大概有了最初的新码表。之后大概就是不断的 dog fooding,以及不断吸引到有人来测试。我和我自己定下的约定就是不能比 Fcitx 4 缺少什么功能,因此最后的时光大概就是移植,移植再移植。

快车道和新的伙伴们(2020-2023)

在 Fcitx 5 发布一年之后我也写过一个记录 https://www.csslayer.info/wordpress/fcitx-dev/one-year-in-fcitx-5/ 那里就已经提到了 android 移植。虽然可能也是某人头脑一热的决定,不过到现在也变得更加完善,参与的人也变多了不少。

然后就是 open kylin 的虚拟键盘作为去年最重要的功能更新之一。现在还有人正在进行 mac 的移植工作。对我自己来说我最大的成就大概是 libime 新模型的训练吧……这个是货真价实因为数据量级的问题,而不得不使用钞能力来解决的。

所幸这个最后的结果是我很满意的,现在我就在用着 Fcitx 来输入这篇博客。我对于它预测准确度可以说是相当满意的。

未来是?

还有很多工作需要完成,不过感觉也已经渡过了最难的时候。在去年年初的时候我家庭成员的数量又翻了一倍,但我也没有以前那么焦虑了。虽然我自己的未来还有很多不确定,但 Fcitx 的未来至少在许多朋友的帮助下又打开了更多的可能性的门。

时钟已经指向 1:30,虽然我感觉有许多话想要讲出来,但是也总是在这种时候才能感受到自己语言方面才能的匮乏。

若要问我有什么想法…大概感觉就是他已经是我生活的一部分。

至于关于技术上的体会,有机会再聊吧。

不快乐的,终将过去,快乐的,总会留下。在不远的未来再见吧。

Posted in 日志 | Tagged , | 11 Comments

Accent color support in Fcitx 5

Recently a new feature was added to XDG desktop portal, which allows portal to return an accent color.

Since the default theme of fcitx5 is almost monochrome, it is a good addition the default theme together with the dark/light theme support.

Here’s a demo of this feature on master.

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

Key repetition and key event handling issue with Wayland input method protocols

I do have lots of complaints about wayland current input method protocols. Some of them are just lacking features, but this issue is the one that I think have design flaw from the beginning.

Let’s first review how the keyboard event is handled with input method on Wayland and X11.

The XFilterEvent would use XClientMessage to transport the key event to input method, which would actually introduce another message to X Server which is omitted in the graph above. Other than the XClientMessage, other methods may also be used, including raw socket, or DBus which is used by fcitx/ibus.

In Wayland, the things become different.

The input method first places a keyboard grab, to make compositor send all key event to input method server first. Then, depending on the result of key event (filter or not), the input method server may forward the key event back to compositor, then the key event will be forwarded from compositor to application, if input method server find this key is not relevant to the input method engine’s logic.

It may look ok right now, but if you put key repetition into consideration, you’ll find more issues with this design.

Imagine a following scenario:

1. User is using an editor to type some text, and already have some text in application already.
Let’s just say there’s some existing Chinese text: 你好.

Literally this means “hello” in Chinese 🙂

2. User types some new text and the text is stored in input method’s buffer to be converted to another language.

Hello, world!

3. User thinks that all the text is unwanted so the user press backspace and hold it, expect key repetition to remove the whole line, including “shi jie” and 你好 which is already committed.

Here is where it becomes problematic when Wayland decides to use keyboard grab for input method, and client side key repetition.

In X11, key repetition is done on the X Server side, client doesn’t need to worry about the key repetition generation. Client will just receive multiple key press events (release is optional, depending on a “detectable key repetition” option) until the key is physically released.

In Wayland, the key repetition is done on the application(client) side, the common logic is to implement this feature is that, when client gets a wl_keyboard.key press, it will start a timer and generate new key event on its own.

When you put input method in to this example, you will notice that, the very first “Backspace” is forwarded to input method and is invisible to client. So client will not be able to initiate the key repetition logic. That means, if the key need to be filtered by the input method, the input method server have to do the key repetition on its own.

In the case above, since there are texts in the buffer (shi jie), the first backspace will delete “e” in the buffer, then “i”, and then “j” etc..

When the last character in the buffer “s” is deleted, the buffer will become empty, which means, the next “repeated” backspace event need to be forwarded to application. This can still be done via zwp_virtual_keyboard_v1 or zwp_input_method_v1 depending on which version of protocol you are using.

Expected backspace behavior

But the problem is that, what do to next?

Let’s suppose the key repetition option is that “initial delay is 600ms, the repeat rate is 25/s”. The re-injected backspace can only trigger client’s own key repetition after 600ms, while user would expect it is already in the repeat phase, which will generate a backspace every 40ms. So input method have to continue to generate key press since application does not know the key repetition is already started in the past. But, after the first fake key repetition from input method is re-injected to the application, the client side key repetition logic will be now triggered. If input method doesn’t do anything to prevent it, the client will start to trigger key repetition after 600ms. If that happens, we will see both input method and client generating key repetition at the same time. To prevent this from happening, fcitx5 does a workaround by always sending a fake key release immediately after it send a key repetition from the input method side in order to stop the just started client side key repetition timer.

This seems to be very hacky and unreliable to me, since we are trying to “take over” the key repetition on client side, instead of hand it over.

Lets consider another scenario where it is totally broken.

Imagine a input method that can dynamically convert the text around cursor into preedit, and shows alternative text for the word around cursor. This is very common on mobile phone: you can click on a word and the word will be “underlined”, and alternative candidates is shown on the on-screen keyboard.

1. Image user have text “Hello, world |” (| represent the cursor location in application.

2. user starts to press backspace.

3. the first backspace press is ignored by input method, since there’s no word around cursor.

4. client side key repetition kicks in. Please notice that client side key repetition will not be forwarded to input method under current version of protocol

5. Text becomes “Hello, world”, and input method will try to consume “world” and convert it into preedit text and put “world” in the buffer on the input method server. Which means, from this point, any new backspace event should be handled by input method.

Consume the word “world” is and convert it to preedit is not a feature currently supported under fcitx, but we do want to implement such things in the future. Actually, fcitx5-unikey is already able to do something in a similar way, see the video below.

fcitx5-unikey’s consume existing text and re-edit feature. It’s not triggered by “backspace” but “e” in this case, but you get my idea.

But if you remember how client side key repetition works, you will notice that it will never be forwarded to input method, thus the backspace is “leaked” from input method into application, and will cause unexpected behavior.

My proposed solution to solve this is that: just go back to the old X11 model of forwarding event to input method. The procedure would look like:

  1. wl_keyboard.key send to application
  2. text_input.key send key to input method through compositor, this includes all key events, including the repeated key events generated on the client side.
  3. input method server forward it back with the old interface
  4. application got the input method forwarded back event via a new event text_input.forward_key, instead of from wl_keyboard.key.

This introduce more round trips between compositor and application, but it solves the whole issue in a much more cleaner way comparing to the other solution I can think of. And this new interface can even help on other issue like type-to-search’s chicken-and-egg issue, also this may make browser happier by allow them to stick to the javascript key/IME event standard better.

If one want to stick to keyboard grab model, they may have to add a lot of tricky new events like “handover ongoing key repetition” etc, which from my point of view would introduce much more complexity and easier to go wrong.

Posted in fcitx development, Linux | Tagged , , , | 2 Comments

演示一下和 openKylin 合作开发的虚拟键盘

将在下一个版本的 Fcitx 加入支持。

界面的代码位于:https://gitee.com/openkylin/kylin-virtual-keyboard

欢迎大家进行测试,虽然功能的支持还非常初级,但是已经可以进行一些简单的测试(X11 下,想要在 Wayland 下使用还有一些工作需要进行)。

(视频闪烁主要是录制问题)

Posted in fcitx development | Tagged , | 2 Comments