同步: 最近刚来渠道, 方案将自己之前在其他地方发的文章转移 (同步) 过来.
首发日期2024-02-12
, 以下为原文内容.
ibus 是一种 GNU/Linux 操作体系的输入法结构. GNOME 桌面环境从 3.6 版别开端直接集成了 ibus, 所以在 GNOME 中运用 ibus 比较便利, 整体体验也比较好.
可是 ibus 的文档写的欠好 (特别是几乎没有中文文档, 这点有必要差评), 许多东西有必要通过阅览源代码才干明白. 可是 ibus 运用 C 编程言语, GObject 以及 D-Bus, 这些结合起来使得阅览 ibus 的源代码比较费力.
本文主要内容是 ibus 架构简介和发动初始化部分.
相关链接:
- github.com/ibus/ibus
- www.freedesktop.org/wiki/Softwa…
- docs.gtk.org/gobject/
- help.gnome.org/misc/releas…
- github.com/libpinyin/i…
目录
-
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 设置里面设置输入法了:
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-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 <alexepico@gmail.com></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 <alexepico@gmail.com>
Peng Huang <shawn.p.huang@gmail.com>
BYVoid <byvoid1@gmail.com>
</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>拼</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 <alexepico@gmail.com>
Peng Huang <shawn.p.huang@gmail.com>
BYVoid <byvoid1@gmail.com>
</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>ㄉ</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 地址填进去, 开端抓包:
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-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
按下的时分, 输入汉字 啊
.
编译之后发动:
输入测试:
对应的原始输入: cbacbacbacbaqaqaqaaaa
本文运用 CC-BY-SA 4.0 答应发布.