<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.