打包输入法框架和引擎

跳转至: 导航, 搜索

本章节教您如何打包输入法框架和引擎以及它们的一些特殊注意事项。
输入法是一个 CJK only 的工具。西方语系都可以通过带废键/不带废键的键盘来输入,他们使用的是 X11 的 xkb 而不是我们中国人常说的输入法。键盘布局和输入法是两个概念,输入法要复杂高深得多。因此这么说吧,如果你是一个中文打包者,如果你不会打包和维护输入法,那你的世界至少灰暗一多半。另外还有一个深层意思就是说,你一定要学好输入法的打包,别轻易听信老外们关于输入法的意见,因为他们从来没用过,完全就不懂,即使日本人也不行。(除了红帽的 Takao Fujiwara)
注意: 根据小企鹅输入法开发者翁学天的名言,输入法是唯一一个不建议最终用户直接编译使用的软件。因此作为打包者,不要推荐用户这么做。那是自寻死路。

什么是输入法?

注意: 本章节不是技术讨论,因此可能存在技术错误,只是作为给打包者的概念扫盲而存在。不适合作为开发参考。

输入法从底层到上层可以简单的分为三类:

  • 输入法框架
  • 输入法引擎
  • 输入法界面

因此,和最终用户和半瓶水的开发者讨论输入法是非常困难的,大部分时候都是鸡同鸭讲。最终用户对输入法的认识大部分停留在输入法界面的层面上,也就是诸如搜狗输入法换皮肤这个境界,而半瓶水的开发者可能认识到了「壁纸不是桌面环境」这个命题,但是他们的认识大部分停留在输入法引擎的层面上(包括大多数 Linux 核心项目的老外开发者,比如 GNOME),他们会认为 sunpinyin 好就代表 fcitx 好,或者 googlepinyin/五笔好就代表 ibus 好这样的层面。

因此请注意下文中的输入法,输入法框架,输入法引擎,输入法界面这样的词,它们说的都是不同的东西。

输入法框架

输入法框架是作为处理输入法引擎处理后的用户输入数据与最终要显示输入结果的图形/命令行环境比如 X11/Console,不同桌面环境比如 GNOME/KDE,以及各类应用程序间的通信的中心而存在的。常见的输入法框架有苏哲先生的 scim,黄鹏先生、日本人 Takao Fujiwara 的 ibus 以及 22 岁的开发者翁学天(老 K,KDE 的 K)的 fcitx(最早是鱼王的,现在鱼王还在,只是脱离的主要贡献者的角色)和不入流的小小啊,gcin/hime 等等。目前比较常见的通信协议是自定义的 dbus 协议。

输入法框架复杂就复杂在它要处理与几乎所有应用程序的通信,除非这个应用程序上一个输入框没有。而且它是 CJK 用户唯一直接面对的系统程序,甚至比诸如音乐播放器 Amarok 什么的重要得多。没有输入法,这个操作系统再好,对你来说,也是个坏的操作系统。

另外一个复杂之处在于输入法需要提供几乎所有桌面环境下原生的界面。对,输入法框架的开发者不开发输入法引擎,但是他需要提供最底层,和最上层。但是,第一,输入法框架的作者是有桌面环境倾向的,一个人不可能日常既用 GNOME,又用 KDE,只有小白才会这么朝秦暮楚,读这篇介绍的都是开发者,你们自然懂; 第二,几乎所有的上游桌面环境在开发时都很少考虑输入法的兼容问题,至于集成,考虑不来。人的时间精力有限,作为用户可以要求输入法作者既开发输入法,又积极参与各类桌面环境的底层开发,作为开发者,这么说话应该掌嘴。因此输入法 bug 多可以理解,而且其中大多数的 bug 直接来自于最上层的输入法界面,大多数输入法界面的 bug 直接来自于上游的桌面环境。

第三个复杂之处在于输入法框架作为一个通信中心,需要有信可通。由于第二个复杂之处,目前输入法作者是自己去找信来通。这就存在着兼容问题。这是产生输入法框架的 bug 的一个来源。一个理想的输入环境是应用程序反馈给输入法一些信息供输入法处理,但目前几乎所有的图形环境/桌面环境/应用程序都没有提供这样的信息。

