同步: 最近刚来渠道, 方案将自己之前在其他地方发的文章转移 (同步) 过来.

首发日期2024-02-12, 以下为原文内容.


ibus 是一种 GNU/Linux 操作体系的输入法结构. GNOME 桌面环境从 3.6 版别开端直接集成了 ibus, 所以在 GNOME 中运用 ibus 比较便利, 整体体验也比较好.

可是 ibus 的文档写的欠好 (特别是几乎没有中文文档, 这点有必要差评), 许多东西有必要通过阅览源代码才干明白. 可是 ibus 运用 C 编程言语, GObject 以及 D-Bus, 这些结合起来使得阅览 ibus 的源代码比较费力.

本文主要内容是 ibus 架构简介和发动初始化部分.


相关链接:

目录

  • 1 ibus 架构简介

    • 1.1 装置 ibus
    • 1.2 systemd user 服务
    • 1.3 D-Bus 多进程架构
    • 1.4 输入法接口模块
  • 2 ibus 源码剖析

    • 2.1 下载 ibus 源代码
    • 2.2 D-Bus 地址
    • 2.3 运用 Bustle 抓包剖析
    • 2.4 从 SetGlobalEngine 作为进口剖析
    • 2.5 从 ibus-libpinyin 入手进行剖析
  • 3 运用 rust 完成 ibus 输入法

    • 3.1 获取 D-Bus 地址
    • 3.2 衔接 ibus
    • 3.3 注册 engine factory
    • 3.4 完成 CreateEngine
    • 3.5 简略输入法

1 ibus 架构简介

1.1 装置 ibus

操作体系: ArchLinux

  • 装置命令:

    > sudo pacman -S ibus ibus-libpinyin
    

    其间 ibus 是输入法结构, ibus-libpinyin 是一个相对比较好用的拼音输入法.

  • 软件包版别:

    > pacman -Ss ibus
    extra/ibus 1.5.29-3 [已装置]
        Next Generation Input Bus for Linux
    extra/ibus-libpinyin 1.15.3-1 [已装置]
        Intelligent Pinyin engine based on libpinyin for IBus
    extra/libibus 1.5.29-3 [已装置]
        IBus support library
    

重启, 然后就能够直接在 GNOME 设置里面设置输入法了:

(同步) ibus 源代码阅览 (1)

1.2 systemd user 服务

查看 ibus 软件包有哪些文件 (省掉一部分):

> pacman -Ql ibus
ibus /usr/
ibus /usr/bin/
ibus /usr/bin/ibus
ibus /usr/bin/ibus-daemon
ibus /usr/bin/ibus-setup
ibus /usr/lib/ibus/
ibus /usr/lib/ibus/ibus-dconf
ibus /usr/lib/ibus/ibus-engine-simple
ibus /usr/lib/ibus/ibus-extension-gtk3
ibus /usr/lib/ibus/ibus-portal
ibus /usr/lib/ibus/ibus-ui-emojier
ibus /usr/lib/ibus/ibus-ui-gtk3
ibus /usr/lib/ibus/ibus-wayland
ibus /usr/lib/ibus/ibus-x11

除了 ibus 可执行文件 (比方 ibus-daemon) 之外, 重要的是 systemd 装备文件:

ibus /usr/lib/systemd/
ibus /usr/lib/systemd/user/
ibus /usr/lib/systemd/user/gnome-session.target.wants/
ibus /usr/lib/systemd/user/gnome-session.target.wants/org.freedesktop.IBus.session.GNOME.service
ibus /usr/lib/systemd/user/org.freedesktop.IBus.session.GNOME.service

查看 service 文件:

> cat /usr/lib/systemd/user/org.freedesktop.IBus.session.GNOME.service
[Unit]
Description=IBus Daemon for GNOME
CollectMode=inactive-or-failed
# Require GNOME session and specify startup ordering
Requisite=gnome-session-initialized.target
After=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
Before=gnome-session.target
# Needs to run when DISPLAY/WAYLAND_DISPLAY is set
After=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
# Never run in GDM
Conflicts=gnome-session@gnome-login.target
[Service]
Type=dbus
# Only pull --xim in X11 session, it is done via Xwayland-session.d on Wayland
ExecStart=sh -c 'exec /usr/bin/ibus-daemon --panel disable $([ "$XDG_SESSION_TYPE" = "x11" ] && echo "--xim")'
Restart=on-abnormal
BusName=org.freedesktop.IBus
TimeoutStopSec=5
Slice=session.slice
[Install]
WantedBy=gnome-session.target

