One year in Fcitx 5

Fcitx 5 first release is in 2020/11/2, and as of today, we have reached the 13th release of Fcitx 5.

What happened to Fcitx 5 within this whole year of development?

1. Best Wayland support on Linux

As of today, to my knowledge, Fcitx 5 is the only input method frameworks under Linux that works under all different types of wayland.

We have been testing KDE/GNOME/Sway (Only causally played with wayfire and since there are too many wlroots based around we can’t do much test there.).

2. New engines

By the end of year we have two new engines https://keyman.com/ and https://github.com/OpenBangla/OpenBangla-Keyboard .

3. Flatpak support

Not only flatpak support fcitx5, but fcitx5 also works as a flatpak package.

https://fcitx-im.org/wiki/Install_Fcitx_5#Install_Fcitx_5_from_Flatpak

4. New features comparing to Fcitx 4 counterparts.

We really got tons of them new features, e.g.

  • fully customizable shuangpin profile (you can define shuangpin profile with any combinitions of initial/final
  • new preedit mode that takes less space in input window
  • punctuation configuration via GUI
  • quick match for long word in Pinyin
  • paritial shuangpin support
  • rime plugin loading
  • rime new preedit mode
  • rime dbus API
  • rime app_options

5. Android support

I didn’t develop this, only provides some Q/A on the details, and add some support to make it build/work on android more easily. https://github.com/rocka/fcitx5-android-poc/ It’s still considered as highly experimental, please use it with caution. One of our next target is to come up with a new API for implementing virtual keyboard, which will also benefit the native linux on screen keyboard.

If you are still using Fcitx 4, please consider give Fcitx 5 a try. You can find the distribution that packages fcitx5 via https://pkgs.org/search/?q=fcitx5 .

Posted in fcitx development | Tagged , , , | 1 Comment

Why surrounding text is the worst feature in the Linux input method world

This is mainly a complain about how mess this feature is and why no one could reliably use it.

To give people some background, surrounding text is about the feature that an application can notify the input method what are the characters around the cursor, and the input method can directly change the text around the cursor.

For example, in a input box, you have some text like this.

With surrounding text, application is able to notify input method the context around the cursor.

For example, in this case, the input method will receive text is “I like typing.”, the anchor is 8, and the cursor is 10. Anchor is the starting offset of the selection, and the cursor is the end of selection. If there is no selection, anchor will equal to cursor.

Now you may want to ask, isn’t it a costly thing to do? Answer is YES. Imagine you have a crazy long line in the editor, and whenever you change the text, you will need to send it over to the input method. Usually, input method would just apply a maximum size.

Next we will need to talk about the messiest thing about this is the API. Here lets list things about what are people doing with it.

  1. Gtk native API: set_surrounding_text / delete_surrounding_text, the value of offset is Unicode(UCS4) character based. delete_surrounding_text uses (offset, length) to define the range.
  2. Qt native API: the value of offset is UTF-16 character based. delete_surrounding_text uses (offset, length) to define the range, but, it excludes the current selected text when applying offset and length.
  3. Wayland protocol text-input-v1 / zwp_input_method_v1, similar to Qt, but offsets are UTF-8 character based.
  4. Wayland protocol text-input-v2 / text-input-v3 / zwp_input_method_v2 , delete_surrounding_text uses (before, after) to described the range. Basically it means some additional character before and after the selection. Offsets are also utf8 character based.
  5. Gtk implementation of text-input-v3 (?!), does not follow (4), by just using received UTF-8 offset as Unicode (UCS4) based offset, which is actually a bug. Also, it does not actively sending over the update of surrounding text, which makes it useless.

Also, people seems to not have a clear definition about whether surrounding text should include preedit text. Which is purely headache to deal with.

Not to mention that non-native widget implemented with Gtk/Qt are very likely to implement it in a wrong way. Also XIM does not support it. Not to mention that terminal application that does not support it have to claim it support surrounding text, due to lacking of ability to notify application.

So now, people are more likely to stick to use a limited set of feature in surrounding text.

  1. Use it as auxiliary data like primary selection, to just learn about what text is being selected.
  2. Delete surrounding text only when it is extremely reliable, e.g. delete 1 cursor before cursor.
  3. When implementing a feature that requires full featured surrounding text, make this feature optional and always provides an alternative easy way for user to not using it.

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

上下跳动的文字

我觉得我自从手搓几个low-level的文本绘制之后这个问题其实已经变成专家了。问题本身其实还是很有趣的,这里特别总结一下。

不少人可能都注意到了 Linux 下面有时候会有一种奇怪的现象,就是你输入文字的时候,一旦输入了汉字,汉字可能会把整行文字的位置降低。删除掉汉字的时候,则又会恢复到原本。这是为什么呢?

其实道理是很简单的,就是你这一行文字混合了两种不同的字体。为什么明明没有单独配置但是却使用了不同的字体?因为系统默认使用的英文字体本身,可能并不包含汉字。因此当需要显示汉字(或者其他任何不存在于这个字体的字符)时,就只能回落(fallback)到另外的字体上。你也许注意到了,即使两种字体配置为同一个大小(point),但是实际显示在屏幕上的大小可能是不同的。当两个字体同时在一行文本中使用的时候,为了将他们对齐,则需要使用一个标准将两个字体的文本对齐。这个标准就是基线(baseline)

A diagram showing the line terms used in typography, with the baseline highlighted.
图片来自 Wikipedia

这里还有两个重要的线 ascent 和 descent。ascent 就是从 baseline 到字体的最高点,descent 就是 baseline 到字体的最低点。可以假想一下,当另一个字体混入的时候,如果它的 ascent 高于原本字体的 ascent,自然就需要将文本整体“下推”。

gedit

通过辅助线,我们可以明显观察到 DejaVu 和中文字体组合之后可以出现明显的下沉现象。

对于编辑器来说,只有两种方式,一种允许每行的高度不同,从而适配文字本身的大小, 另一种则是每行高度相同,但带来的副作用可能是会对 fallback 到的较高的字体砍头去尾。

这里有一个展示了 katepart 曾经长时间存在的文字渲染问题,当字体可能下推文本过多时,超过了原本的行高,则不得不砍掉了一些。右侧是经过我修复之后的 katepart。

这里所谓的修复是什么呢?其实非常的简单,就是把“下推”的文字拉回原本只有单个字体的基线位置。简单的来说,就是进行这样的运算:在 y 轴上偏移一个这样的数值:(渲染文本的整体 ascent – 字体本身的 ascent)。

这样运算之后,那些并非 fallback 的字符将不会被「下推」,而保持在原始的位置。fallback 到其他字体的字符则会对应的上拉一些位置。你可能会问了,这样似乎并没有解决字体被砍头的问题啊?确实,但字体设计的时候,一般会在顶部留有余地不会紧密排列,经过这样调整之后,反而会让大部分即使是 fallback 的字符也完整显示在行内,同时还会避免跳动的问题。

同时,如果你多次使用文本绘制 API 来绘制多条文本,这样也可以保证他们自动会对齐在一起。否则则可能出现即使逻辑上是一行,但是因为文本不同而上下参差不齐的问题。

Posted in Linux | Tagged , | Leave a comment

How to use a missing keyboard layout in Fcitx 5

When Fcitx read the layout information, it read from xkeyboard-config, mostly like to be /usr/share/X11/xkb/rules/evdev.xml on your system. But unfortunately, there might be missing data in this file and you should report the bug to bugs.freedesktop.org for missing data.

But what if you want to use the layout right now without waiting for the bugfix for xkeyboard-config?

Here’s an easy way to do it at user level.

For example, if you use setxkbmap -layout br -variant abnt2 to setup your keyboard input method, but you find this entry is not currently in evdev.xml files, then you can do:

1. Create a configuration for input method
Pleasse notice, the file name matters, it should be named by keyboard-[layout]-[variant].conf
The file name for this layout should be ~/.local/share/fcitx5/inputmethod/keyboard-br-abnt2.conf

[InputMethod]
Name=br-abnt2
Icon=input-keyboard
LangCode=pt_BR
Addon=keyboard
Configurable=True
Label=br

The “Name=” section in the file doesn’t really matter, you can put anything meaningful to you. LangCode= and Label= also doesn’t really matter.

2. Restart fcitx 5 to make it reload the data.

3. Open config tool to add it to the list.

4. Move it to the first place in the list, and you will get a popup that ask you: whether you want to fix the config to make the system layout configuration matches the first input method. You may click yes to set it. Because evdev.xml does not have the corresponding entry, you’ll not be able configure the system layout the regular way.

Then you should be able to use the layout just like using the setxkbmap command, enjoy!

Posted in fcitx development | Tagged , , | 2 Comments

Linux 的 HiDPI 配置问题

其实这个问题对我来说从来也不是个问题,因为我已经基本深入理解了这个问题的本质……然而似乎有不少人对于怎么配置,为什么这么配置,为什么又出了问题一知半解,因为最近在 Twitter 上回复一些比较复杂的内容,特此整理记录一下。

在过去,电脑的屏幕一般都是 96 DPI,DPI 的含义是每英寸点数(像素点),具体计算的方法也非常简单,用像素的个数除以屏幕的长宽即可。随着技术发展(显示器和处理器),电脑逐渐可以支持更高的 DPI 用于显示。抛开具体发展不提,但是这里引入了一个问题,你程序并不支持在这样的屏幕上正常显示。通常来说的问题就是界面特别小。

为什么?

因为程序过去从来也不需要考虑这个问题,都是按照像素来进行的设计,例如显示一个 100 x 100 的窗口,在 96 DPI 上大小正常,但是 DPI 翻倍的话,就只有原来的 1/4 大。人的眼睛在乎的是它现实中有多大(像素 / DPI = 长度),而不是有多少像素点。为了让程序能够正常显示,同时又不需要修改太多代码,大家也是努力想了很多办法。

1、设置字体大小。

界面框架大小不提,字看不清那是最难受的,因此只调整字体大小这样的设置,成为了大部分人解决问题的第一步。但是,在 X 上,这个设置是全局的,因此也就不能针对不同的屏幕,来设置一个统一的 DPI。一个屏幕大一个屏幕小也可以说是常态了,不少人就选择设置一个居中的值来自己适应。毕竟人是活的,程序是死的,改变不了世界那就只好自己适应世界。

2、程序来支持内部缩放。

这里,当然也就是 Qt / Gtk 这样的大框架才有精力和能力去支持这种功能。这个功能在程序不需要进行额外修改,或者只进行少量修改的前提下,直接在内部添加坐标的转换功能,乘以一个倍数,变相将程序放大。这样,在高分辨率的前提下,就可以用正常的大小显示界面。

3、设置分辨率来进行缩放

也许有人说,显示器不都可以配置分辨率么?你3200×1800的屏幕,我强行设置 1600×900 不就完全正常大小了么?这样说当然是没有问题,但是相对的你也就没有利用上这个屏幕的资源,原本你可以在更高的像素点上进行更好的反锯齿。相当于说,你把大图片缩小再放大,你得到的能是原始图片的效果么?必然是有数据损失的。当然,还是那句话,人是活的,程序是死的。一句 I don’t care 什么问题都能解决。

上面说的分辨率缩放只是在配置当中其中的一个方向。相反,还有故意把 1600×900 伪装成 3200×1800 这种操作,这是为什么呢?

这是因为,尽管在 2 中你可以靠 Toolkit 来放大程序的界面,但是如果你同时有两个不一样的屏幕,那这可就倒了大霉了。一个屏幕 96 DPI 一个屏幕 289 DPI,那你的程序是选哪一个 DPI 来显示呢?

关于这点,其实甚至还没有到「选哪个」的问题,支持根据屏幕动态切换 DPI 的 Toolkit 在 Linux 上只有 Qt 这一家。Gtk 根本就不支持动态在运行时根据屏幕切换 DPI。那你既想要显示“锐利”,又想要大小正常,那就只有一个办法了。把这个 96 DPI 的屏幕也变相变成 HiDPI 的。尽管你没有 HiDPI 的屏幕,但是你可以通过软件「装作」你有这么一个屏幕。

xrandr --output eDP-1 --scale 2x2 --mode 1920x1080 --pos 0x0 --output DP-1-0 --mode 2560x1440 --pos 3840x0

上面这条命令,就是把 eDP-1 在软件层面变成 3840×2160 的屏幕,放在左边,尽管它原生只有 1920×1080。这样有两个 DPI 相近的屏幕,你自然就可以对按照 2 对程序设置统一的缩放倍率了。

4、将他们组合在一起

先来说说 X 到底是怎么回事。对 X 来说,它不知道窗口里面什么 HiDPI 不 HiDPI,你给我什么,我就显示什么。所有坐标在 X 层面都是原生的。也就是说,它没法把不支持 HiDPI 的程序放大,例如一些 Java 程序或者 Xlib 程序。那怎么办?如果你非要一切程序都正常大小,那就只能走暴殄天物的方案 3 ,把分辨率人工降下来。

还是再重复一次,「人是活的,代码是死的」。你当然可以选择「那些不支持 HiDPI 的程序都是辣鸡,我不用了!」。为了享受「锐利」,你选择了只用 Gtk / Qt 的程序,其他程序如果能用调整程序内部字体凑合就凑合一下,凑合不了干脆不用,反正 Gtk / Qt 的选择那么多。

那么在你选择这条路的时候,首先就来了解一下 Gtk 和 Qt 设定上的不同。Gtk 的 scale factor 是影响字体大小的,而 Qt 的是不影响字体大小的。

通过下图,你可以很容易对比理解 Qt 和字体 DPI 组合的行为。

https://pbs.twimg.com/media/EvcWJYPVIAEo0x_?format=jpg&name=orig

然后再来对比一下 Gtk 的行为。

图像

也就是说,如果你想 Qt 和 Gtk 同时达到两倍缩放,并且大小相当,需要设置:

1、Xft DPI  192
2、QT_SCREEN_SCALE_FACTORS=2
3、GDK_SCALE=2 GDK_DPI_SCALE=0.5

具体数值你可以根据你的屏幕自行修改。当然,这些数值具体的设置方式可以通过环境变量,又或者可以通过桌面环境的设置,但不管通过哪种手段,本质其实是一样的。

在这个设置的基础上,又可以分别分成两个路线,使用 3 的方法伪装低 DPI 的屏幕,又或者你连 Gtk 也一起放弃,采用 Qt 可以支持不同屏动态切换 DPI 的设置。

QT_SCREEN_SCALE_FACTORS="eDP-1=1;DP-1-0=2"

例如这样设置的话,就可以在 eDP-1 用 1 倍缩放,DP-1-0 用两倍缩放。尽管 Xft DPI 还是只能一个值,但是 Qt 会自动帮你绑定到某个屏幕,另一个屏幕还是采取包括字体大小在内的完全缩放,把窗口在不同屏幕移动的时候也会自动调整程序的大小。当然为什么只有 Qt 实现了这个我猜想主要是因为 Windows 上只有这一种方案可以总是保持原生状态渲染,就顺带着也支持了 Linux 。

如果说到 Wayland(目前已有的实现),本质上是一样类似的,但是参数实际上就没有这么多花活可以弄。Wayland 对于窗口可以有一个窗口本身的 scale 是多少的属性,当然,得是原生 Wayland 窗口,X wayland 的窗口 scale 会自动当成是 1。然后这个窗口会在不同的屏幕上,根据屏幕的 scale 进行匹配缩放。这也就是为什么 Wayland 可以支持所谓的「分数」缩放(非整数百分比,例如 150%)。其实 X 如果你把 xrandr 的 scale 改成带小数的倍数也是可以的。

其实 Qt 在 X 下,也是可以支持非整数的 scaleFactor。但是这里有一个问题。浮点数的计算是有精度损失的,所以如果你原生进行内部坐标非整数的运算,实际上更有可能出问题,在哪里界面上缺掉一像素也是常见的事,相比整数倍数运算之后再缩放来说的方案,问题会少一些。但是希望大家还没有忘记 Wayland 下也还有X wayland 需要显示。根据之前说的情况 X Wayland 和 X 并没有什么差别,只是 3 中描述的操作现在交给 wayland compositor 进行了。GNOME 默认的行为是不缩放 X,可以通过 gsettings 启用一个实验性功能来让 X 被「放大」。KDE 是默认采用设置的倍数放大。相对来说,反而和 X11 相比更加不「自由」,并不能分开指定 Wayland 和 X 的缩放倍率。

毕竟,对有些人来说,显示锐利本身比大小更重要。左侧是原生 Wayland,右侧是 X wayland。尽管大小一致,在 150% 缩放的情况下 X 的程序自然是模糊了(从 100% 放大到 150 %)。而 Wayland 这个则是从 200% 缩小到 150%(当然,也许用 300 % 绘制再缩小 150 % 效果可能更好?但具体来说现在系统实现就是这样的)

https://pbs.twimg.com/media/EvcdZv1VIAQ4lkM?format=jpg&name=orig

整个 HiDPI 问题就是那么一个问题,怎么把原本针对小屏幕设计的窗口放到高分辨率上显示,和 X 还是 Wayland 有什么相关么?没有,只是在不同平台你有相对不同的变量可以操纵。有没有完美的解决方案?当然也是没有。你偏要把一个 100×100 的窗口当成 200×200 显示,窗口不支持缩放当然只能模糊。窗口支持缩放却又不支持动态切换,混合分辨率的在低分辨率上那就只能浪费资源来缩放,这本来就是两难的问题。真正完美的纯动态切换现在也就只有 X11 + Qt,但大部分人也不是靠纯 Qt 程序不是?非要概括的话,就是有钱能解决大部分问题,有钱就买俩 HiDPI 屏幕,有钱就硬件强力,浪费资源缩放也无所谓。

Posted in Linux | Tagged , , | Leave a comment