<style>
details { border: 1px solid #666; padding: 8px; background: rgba(128, 128, 160, .05); }
</style>
# How to port an IME to IBus
> ... in Vala!
Reference impls:
* ibus 1.5.26 (Ubuntu 22.04)
* https://github.com/openvanilla/McBopomofoWeb
* https://github.com/openvanilla/fcitx5-mcbopomofo
Additional testing environments:
* ibus 1.5.27 (Fedora 37) with Xfce4 and GNOME wayland
## Screenshots of McBopomofo on Mac
<figure style="display: flex; align-items: flex-start; flex-wrap: wrap;">
<img style="display: block" src="https://i.imgur.com/07ezQJy.png" width="50%">
<img style="display: block" src="https://i.imgur.com/HeHwqSK.png" width="50%">
<img style="display: block" src="https://i.imgur.com/pPbR4I1.png" width="50%">
<img style="display: block" src="https://i.imgur.com/8BoaDKd.png" width="50%">
</figure>
## Subprojects
* <https://github.com/qbane/gnome-disable-ibus-manager>: For raw IBus development experience
> TODO:
js/ui/status/keyboard.js: the controller indicating current input source is called `InputSourceSettings`.
>
> when setting global engine by clicking on menu, `activateInputSource` calls `this._ibusManager.setEngine(engine);` and then `_setEngine` sets global engine (but not per-window engine?), but that is not effective, needs further diagnosis:
>
> set_global_engine_async will be called twice, one in `_sourcesChanged` is correct, and the other `_setPerWindowInputSource` is reverting it back.
>
> <details>
>
> ```
> JS ERROR: Gio.DBusError: GDBus.Error:org.freedesktop.DBus.Error.Failed: Set global engine failed: 操作已被取消#012_promisify/proto[asyncFunc]/</<@resource:///org/gnome/dules/core/overrides/Gio.js:425:45
> ### Promise created here: ###
> setEngine@resource:///org/gnome/shell/misc/ibusManager.js:293:30
> activateInputSource@resource:///org/gnome/shell/ui/status/keyboard.js:480:31
> _emit@resource:///org/gnome/gjs/modules/core/_signals.js:114:47
> activate@resource:///org/gnome/shell/ui/status/keyboard.js:58:14
> _inputSourcesChanged@resource:///org/gnome/shell/ui/status/keyboard.js:613:33
> _ibusReadyCallback@resource:///org/gnome/shell/ui/status/keyboard.js:371:14
> _emit@resource:///org/gnome/gjs/modules/core/_signals.js:114:47
> _updateReadiness@/home/qbane/.local/share/gnome-shell/extensions/disable-ibus-manager@qbane.github.io/extension.js:45:14
> _initEngines@resource:///org/gnome/shell/misc/ibusManager.js:178:18
> async*_onConnected@resource:///org/gnome/shell/misc/ibusManager.js:166:14
> ```
>
> </details>
>
> GNOME seems to ignore global/per-window settings in ibus-setup, too
* <https://github.com/qbane/mcbopomofo-core>: Maintained as a submodule.
* <https://glib-doc.netlify.app/>: GLib docs from Fedora 37.
## Vala docs
gir:
* Replace ` +<constant name="[\w_]+"\s+value="\d+"\s+c:type="IBUS_[\w_]+"\s*>\s+<source-position\s+filename="ibuskeysyms-compat.h"\s+line="\d+"/>\s+<type\s+name="gint"\s+c:type="gint"/>\s+</constant>\n` to empty
* Replace ` +<constant name="KEY_[\w_]+"\s+value="\d+"\s+c:type="IBUS_KEY_[\w_]+">\s+<source-position filename="ibuskeysyms.h" line="\d+"/>\s+<type name="gint" c:type="gint"/>\s+</constant>\n`
* Some entries with comments are removed manually
vapi:
* Replace `\t+\[CCode .+, cname = "IBUS_[\w_]+"\)]\n\s+public const int [@\w]+;\n` to empty
see https://gist.github.com/andy0130tw/dc28fbeca31cde988475c60682aa55c2
## Caveats
### Installation without root permission
See: https://github.com/ibus/ibus/blob/1.5.27/src/ibusregistry.c#L271
### Lifecycle of ibus engines
Every implementation has different handling on various lifecycle signals, in the following order. The document tells nothing about what the engine is expected to do.
* construct (the GObject-style construction)/destroy
* focus_in/focus_out
* enable/disable
* reset
e.g. [ibus-skk](https://github.com/ueno/ibus-skk/blob/master/src/engine.vala#L449) implements all, but [ibus-rime](https://github.com/rime/ibus-rime/blob/master/rime_engine.c#L186) implements only focus_in and disable. Hmm. Should be interesting to organize them in a table. [ibus-chewing](https://github.com/definite/ibus-chewing/blob/master/src/IBusChewingEngine-signal.c#L41). [ibus-libzhuyin](https://github.com/libzhuyin/ibus-libzhuyin/blob/main/src/ZYZZhuyinEngine.cc#L153).
The trace on my machine (excerpted):
```
14:56:40.333: engine.vala:37: construct!
14:56:40.571: engine.vala:462: focus_in!
14:56:40.574: engine.vala:476: enable!
14:56:40.585: engine.vala:545: set_capabilities 41!
14:56:40.590: engine.vala:556: set_content_type 10 0!
14:56:57.259: engine.vala:466: focus_out!
14:56:57.310: engine.vala:462: focus_in!
14:56:57.379: engine.vala:466: focus_out!
14:56:57.389: engine.vala:480: disable!
14:56:57.410: engine.vala:90: destruct!
14:57:08.350: engine.vala:37: construct!
14:57:08.375: engine.vala:462: focus_in!
14:57:08.378: engine.vala:476: enable!
14:57:08.379: engine.vala:545: set_capabilities 41!
14:57:08.383: engine.vala:556: set_content_type 10 0!
14:57:08.386: engine.vala:466: focus_out!
14:57:08.391: engine.vala:462: focus_in!
```
### what is ibus-portal?
* [在 Linux 上用 Telegram Desktop 打不到中文? -- 修正 Telegram Desktop 及其他 Qt 軟件在 GNOME Wayland 下的 iBus 中文輸入問題](https://medium.com/hong-kong-linux-user-group/%E4%BF%AE%E6%AD%A3-telegram-desktop-%E5%8F%8A%E5%85%B6%E4%BB%96-qt-%E8%BB%9F%E4%BB%B6%E5%9C%A8-gnome-wayland-%E4%B8%8B%E7%9A%84-ibus-%E4%B8%AD%E6%96%87%E8%BC%B8%E5%85%A5%E5%95%8F%E9%A1%8C-797abc906c3d)
* [Linux 桌面環境, 輸入法 -- 為自己在 Linux Desktop 上, SCIM 輸入法可用性提升的研究做個記錄](https://nekonya.cyou/2022/08/23/linux-zhuo-mian-huan-jing-shu-ru-fa/)
### d-feet:
Need a patch to test ibus: https://bugzilla.redhat.com/attachment.cgi?id=910263&action=diff
# Implementations
https://github.com/ibus/ibus-tmpl (enchant)
https://github.com/libzhuyin
https://github.com/rime/ibus-rime
ibus-skk
https://github.com/ueno/ibus-skk (also in Vala!)
Backed by: https://github.com/ueno/libskk
IBusChewing
https://github.com/definite/ibus-chewing
Backed by: https://github.com/chewing/libchewing
GOB:
https://github.com/definite/ibus-chewing/blob/master/src/IBusChewingEngine.gob
* no cursor in lookup table, but reacts to cursor click
* used gconf and then migrated to dconf
Handmade:
https://github.com/fourdollars/ibus-zhuyin
# Upstream issues
## Lack of input mode memoization
Currently only the name of the engine is remembered (Q: is it done by panel or im module?), but nothing else. Engines are destroyed (either by panel or global engine) and recreated after a focus out, if not set to global. Input context IDs, however, remain persistent.
One may request current_input_context when `focus-in`, but sorry this may cause a deadlock :(
On the other hand, fcitx has "per-program" state in addition to per-window state. This is called `propertyPropagatePolicy` (`All`, `Program`, `No`), controlled with key `shareInputState` in settings.
> * In GTK 2/3/4: https://github.com/fcitx/fcitx5-gtk/blob/4e0e25458caf62c90890410a7434b212b7a56072/gtk3/fcitximcontext.cpp#L445
> * In Qt 4/5: https://github.com/fcitx/fcitx5-qt/blob/1d50b4e6e101545e56d0fba4b7994938fd56101d/qt5/dbusaddons/fcitxqtinputcontextproxy_p.h#L104
* https://github.com/ibus/ibus/issues/1679#issuecomment-569178649
* https://github.com/ibus/ibus/issues/2100
Q: There is a similar intention in ibus: https://github.com/ibus/ibus/commit/92771d0e2cc7e5c12dc4312eff2a106b860deb77. Only GTK program names are exposed, not Qt4/5/XIM. Is this going to help? (v1.5.27+)
> The only occurrences I could find (via SourceGraph):
> * Only used in debug messages... ([issue](https://github.com/mike-fabian/ibus-typing-booster/issues/328#issuecomment-1170920907))
> https://github.com/mike-fabian/ibus-typing-booster/blob/2.19.12/engine/hunspell_table.py#L6343
> * Use to determine client version only
> https://github.com/PhilippeRo/IBus-Speech-To-Text/blob/5087bed51fff394213df237257de4a894649d102/engine/sttengine.py#L319
> * See source code of [ibus-bamboo](https://github.com/BambooEngine/ibus-bamboo) for `src/*_inspector.c`.
>
> They also use at-spi or xprop to get current window. Also async. Aren't they racy?
## Panel UI
In `ui/gtk3/candidatepanel.vala`, `move` is not working consistently when invoked synchronously ([documentation](https://valadoc.org/gtk+-3.0/Gtk.Window.move.html) says "most window managers ignore requests for initial window positions...").
The solution is to use `Idle.add/add_once`:
```vala
private void move(int x, int y) {
Idle.add(() => {
m_toplevel.move(x, y);
return false;
});
}
```
> Seems that in GNOME wayland, the preedit text always shows at (0, 0), making it nearly unusable. Need further diagnosis.
## Pre-edit text
(spec?) GNOME sets `EmbedPreeditText` property on ibus to `true` and does not sync it from dconf. We have to subscribe to it in order to test the integration (hinted from: https://hev.cc/2788.html)
In Vala it looks like:
```vala
bus.get_connection().signal_subscribe(
/* sender */ null,
/* ifname */ "org.freedesktop.DBus.Properties",
/* member */ "PropertiesChanged",
/* objpath */ "/org/freedesktop/IBus",
/* arg0 */ null,
DBusSignalFlags.NONE,
(_conn, _sender, _objpath, _ifname, _signame, args) => {
VariantIter dict;
// [String, dict of {string, variant}, String[]]
args.get("(sa{sv}as)", null, out dict, null);
string key;
Variant val;
while (dict.next("{sv}", out key, out val)) {
if (key == "EmbedPreeditText") {
debug("%s is set to [%s]", key, val.get_boolean().to_string());
}
}
});
```
Moreover, GNOME do not draw any text attrs either when embed-preedit-text is **off**
👉 in this case, inserting our own caret character is required.
# TODOs
- [ ] configurable lookup table selection/key
- [ ] actual cursor & preselect in lookup table
- [x] double check caret (gnome/ibus-native) & (embed/no-embed)
- [ ] persist and shared LM between sessions
- [ ] restore passthrough/width status in the same application (but is this possible?)
- [ ] configurations through dconf
- [x] property panel
- [ ] integrate opencc (chttrans in fcitx)
- [x] fork libmcbpmf and make it redistributable
- [x] config.h on Meson
- [ ] xdg directory data storage -- `get_user_data_dir`
- [ ] Vala bindings / redesign APIs
- [ ] traditional bpmf mode
- [ ] keyboard layout within Bpmf mode (see https://github.com/rime/ibus-rime/issues/15), layout of passthrough mode uses system default, but this disrupts some bpmf mode (e.g., 大千, the "standard" layout) that uses a Latin-agnostic layout
- [ ] setup UI
- [ ] i18n (https://www.rocksaying.tw/archives/15171511.html)
---
## Extension points
- [ ] user phrases
- [ ] excluded phrases
- [ ] associated
- [ ] replacement
- [ ] external converters
## Preference toggles
# Draft
<details>
How can I expand macros? `gcc -E ../src/api/McBopomofoApi.h $(pkg-config --cflags glib-2.0) > out.h` but the output is not understood by scanner.
then
`g-ir-scanner ../src/api/*.h --namespace mcbpmf_api --library src/libMcBopomofoLib.a --pkg glib-2.0 --warn-all > mcbpmf_api.gir`
Caddyfile for custom Valadoc
```
:7777 {
root * ./valadoc.org
route {
# Add trailing slash for directory requests
@canonicalPath {
file {path}/index.php
not path */
}
redir @canonicalPath {http.request.orig_uri.path}/ 308
# If the requested file does not exist, try index files
@indexFiles file {
try_files {path} {path}/index.php index.php
split_path .php
}
rewrite @indexFiles {file_match.relative}
# Proxy PHP files to the FastCGI responder
@phpFiles path *.php
reverse_proxy @phpFiles localhost:9000 {
transport fastcgi {
split .php
}
}
}
file_server browse
}
```
occational `ibus_serializable_deserialize_object: assertion 'g_type_is_a (type, IBUS_TYPE_SERIALIZABLE)' failed`...?
https://github.com/ibus/ibus/wiki/Wayland-Colors#current-engines-preedit-color
stateful with `ibusbus.current_input_context`?
then `ibusinputcontext.get_input_context` listening for its destroy signal?
ibus-chewing's logic of layout conversion ("keycode-to-keysym"):
* if passthrough -> no conversion
* if ibus's pref is use system layout -> no conversion
* `ibus_keymap_lookup_keysym` with US layout, use its result iff != voidSymbol
* no conversion otherwise
I think it is quite decent. But mcbpmf seems to differentiate the layout further. For example, it makes no sense to use 大千 layout in Colemak.
See [their wiki](https://github.com/openvanilla/McBopomofo/wiki/%E4%BD%BF%E7%94%A8%E8%AA%AA%E6%98%8E_%E9%80%B2%E9%9A%8E%E9%8D%B5%E7%9B%A4%E9%85%8D%E7%BD%AE) for detailed workaround on Mac.
</details>
# Design choice: Core / impl??
I choose to follow suit of libchewing: Keep the key handler in the core, and handle the candidate popup in the implementation.
Doc of libchewing: https://qbane.keybase.pub/libchewing-doc.html. I cannot find it online, so I build and publish my copy.