当用户登录进入 GNOME 桌面环境时 (对应 systemd gnome-session.target), ibus 服务 (org.freedesktop.IBus.session.GNOME.service) 作为依靠项会被发动, 从而执行 /usr/bin/ibus-daemon.

假如一起有多个用户登录, 每个用户都会运转自己的 ibus 服务.

1.3 D-Bus 多进程架构

D-Bus 是一种总线型的进程间通信 (IPC) 协议, 现已发展许多年了, 在 Linux 桌面环境广泛运用.

D-Bus 通常有 2 种总线: 体系 (system) 总线和会话 (session) 总线. 体系总线便是整个体系有一个, 用于体系服务. 会话总线是每个登录的用户有一个, 用于用户自己的服务.

那么猜猜看, ibus 用的是哪个 D-Bus 总线 ?

答案是, 都不是 ! ibus 运用一条自己创立的 D-Bus 总线.

每条 D-Bus 总线有一个接口地址, 运用的是 UNIX socket, 便是表现为文件体系中的一个文件. ibus 将其总线地址写入一个文件:

> cat ~/.config/ibus/bus/*-unix-wayland-0
# This file is created by ibus-daemon, please do not modify it.
# This file allows processes on the machine to find the
# ibus session bus with the below address.
# If the IBUS_ADDRESS environment variable is set, it will
# be used rather than this file.
IBUS_ADDRESS=unix:path=/home/s2/.cache/ibus/dbus-9T2iv1UE,guid=a282405d82905fe34f2770cc65c8668f
IBUS_DAEMON_PID=88314

这个文件坐落 ~/.config/ibus/bus 目录, 当 ibus-daemon 发动时会写这个文件.

其间 IBUS_ADDRESS= 后边的一串东西, 便是 D-Bus 总线的地址. IBUS_DAEMON_PID= 写的是 ibus-daemon 的进程号.

咱们先看一下 D-Bus 总线接口对应的文件:

> ls -l ~/.cache/ibus
srwxr-xr-x 1 s2 s2   0  2月11日 14:17 dbus-9T2iv1UE=
srwxr-xr-x 1 s2 s2   0  2月10日 21:11 dbus-HYqDsOMp=
srwxr-xr-x 1 s2 s2   0  2月10日 21:16 dbus-jx9gKPt5=

其间 dbus-9T2iv1UE 便是 unix socket 文件, 还有一些其他没用的垃圾文件. 因为 ibus-daemon 每次发动都会随机生成一个文件名, 发动次数多了就会留下一堆垃圾 (窝觉得这是 ibus 的一个很欠好的规划).

咱们再来看一下 ibus 相关的进程:

> ps -elwwf | grep 88314
0 S s2        88314    1061  0  80   0 - 115614 do_sys 14:17 ?       00:02:45 /usr/bin/ibus-daemon --verbose --panel disable
0 S s2        88321   88314  0  80   0 - 77566 do_sys 14:17 ?        00:00:00 /usr/lib/ibus/ibus-dconf
0 S s2        88322   88314  0  80   0 - 115321 do_sys 14:17 ?       00:00:47 /usr/lib/ibus/ibus-extension-gtk3
0 S s2        88340   88314  0  80   0 - 59169 do_sys 14:17 ?        00:00:27 /usr/lib/ibus/ibus-engine-simple
0 S s2        90835   88314  0  80   0 - 115480 do_sys 14:33 ?       00:00:31 /usr/lib/ibus-libpinyin/ibus-engine-libpinyin --ibus

运用 systemctl 能够更明晰的看出多进程结构:

> systemctl --user status org.freedesktop.IBus.session.GNOME
● org.freedesktop.IBus.session.GNOME.service - IBus Daemon for GNOME
     Loaded: loaded (/home/s2/.config/systemd/user/org.freedesktop.IBus.session.GNOME.service; disabled; preset: enabled)
     Active: active (running) since Sun 2024-02-11 14:17:51 CST; 7h ago
   Main PID: 88314 (ibus-daemon)
      Tasks: 23 (limit: 14200)
     Memory: 163.4M (peak: 172.1M)
        CPU: 4min 37.382s
     CGroup: /user.slice/user-1000.slice/user@1000.service/session.slice/org.freedesktop.IBus.session.GNOME.service
             ├─88314 /usr/bin/ibus-daemon --verbose --panel disable
             ├─88321 /usr/lib/ibus/ibus-dconf
             ├─88322 /usr/lib/ibus/ibus-extension-gtk3
             ├─88340 /usr/lib/ibus/ibus-engine-simple
             └─90835 /usr/lib/ibus-libpinyin/ibus-engine-libpinyin --ibus

ibus-daemon 是 ibus 的管理进程, 首要发动, 并创立 D-Bus 总线. ibus 的多个组件, 别离作为一个进程发动. 比方 ibus-engine-libpinyin 便是一个拼音输入法. 多个进程之间运用 D-Bus 通信.

(同步) ibus 源代码阅览 (1)

咱们来看一下 ibus-libpinyin 软件包有哪些文件:

> pacman -Ql ibus-libpinyin
ibus-libpinyin /usr/
ibus-libpinyin /usr/lib/
ibus-libpinyin /usr/lib/ibus-libpinyin/
ibus-libpinyin /usr/lib/ibus-libpinyin/ibus-engine-libpinyin
ibus-libpinyin /usr/lib/ibus-libpinyin/ibus-setup-libpinyin
ibus-libpinyin /usr/share/ibus/
ibus-libpinyin /usr/share/ibus/component/
ibus-libpinyin /usr/share/ibus/component/libpinyin.xml

重点是最终这个 xml 文件, 这个输入法将自己注册给 ibus:

> cat /usr/share/ibus/component/libpinyin.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- filename: pinyin.xml -->
<component>
	<name>org.freedesktop.IBus.Libpinyin</name>
	<description>Libpinyin Component</description>
	<exec>/usr/lib/ibus-libpinyin/ibus-engine-libpinyin --ibus</exec>
	<version>1.15.3</version>
	<author>Peng Wu &lt;alexepico@gmail.com&gt;</author>
	<license>GPL</license>
	<homepage>https://github.com/libpinyin/ibus-libpinyin</homepage>
	<textdomain>ibus-libpinyin</textdomain>
	<engines>
		<engine>
			<name>libpinyin</name>
			<language>zh_CN</language>
			<license>GPL</license>
			<author>
                        Peng Wu &lt;alexepico@gmail.com&gt;
                        Peng Huang &lt;shawn.p.huang@gmail.com&gt;
                        BYVoid &lt;byvoid1@gmail.com&gt;
                        </author>
			<icon>/usr/share/ibus-libpinyin/icons/ibus-pinyin.svg</icon>
			<layout>default</layout>
			<longname>Intelligent Pinyin</longname>
			<description>Intelligent Pinyin input method</description>
			<rank>99</rank>
			<symbol>&#x62FC;</symbol>
			<icon_prop_key>InputMode</icon_prop_key>
			<setup>/usr/lib/ibus-libpinyin/ibus-setup-libpinyin libpinyin</setup>
			<textdomain>ibus-libpinyin</textdomain>
		</engine>
		<engine>
			<name>libbopomofo</name>
			<language>zh_TW</language>
			<license>GPL</license>
			<author>
                        Peng Wu &lt;alexepico@gmail.com&gt;
                        Peng Huang &lt;shawn.p.huang@gmail.com&gt;
                        BYVoid &lt;byvoid1@gmail.com&gt;
                        </author>
			<icon>/usr/share/ibus-libpinyin/icons/ibus-bopomofo.svg</icon>
			<layout>default</layout>
			<longname>Bopomofo</longname>
			<description>Bopomofo input method</description>
			<rank>98</rank>
			<symbol>&#x3109;</symbol>
			<icon_prop_key>InputMode</icon_prop_key>
			<setup>/usr/lib/ibus-libpinyin/ibus-setup-libpinyin libbopomofo</setup>
			<textdomain>ibus-libpinyin</textdomain>
		</engine>
	</engines>
</component>

这个文件界说了输入法的一些信息, 比方称号, 言语等, 重要的部分是:

<component>
  <name>org.freedesktop.IBus.Libpinyin</name>
  <description>Libpinyin Component</description>
  <exec>/usr/lib/ibus-libpinyin/ibus-engine-libpinyin --ibus</exec>

这儿界说组件 (component) 称号 org.freedesktop.IBus.Libpinyin (后边要用到), 以及发动这个组件对应的可执行程序 /usr/lib/ibus-libpinyin/ibus-engine-libpinyin.

<engine>
  <name>libpinyin</name>
  <language>zh_CN</language>

这儿界说了一个详细的拼音输入法 (engine), 称号为 libpinyin (后边要用到), 言语 zh_CN (简体中文).

  <longname>Intelligent Pinyin</longname>

这个界说的是用户界面显示的输入法称号 (“智能拼音”).

1.4 输入法接口模块

ibus 作为一种输入法结构, 最重要的当然是提供输入功能了. 比方, 向某个应用中输入汉字.

可是因为 Linux 体系开源敞开的特色, 不同的应用运用不同的输入规范. 输入法结构需要对每种情况别离适配:

> ls ibus/client
gtk2/  gtk3/  gtk4/  Makefile  Makefile.am  Makefile.in  wayland/  x11/

这是 ibus 源代码的一个目录. 能够看到 ibus 别离对 x11, gtk2, gtk3, gtk4, wayland 等的支撑代码.

> pacman -Ql ibus
ibus /usr/lib/
ibus /usr/lib/gtk-2.0/
ibus /usr/lib/gtk-2.0/2.10.0/
ibus /usr/lib/gtk-2.0/2.10.0/immodules/
ibus /usr/lib/gtk-2.0/2.10.0/immodules/im-ibus.so
ibus /usr/lib/gtk-3.0/
ibus /usr/lib/gtk-3.0/3.0.0/
ibus /usr/lib/gtk-3.0/3.0.0/immodules/
ibus /usr/lib/gtk-3.0/3.0.0/immodules/im-ibus.so
ibus /usr/lib/gtk-4.0/
ibus /usr/lib/gtk-4.0/4.0.0/
ibus /usr/lib/gtk-4.0/4.0.0/immodules/
ibus /usr/lib/gtk-4.0/4.0.0/immodules/libim-ibus.so
ibus /usr/lib/ibus/ibus-wayland
ibus /usr/lib/ibus/ibus-x11

编译之后就成了这些不同的输入模块.

2 ibus 源码剖析

注意: 本章节会张贴一些 ibus 的代码片段, 这些属于合理引证. 本文现已注明出处, 不侵犯版权.

2.1 下载 ibus 源代码

运用 git 下载 ibus 的源代码:

> git clone https://github.com/ibus/ibus --single-branch --depth=1

顺便把 ibus-libpinyin 的源代码也下载了, 后边有用:

> git clone https://github.com/libpinyin/ibus-libpinyin --single-branch --depth=1

命令行参数 --single-branch --depth=1 表明只下载一个分支 (默许分支), 只下载一个提交 (最新提交). 假如不需要 git 提交历史的话, 这样下载能够明显削减需要下载的数据, 特别合适网络情况较差的时分.

然后建议运用 vscode 打开目录, 便利阅览源代码.

(假如不想运用 git 的话, 在网页上点击 “下载 zip” 进行代码下载, 效果是一样的. )

2.2 D-Bus 地址

  • 源文件: ibus/src/ibusshare.h 函数: ibus_get_address()

    ibusshare.h 是 C 言语的头文件, 主要是一些界说. 对应的 ibusshare.c 文件是完成代码, 能够对照着一起看.

    /**
    * ibus_get_address:
    *
    * Return the D-Bus address of IBus.
    * It will find the address from following source:
    * <orderedlist>
    *    <listitem><para>Environment variable IBUS_ADDRESS</para></listitem>
    *    <listitem><para>Socket file under ~/.config/ibus/bus/</para></listitem>
    * </orderedlist>
    *
    * Returns: D-Bus address of IBus. %NULL for not found.
    *
    * See also: ibus_write_address().
    */
    const gchar     *ibus_get_address       (void);
    

    依据此处的注释, 这个函数获取 ibus 的 D-Bus 地址. 首要尝试从环境变量 IBUS_ADDRESS 获取, 其次从 ~/.config/ibus/bus/ 目录下面的文件中获取.

    要害完成代码:

      /* get address from env variable */
      address = g_strdup (g_getenv ("IBUS_ADDRESS"));
      if (address)
          return address;
      /* read address from ~/.config/ibus/bus/soketfile */
      pf = fopen (ibus_get_socket_path (), "r");
    

    此处的逻辑和上面注释中的共同, 首要读取环境变量, 然后读取目录中的文件.

          /* skip comment line */
          if (p[0] == '#')
              continue;
          /* parse IBUS_ADDRESS */
          if (strncmp (p, "IBUS_ADDRESS=", sizeof ("IBUS_ADDRESS=") - 1) == 0) {
              gchar *head = p   sizeof ("IBUS_ADDRESS=") - 1;
              for (p = head; *p != 'n' && *p != ''; p  );
              if (*p == 'n')
                  *p = '';
              g_free (address);
              address = g_strdup (head);
              continue;
          }
    

    首要疏忽注释 (以 # 最初的行), 然后假如找到 IBUS_ADDRESS= 那么后边的一堆便是所需地址.

    能够看到, C 言语对字符串进行处理是比较麻烦的.

剖析完毕, 能够看到, 代码中对 D-Bus 地址处理的逻辑, 和前面 (章节 1.3) 描绘的共同.

2.3 运用 Bustle 抓包剖析

ibus 运用 D-Bus 这种松散耦合的多进程结构, 有一个额外的好处, 那便是 D-Bus 是能够抓包剖析的 ! 因为 D-Bus 就像总线一样转发通过的音讯.

装置 bustle, 这个软件能够便利对 D-Bus 进行抓包剖析:

> sudo pacman -S bustle

bustle 也有 flatpak 版: flathub.org/zh-Hans/app…

发动 bustle, 把 ibus 的 D-Bus 地址填进去, 开端抓包:

(同步) ibus 源代码阅览 (1)

2.4 从 SetGlobalEngine 作为进口剖析

通过屡次尝试后发现, 每次切换输入法的时分, bustle 就会抓到一条 SetGlobalEngine 音讯.

那么咱们就从 SetGlobalEngine 开端剖析吧.

  • (1) 源文件: ibus/bus/ibusimpl.c 函数: bus_ibus_impl_service_method_call()

          { "SetGlobalEngine",       _ibus_set_global_engine },
    

    也便是 SetGlobalEngine 实际调用函数 _ibus_set_global_engine().

    同一个文件找到这个函数, 发现它调用函数 bus_input_context_set_engine_by_desc().

  • (2) 源文件: ibus/bus/inputcontext.c 函数: bus_input_context_set_engine_by_desc()

    发现它调用函数 bus_engine_proxy_new().

  • (3) 源文件: ibus/bus/engineproxy.c 函数: bus_engine_proxy_new()

      data->factory = bus_component_get_factory (data->component);
      if (data->factory == NULL) {
          // 省掉
          bus_component_start (data->component, g_verbose);
      } else {
          // 省掉
          bus_factory_proxy_create_engine (
                  data->factory,
                  data->desc,
                  timeout,
                  cancellable,
                  (GAsyncReadyCallback) create_engine_ready_cb,
                  data);
      }
    

    发现它分情况调用函数: bus_component_start(), bus_factory_proxy_create_engine().

  • (4) 源文件: ibus/bus/component.c 函数: bus_component_start()

      if (!g_shell_parse_argv (ibus_component_get_exec (component->component),
                               &argc,
                               &argv,
                               &error)) {
          // 省掉
          return FALSE;
      }
      // 省掉
      retval = g_spawn_async (NULL, argv, NULL,
                              flags,
                              NULL, NULL,
                              &(component->pid), &error);
    

    这个函数获取组件对应的可执行程序途径, 然后发动进程.

  • (5) 源文件: ibus/bus/factoryproxy.c 函数: bus_factory_proxy_create_engine()

      g_dbus_proxy_call ((GDBusProxy *) factory,
                         "CreateEngine",
                         g_variant_new ("(s)", ibus_engine_desc_get_name (desc)),
                         G_DBUS_CALL_FLAGS_NONE,
                         timeout,
                         cancellable,
                         callback,
                         user_data);
    

    这个函数通过 D-Bus 调用 CreateEngine.


因为 C 言语代码实在太烦琐了, 不得不越过和省掉了大量代码.

(同步) ibus 源代码阅览 (1)

通过上面的一通剖析, 能够得到这样的函数调用图. 当 ibus-daemon 收到 SetGlobalEngine 恳求时, 首要查找对应 engine 的信息. 然后分情况, 假如 engine 还没有发动, 就发动对应进程 (bus_component_start()). 假如现已发动了, 就调用 CreateEngine 完成初始化.

剖析完毕, ibus-daemon 的主要行为现已清楚了.

2.5 从 ibus-libpinyin 入手进行剖析

上面是从 ibus 的视点剖析初始化的, 那么一个输入法要怎么样发动 ? 咱们来阅览 ibus-libpinyin 的源代码.

  • (1) 源文件: ibus-libpinyin/src/PYMain.cc 函数: main()

    这个是整个程序的执行进口. 文件名后缀 .cc 表明编程言语是 C (.c 表明编程言语是 C).

    调用函数 start_component(). 同一个文件找到这个函数:

    顺次调用函数:

    • ibus_init() ibus 初始化 (不重要).

    • ibus_bus_new() 衔接到 ibus-daemon (稍后剖析).

    • ibus_bus_get_config() 和装备相关 (不重要).

    • ibus_factory_new() 创立 factory (稍后剖析).

    • ibus_factory_add_engine() 增加 engine (不重要).

    • ibus_bus_request_name() 恳求称号 (稍后剖析).

    • ibus_main() 进入主循环 (不重要).

  • (2) 源文件: ibus/src/ibusbus.c 函数: ibus_bus_new()

    IBusBus *
    ibus_bus_new (void)
    {
        IBusBus *bus = IBUS_BUS (g_object_new (IBUS_TYPE_BUS,
                                              "connect-async", FALSE,
                                              "client-only", FALSE,
                                              NULL));
        return bus;
    }
    

    此处调用 g_object_new(), 会间接调用 ibus_bus_constructor(). (这个是 GObject 的知识点, 假如不清楚就无法持续剖析了. )

    同一个文件找到函数 ibus_bus_constructor(): 调用 ibus_bus_connect().

    同一个文件找到函数 ibus_bus_connect():

    • 首要调用 ibus_get_address() 获取 ibus-daemon 的 D-Bus 地址 (详见章节 2.2 的剖析).

    • 然后调用 g_dbus_connection_new_for_address_sync() 衔接这个 D-Bus 地址.

    • 然后调用 ibus_bus_connect_completed().

    同一个文件找到函数 ibus_bus_connect_completed(): 调用 ibus_bus_hello().

    同一个文件找到函数 ibus_bus_hello(): 调用 g_dbus_connection_get_unique_name().

    这个是获取当前衔接在 D-Bus 上的仅有称号.

  • (3) 源文件: ibus/src/ibusfactory.c 函数: ibus_factory_new()

    这个函数自身没啥好剖析的, 可是同文件的另一个函数 ibus_factory_class_init() 有点意思. (这个也是 GObject 的知识点)

      class->create_engine = ibus_factory_real_create_engine;
      ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), introspection_xml);
    

    这两行是要害代码.

    函数 ibus_factory_real_create_engine() 便是实际处理 CreateEngine 的. 还记得上一节剖析最终的 CreateEngine 嘛 ? ibus-daemon 在收到 SetGlobalEngine 恳求时, 最终调用 CreateEngine. 而实际上后续的工作, 便是在这儿完成初始化.

    函数 ibus_service_class_add_interfaces() 增加一个 D-Bus 服务接口. introspection_xml 便是 D-Bus 的接口 xml:

    <node>
      <interface name='org.freedesktop.IBus.Factory'>
        <method name='CreateEngine'>
          <arg direction='in'  type='s' name='name' />
          <arg direction='out' type='o' />
        </method>
      </interface>
    </node>
    

    Factory 这个接口有一个办法 CreateEngine, 参数有 1 个, 称号为 name, 类型为字符串. 回来值类型为 object path. (此处为 D-Bus 的知识点)


    同一个文件找到函数 ibus_factory_real_create_engine(): 调用 ibus_engine_new_with_type().

  • (4) 源文件: ibus/src/ibusengine.c 函数: ibus_engine_new_with_type()

    这个函数也没啥好剖析的, 可是同一个文件的另一个函数 ibus_engine_class_init(): 调用 ibus_service_class_add_interfaces().

    和上面类似, 此处的 xml 才是最重要的 (部分省掉):

    <node>
      <interface name='org.freedesktop.IBus.Engine'>
        <method name='ProcessKeyEvent'>
          <arg direction='in'  type='u' name='keyval' />
          <arg direction='in'  type='u' name='keycode' />
          <arg direction='in'  type='u' name='state' />
          <arg direction='out' type='b' />
        </method>
        <method name='SetCursorLocation'>
          <arg direction='in'  type='i' name='x' />
          <arg direction='in'  type='i' name='y' />
          <arg direction='in'  type='i' name='w' />
          <arg direction='in'  type='i' name='h' />
        </method>
    

    此处界说了 Engine 接口, 具有一堆函数 (后边省掉了).

  • (5) 源文件: ibus/src/ibusbus.c 函数: ibus_bus_request_name()

      result = ibus_bus_call_sync (bus,
                                   DBUS_SERVICE_DBUS,
                                   DBUS_PATH_DBUS,
                                   DBUS_INTERFACE_DBUS,
                                   "RequestName",
                                   g_variant_new ("(su)", name, flags),
                                   G_VARIANT_TYPE ("(u)"));
    

    此处调用 RequestName 是运用 D-Bus 的功能, 便是给自己在 D-Bus 总线上起一个固定的姓名, 便利其他程序找到自己.