第四个复杂之处在于输入法框架没有一个标准的协议。目前使用的 dbus 协议都是输入法作者个人开发,不能在输入法框架之间兼容。但要求一个标准的协议有点强人所难。第一,我们是自由世界; 第二,输入法的完成度和历史不同,比如 scim 和 gcin 是第一古老,fcitx 是第二(后来有重写),ibus 是第三。他们有的使用了 dbus,有的可能没有。在这里用 Windows 的思维去考虑问题是不对的,因为 Windows 只有一个微软提供的输入法框架,所谓搜狗输入法,谷歌输入法,都是输入法引擎和输入法界面。甚至 Windows 和 Linux 的输入法开发者需要提供的东西都不一样。

输入法引擎

输入法引擎主要是用来处理比如输入连续的拼音,如何把它们切分成不同的字和词組这样的认字和断句的任务的程序。这点中文和西文很大不同,因为西文的是几乎不存在字这个概念的(26 个英文字母严格来说不是字,因为它们本身并无意义,而「说文解字」这样的书中,每一个方块字都有自己的出处和意思,这才是真正意义上的字),它们的句子只有词,而词和词之间是通过空格来划分的。而中文首先认字就是一个很大困难,存在五笔、拼音、区位等多种输入和认识字的方法,存在很多选择和冗余(因为大部分时候你只需要一个特定的字)。词组的划分又有比如两维、三维、多维等等各种语言和语义模型。

实际上所有的输入法框架用的引擎都大同小异,拼音有 libpinyin, libgooglepinyin(谷歌拼音), sunpinyin, 码表有五笔86, chewing(仓颉,新酷音),郑码,大易,以及一些比如印度语码表什么的,日文有 anthy,canna,wnn,skk,韩文就是 libhangul。这些都是引擎。

关于输入法引擎,请参考吴鹏先生的 libpinyin 的 PPT(36 页的人生)。

输入法界面

输入法界面是被最终用户直观感受到的图形界面。在 Linux 世界就是 ibus 的 gtk 界面,和 gnome-shell 外挂,以及 fcitx 的 gtk 界面和 kcm-fcitx 模块。它们和其他 gtk/kde 程序一样,本身就是一个 gtk/kde 程序。在 Windows 世界就是所谓的搜狗拼音、百度拼音等等。

输入法界面分为两部分。一部分叫做输入面板,主要用来显示拼音/五笔和候选词,一部分叫做配置页面,主要用来显示与输入法框架/引擎相关(不是每个输入法框架都有针对框架的配置选项)的配置选项。输入面板和配置页面与输入法框架本身的联系都不是很紧密,因此可以由外部程序来提供。比如 ibus 可以使用 kde playground 中的 kimpanel 或者 opendesktop.org 的 kimtoy 实现 kde 的输入面板或 qt 的输入面板,上面两个都是独立的 dbus 通信程序,监听输入法框架通过专有 dbus 协议传递的信号并做出反应。再比如 fcitx 的 kde 配置页面是在 kde 个人中心里面,这是更像是一个独立的 kde 模块而不是一个输入法模块。但是这都是传统意义上的输入法界面。因为输入法框架仍然是在主程序中提供一个图形界面的,比如 ibus 的 pygtk 和 fcitx 默认的 c。

打包输入法框架的注意事项

输入法模块的编译

注意: 输入法模块一定要使用 %package gtk2 这样的切分子软件包函数拆分。原因见 64 位系统下 32 位输入法模块的编译
输入法模块是输入法框架针对不同平台比如 GTK/QT 的,利用该平台提供的接口(有些有,有些无,即使有功能也很残缺)和平台函数创造的输入法框架的而不是这个平台的函数区块,目的是使输入法框架的核心代码可以在该平台上运行,以获得原生的用户输入体验。由于这些函数区块会单独编译为一个函数库文件,后缀是 .so,我们打包者一般管这个库文件叫做输入法模块。
你可以想象成两块积木对插,其中一个积木要有一个凸出部,另一个积木要凹进去一块,这样才能无缝对接。输入法模块就是那个凸出部,因此它是属于输入法框架的而不是作为对象的那个平台的。实际上正是这种设计使得输入法不必严格依赖平台,比如 GTK 输入法必须使用 GTK 开发,输入法可以使用 C++/Python 开发。但这种设计也是平台不管输入法这一恶习的一个来源。

openSUSE 下编译输入法的模块是在输入法主包的编译过程中自动进行的,无需人工干预。但需要注意的是针对不同的 openSUSE 版本开启不同的输入法模块。比如 openSUSE 11.4 搭载的是 GNOME2, 因此我们如果默认提供 GTK3 的输入法模块就不是那么的合适了。这需要打包者熟练掌握不同 openSUSE 的核心软件包版本(可以去 s.o.o/s 查询),以及熟练的运用 version 标签和 Requires/Recommends 标签。

编译时开启输入法模块

下面是一般编译系统(cmake/configure/Debian 的 dh_auto_configure 宏)下的开启办法:

  • Cmake。一般是用 -D + Option 开启,具体有那些 option 可以在 CMakeLists.txt 中 grep -r "Option" ./CMakeLists.txt 获取。
  • configure。一般使用 -enable-something 开启。
  • Debian 的 dh_auto_configure 宏。参考 为 Debian 和 Ubuntu 打包

openSUSE 下的范例

下面给出了一个 openSUSE 及 SUSE SLE 各版本的标准范例:

  • BuildRequires/Requires

因为无论哪个版本的 openSUSE/SuSE SLE,都带有 GTK2, 只有 11.4 以上才有 GTK3; QT3 只有 SLE 10 版本才有意义(因为现在没人装/开发 QT3 程序), SLE 10 也没有 QT4.

SLE 10 被 OBS 视为 openSUSE, 所以可以用 openSUSE 的版本标签来识别,它相当于 1010. 紧接着就是 openSUSE 11.1 也就是 SLE 11 了。因此可以用 %if 0%{?suse_version} < 1110 里判定 SLE 10.
BuildRequires: gtk2-devel
%if 0%{?suse_version} >= 1140
BuildRequires: gtk3-devel
%endif
%if 0%{?suse_version} >= 1110
BuildRequires: libqt4-devel
%endif
%if 0%{?suse_version} < 1110
BuildRequires: qt3-devel
%endif
[...]

至于 Requires/Recommends 的使用,一般我们使用 Requires:

  • 第一是比较标准,Recommends 只有 openSUSE 才能识别, Fedora/Mandriva/CentOS 是不认识的。
  • 第二 Requires/Recommends 的主要区别是,Requires 的包如果没有,就装不了本包; Recommends 的包如果有,就装上,没有,本包依然能正常安装。这在输入法模块同输入法框架的主包在一次编译中完成的情况下是没有区别的,但是对于其他情况影响可能会很大,比如 xim 输入法模块是同输入法框架的主包在一次编译中完成的(也没有其他的输入法模块可选/用),你 Recommends 了 libx11, 如果这个包没有,那么你的输入法也用不了,因为完全没有 xim 接口,单独一个 xim 是加载不上的。
Requires:      %{name}-gtk2 = %{version}
%if 0%{?suse_version} >= 1140
Requires:      %{name}-gtk3 = %{version}
%endif
%if 0%{?suse_version} >= 1110
Requires:      %{name}-qt4 = %{version}
%endif
%if 0%{?suse_version} < 1110
Requires:      %{name}-qt3 = %{version}
%endif
  •  %build

唯一需要注意的就是 version 标签要顶格写,自己占一行。

%build 
cmake .. -DENABLE_GTK2_IM_MODULE=On \
%if 0%{?suse_version} >= 1140
        -DENABLE_GTK3_IM_MODULE=On \
%endif
%if 0%{?suse_version} >= 1110
        -DENABLE_QT_IM_MODULE=On \