(这一部分有点杂乱, 还稠浊了 GObject 和 D-Bus 的知识点, 窝就偷个懒不画图了哈 ~ )

咱们来总结一下, 输入法这边的发动初始化主要做了哪些工作:

  • (1) 获取 ibus 的 D-Bus 地址 (ibus_get_address()).

  • (2) 衔接到 D-Bus.

  • (3) 获取 D-Bus 仅有称号 (get_unique_name).

  • (4) 在 D-Bus 注册接口 org.freedesktop.IBus.Factory.

  • (5) 在 D-Bus 恳求称号 (RequestName).

3 运用 rust 完成 ibus 输入法

上面剖析了 ibus 的发动初始化进程, 让咱们写个简略的输入法来练练手吧 !

ibus 自身运用 C 言语和 python, 也便是说假如用 C/C 或 python 来编写, 能够直接运用 ibus 封装好的功能.

可是窝喜爱 rust. 假如用 rust 来写, 并且对 ibus 没有任何的直接依靠 (比方动态链接什么的), 也能进一步验证上面临 ibus 的剖析是否正确.

此处运用了 zbus: crates.io/crates/zbus 这是一个完全由 rust 编写的 D-Bus 库.

(此处只贴出了部分要害代码, 并不是完好代码. )

3.1 获取 D-Bus 地址