%endif
%if 0%{?suse_version} < 1110
        -DENABLE_QT3_IM_MODULE=On \
%endif
  •  %files

文件章节需要注意的和 BuildRequires/Requires 章节的一样,就是只有在满足特定 version 的情况下某些包才会存在:

%if 0%{?suse_version} >= 1110
%files qt4
%defattr(-,root,root)
%endif

这样。

64 位系统下 32 位输入法模块的编译

注意: 该方法对在 OBS 上打包的 Fedora 软件无效。Fedora 不吃 baselibs.conf。

这是好多打包者在打包输入法时容易遗漏的。她们认为 i386/i586/i686 的 RPM 提供 32 位的输入法框架和输入法模块,x86_64 的 RPM 提供 64 位的输入法框架和输入法模块,这不是挺算无遗策的?

其实则不然。64 位系统中也有一些 32 位程序,虽然不常见,但是有些还是很常用的,比如一些商业的 Linux 游戏只有 32 位版本,比如目前的金山 WPS 办公套件,就只有 32 位版本,再比如著名的阿里旺旺 Linux 泄露版,也是一个只有 32 位的程序。用 64 位的输入法模块是不能很好的在它们之下进行输入的,什么架构的平台需要什么架构的模块(比如如果没有该架构的输入法模块,可能会转向去使用 XIM 提供的该架构的输入模块,就存在兼容性问题,如果你的输入法不支持 XIM 模块呢,那么在该 32 位程序下就无法输入中文),而模块和输入法之间是可以通信的,比如 32 位的 Fcitx 模块可以和 64 位的 Fcitx 框架通信(仅指在 64 位平台上安装 32 位依赖编译出来的输入法模块可以和 64 位平台上的输入法框架通信,不是说从i386 平台拷贝一个 .so 拿过来就能用。)。

openSUSE 使用了一个叫做 baselibs.conf 的文件来完成在 64 位平台上编译 32 位模块这项工作。这是一个通用解决方案,具体参考 baselibs.conf 配置

baselibs.conf 的简单用法就是:

  • 创建一个空白文件,名为 baselibs.conf。
  • 写入需要编译 32 位兼容包的子软件包名(知道为什么要把输入法模块单独拆包了吧)。
  • osc add baselibs.conf
  • osc ci

这样 OBS 编译时就会自动生成这些子包的 32 位版本。

如果你在网页点击源名称去查看最终生成的包的话,你会发现这样的包是在 i586 架构下编译出来的。是的,openSUSE 的服务器没有纯 32 位版本,i586/i686 的核心其实也是 64 位,只不过模拟了 32 位环境。这和有些发行版存在纯正的 amd64 或 ia64 服务器不一样。

开机启动说明

QT 输入法模块的加载

是通过我们的开机启动脚本里面的 export 来实现的,无需在 spec 范式文件里操心。用户也可以手动实现:

ALT+F2 打开 krunner,输入 qtconfig,然后在 Interface 下面有个 Default Input Method 下拉列表里选中 Fcitx 即可。

GTK 输入法模块的加载

GTK 的输入法模块和 QT 的不同,它有一个缓存机制在里面。因此即使已经 export 了该模块,依然需要在 %post/%postun 里面刷新该缓存。

  •  %post/%postun
#Convenient define for the scriplets
%if %{_lib} == lib64
%define _gtk2_query_immodules %{_bindir}/gtk-query-immodules-2.0-64
%define _gtk2_query_immodules_update_cache %{_gtk2_query_immodules} > %   {_sysconfdir}/gtk-2.0/gtk64.immodules
%else
%define _gtk2_query_immodules %{_bindir}/gtk-query-immodules-2.0
%define _gtk2_query_immodules_update_cache %{_gtk2_query_immodules} > % {_sysconfdir}/gtk-2.0/gtk.immodules
%endif
//我们定义了两个简单的辅助宏,用来刷新 GTK2 的输入法模块缓存。
%post gtk2
%{_gtk2_query_immodules_update_cache}
%postun gtk2
%{_gtk2_query_immodules_update_cache}
%if 0%{?suse_version} >= 1140
//注意只有在 >= 1140 的情况下才会有 %{name}-gtk3 这个子包(%build 决定的),不然 %post/%postun 是报错的。
%if %{_lib} == lib64
%define _gtk3_query_immodules %{_bindir}/gtk-query-immodules-3.0-64
%else
%define _gtk3_query_immodules %{_bindir}/gtk-query-immodules-3.0
%endif
%define _gtk3_query_immodules_update_cache %{_gtk3_query_immodules} --update-cache
//同样定义了两个辅助宏
%post gtk3
%{_gtk3_query_immodules_update_cache}
#Add fcitx icons to gnome3 panel
TARGET="/usr/share/gnome-shell/js/ui/statusIconDispatcher.js"
if [ -f $TARGET ] && [ ! -f $TARGET-fcitx ] ; then
mv $TARGET $TARGET-fcitx
sed "/^const STANDARD_TRAY_ICON_IMPLEMENTATIONS/a \    'fcitx': 'input-method',"   $TARGET-fcitx > $TARGET
fi 
//GNOME3 的 Bug,所以我们要自己动手
%postun gtk3
%{_gtk3_query_immodules_update_cache}
%endif

Fedora 和 openSUSE 差不多:

%post gtk2
%{_bindir}/update-gtk-immodules %{_host} || :
%postun gtk2
%{_bindir}/update-gtk-immodules %{_host} || :
%post gtk3
%{_bindir}/gtk-query-immodules-3.0-%{__isa_bits} --update-cache || :
%postun gtk3
%{_bindir}/gtk-query-immodules-3.0-%{__isa_bits} --update-cache || :

因此我们在做多版本支持的时候完全不需要在为 Fedora 操心,直接沿用 openSUSE 的就可以。

如何设置系统默认的输入法框架

设置系统默认的输入法框架一般分为两个环节,第一,开机预装,第二,优先使用。

各个发行版有自己不同的做法,openSUSE 在这个方面显得比较智能一些。

开机预装

注意: 开机预装需要发邮件到 opensuse factory 邮件列表或相关的邮件列表讨论必要性。你必须证明装它是必须的,大多数用户都需要的。而不是自己写上去,提交,对方审核者如果通过就可以。

大家都知道把一个软件包提交进 openSUSE:Factory 之后,就代表着它进入了官方 OSS 源。新用户开机直接就能通过 YaST2/zypper 安装,但是我们如何让用户在装机的时候就自动的装上呢?

这是利用 Provides 标签和 locales 的一个 workaround,也是 openSUSE 标准做法。

除非你不选 locale,但这是不可能的,否则即使是 en_US 这样的 locale 也可以预装部分软件包。至于最最基础的软件包,是无需用这种办法的,因为你必须要选择一个桌面环境,基础软件包都是这些桌面环境的依赖。
Provides: locale(zh_CN;zh_TW;)

就是这么简单。还有一种高级 Provides:

Provides: locale(fcitx:zh_CN;)

意思是:只有在 fcitx 软件包存在,且 locale 是 zh_CN 的情况下才会安装本软件包。

比如 fcitx-cloudpinyin 这个软件包,对于台湾用户它是没用的,因为第一台湾不用拼音,第二我们在线查询的拼音都是对应简体中文的。所以就可以用这种高级 Provides。

另外需要注意这种方法如果使用不当,会和 RPM 的依赖关系自动解决产生非常严重的冲突,甚至能造成 SuSE Studio 无法生成 DVD ISO。

比如正常的情况是,fcitx-cloudpinyin 这个软件包 Requires: fcitx,但如果你给 fcitx 加上了 Provides: locale(fcitx-cloudpinyin:zh_CN;) 那就会出现相互 Requires 的问题。

下面是一些常用的高级 Provides:

//在 KDE 桌面环境且 zh_CN 语言区域的情况下,安装本软件包
Provides: locale(kdebase4:zh_CN;)
//在 GNOME 桌面环境且 zh_CN 语言区域的环境下,安装本软件包
Provides: locale(libgnome:zh_CN;)

优先使用