/// 获取 ibus 的 D-Bus (unix socket) 地址
pub fn get_ibus_addr() -> Result<String, Box<dyn Error>> {
    // 查找装备文件
    // ~/.config/ibus/bus/*-unix-wayland-0
    let home = env::var("HOME")?;
    let wd = env::var("WAYLAND_DISPLAY")?;
    let 目录: PathBuf = [home, ".config/ibus/bus".to_string()].iter().collect();
    let 文件名完毕 = format!("-unix-{}", wd);
    let 查看文件名 = |p: &PathBuf| -> bool {
        p.file_name().map_or(false, |n| {
            n.to_str().map_or(false, |s| s.ends_with(&文件名完毕))
        })
    };
    let 文件 = fs::read_dir(目录)?
        .filter_map(|i| i.ok())
        .map(|i| i.path())
        .find(查看文件名)
        .ok_or(IBusErr::new("can not find ibus addr".to_string()))?;
    // 读取装备文件, 获取里面的地址
    // 疏忽注释 (`#`)
    let 地址 = fs::read_to_string(文件.clone())?
        .lines()
        .filter(|i| !i.starts_with("#"))
        .find_map(|i| i.strip_prefix("IBUS_ADDRESS=").map(|i| i.to_string()))
        .ok_or(IBusErr::new(format!("can not find ibus addr: {:?}", 文件)))?;
    Ok(地址.to_string())
}

首要查找相应目录中的文件, 然后读取文件从中获取 D-Bus 地址.

3.2 衔接 ibus

pub async fn 衔接ibus(addr: String) -> Result<Connection, Box<dyn Error>> {
    let c = ConnectionBuilder::address(addr.as_str())?.build().await?;
    // ibus 初始化: 获取 unique_name
    let n = c
        .unique_name()
        .ok_or(IBusErr::new("can not get dbus unique_name".to_string()))?;
    Ok(c)
}

首要衔接 D-Bus 的相应地址, 然后获取仅有称号.

3.3 注册 engine factory

const IBUS_PATH_FACTORY: &'static str = "/org/freedesktop/IBus/Factory";
/// ibus 初始化: 注册 engine factory
pub async fn 注册factory<T: IBusEngine   'static, U: IBusFactory<T>   'static>(
    c: &Connection,
    f: Factory<T, U>,
) -> Result<(), Box<dyn Error>> {
    c.object_server().at(IBUS_PATH_FACTORY, f).await?;
    Ok(())
}

在固定的途径注册 factory 接口.

/// ibus 初始化: 恳求称号
pub async fn 恳求称号(c: &Connection, 称号: String) -> Result<(), Box<dyn Error>> {
    let n = WellKnownName::try_from(称号)?;
    c.request_name(n).await?;
    Ok(())
}

然后恳求称号.

3.4 完成 CreateEngine

#[dbus_interface(name = "org.freedesktop.IBus.Factory")]
impl<T: IBusEngine   'static, U: IBusFactory<T>   'static> Factory<T, U> {
    #[dbus_interface(name = "CreateEngine")]
    async fn create_engine(&mut self, name: String) -> fdo::Result<ObjectPath> {
        debug!("CreateEngine");
        let e = self
            .f
            .create_engine(name.clone())
            .map_err(|s| fdo::Error::Failed(s))?;
        let p = Engine::new(&self.c, e)
            .await
            .map_err(|e| fdo::Error::Failed(format!("{:?}", e)))?;
        let o = ObjectPath::try_from(p).map_err(|e| fdo::Error::Failed(format!("{:?}", e)))?;
        Ok(o)
    }
}

依据 engine 称号创立实例, 然后回来 object path.

impl<T: IBusEngine   'static> Engine<T> {
    /// 创立 engine (包括 ibus 初始化)
    pub async fn new(c: &Connection, e: T) -> Result<String, Box<dyn Error>> {
        let object_path = format!("/org/freedesktop/IBus/Engine/{}", 1);
        let o = Engine {
            e,
            _op: object_path.clone(),
        };
        c.object_server().at(object_path.clone(), o).await?;
        Ok(object_path)
    }
}

创立 engine, 并在某途径注册接口.

#[dbus_interface(name = "org.freedesktop.IBus.Engine")]
impl<T: IBusEngine   'static> Engine<T> {
    async fn process_key_event(
        &mut self,
        #[zbus(signal_context)] sc: SignalContext<'_>,
        keyval: u32,
        keycode: u32,
        state: u32,
    ) -> fdo::Result<bool> {
        self.e.process_key_event(sc, keyval, keycode, state).await
    }
    // 省掉
    #[dbus_interface(signal)]
    pub async fn commit_text(sc: &SignalContext<'_>, text: Value<'_>) -> zbus::Result<()>;

这是 D-Bus engine 接口的完成.

3.5 简略输入法

impl IBusEngine for PmimEngine {
    async fn process_key_event(
        &mut self,
        sc: SignalContext<'_>,
        keyval: u32,
        keycode: u32,
        state: u32,
    ) -> fdo::Result<bool> {
        debug!(
            "process_key_event(keyval, keycode, state)  {}, {}, {}",
            keyval, keycode, state
        );
        // 简略输入法
        if is_keydown(state) {
            if keyval == (b'a' as u32) {
                // 输入 `啊`
                let t = make_ibus_text("啊".to_string());
                info!("啊");
                Engine::<PmimEngine>::commit_text(&sc, t).await?;
                return Ok(true);
            } else if keyval == (b'q' as u32) {
                // 输入 `穷`
                let t = make_ibus_text("穷".to_string());
                Engine::<PmimEngine>::commit_text(&sc, t).await?;
                return Ok(true);
            }
        }
        Ok(false)
    }

这儿完成了一个非常简略的输入法, 只能输入 2 个汉字. 比方按键 a 按下的时分, 输入汉字 .

编译之后发动:

(同步) ibus 源代码阅览 (1)

输入测试:

(同步) ibus 源代码阅览 (1)

对应的原始输入: cbacbacbacbaqaqaqaaaa


本文运用 CC-BY-SA 4.0 答应发布.