One popup per session, so the frontend is picked at startup: a compositor with zwp_input_method_v2 gets that frontend and neither XIM nor the X11 popup is started. The answer for a user is one sentence -- on Wayland set none of the module variables -- and the two exceptions the plan asked to check turned out this way: GTK 4 binds text-input-v3 by itself, with GTK_IM_MODULE unset (4.22 under sway 1.12, typed and composed), and Chromium is beyond help either way, since it asks for text-input-v1 and wlroots implements only v3. The plan is deleted, as it said to be once the README described what landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
256 lines
11 KiB
Markdown
256 lines
11 KiB
Markdown
# strans
|
|
|
|
strans is a small, single-user input method for Korean, Japanese, English,
|
|
emoji, and symbols. It provides one engine for Wayland compositors that
|
|
speak `zwp_input_method_v2` — the wlroots and smithay families, so sway,
|
|
river, labwc, Wayfire, dwl, niri and COSMIC — and for IBus (which GTK 4
|
|
and Qt use), XIM, and GTK 3. Vietnamese Telex is also available as a
|
|
compatibility mode.
|
|
|
|
## Input modes
|
|
|
|
| Key | Mode |
|
|
| --- | --- |
|
|
| `Ctrl+N` | Japanese Hiragana and Kanji conversion |
|
|
| `Ctrl+K` | Japanese Katakana |
|
|
| `Ctrl+S` | Korean Hangul (2-beolsik) |
|
|
| `Ctrl+T` | English |
|
|
| `Ctrl+V` | Vietnamese Telex |
|
|
| `Ctrl+E` | Emoji and symbol search |
|
|
| `Ctrl+H` | One-shot Hanja search |
|
|
|
|
Switching mode shows the mode switched to — `A`, `한`, `あ`, `ア`, `ă` — in
|
|
the popup until the next key.
|
|
|
|
A Korean keyboard's 한/영 key toggles Korean and English, and its 한자 key
|
|
is `Ctrl+H`; a Japanese keyboard's 半角/全角 toggles Japanese and English,
|
|
ひらがな/カタカナ switches between the two kana modes, 変換 turns Japanese
|
|
on and then converts like `Space`, and 無変換 turns it off. Only a plain
|
|
`Ctrl` chord is a strans key: with `Shift`, `Alt`, or `Super` held it
|
|
commits what is pending and goes to the application, so `Ctrl+Shift+V`
|
|
still pastes.
|
|
|
|
Hiragana mode composes a complete reading, then shows its Kanji candidates
|
|
with none chosen: the exact readings of the dictionary, then the verbs and
|
|
adjectives SKK keys by stem and okurigana (かく offers 確 and 書く), and
|
|
under `Space` the reading in Katakana last, so a word the dictionary lacks
|
|
converts to Katakana with one `Space`. `Space` or `Tab` chooses the first
|
|
candidate and then steps on, wrapping (with `Shift`, back); `Up`/`Down` and
|
|
`PageUp`/`PageDown` move without wrapping. Once a candidate is chosen, an unmodified `1`-`9` commits that row of the
|
|
current page; before that the digits type. `Enter` commits the chosen candidate, or the
|
|
reading when none is chosen, as `0` always does; so does typing on, or any
|
|
key that ends the composition. `Backspace` deletes the last kana shown;
|
|
romaji that made no kana stays in the reading as typed until it is mended.
|
|
The dictionary keys a verb or adjective by its stem, so `kaku` offers the
|
|
readings of かく and then 書く and its kin; typing the okurigana with
|
|
`Shift`, as SKK does — `kaKu` — asks for that split first. Caps Lock is
|
|
not `Shift` and marks nothing.
|
|
`Backspace` and `Esc` on a chosen candidate go back to the reading, and
|
|
`Esc` on the reading cancels it. Katakana mode does not perform Kanji
|
|
conversion.
|
|
|
|
Korean composes one syllable at a time; `Enter`, `Tab`, `Esc`, and any key
|
|
that is not a jamo commit the syllable and go on to the application, so
|
|
`Esc` still leaves insert mode. Two lone consonants join into the compound
|
|
final they make (rt → ㄳ) and a vowel typed before its consonant is
|
|
reordered under it (kr → 가), as in libhangul.
|
|
|
|
Emoji and Hanja searches take over the keys until `Enter`, `Space`, or
|
|
`1`-`9` picks a result or `Esc` cancels; they return to the previous mode
|
|
after use, and switching language during a search commits the shown
|
|
query. `Tab`/`Shift-Tab` wrap through the results, and a search with
|
|
nothing typed yet shows ☺ or 漢 in the popup. The Emoji search matches the
|
|
typed keys, and what they spell in the current language, against a prefix
|
|
of every emoji's CLDR name and keywords in English, Korean, and Japanese,
|
|
and against ASCII symbol aliases such as `->` and `<=`. `Ctrl+H` takes the
|
|
syllable being composed as its query and composes on from it; `Esc` gives
|
|
the syllable back. The Hanja dictionary converts a word as well as a
|
|
syllable, so `Ctrl+H` and then 한자 gives 漢字, and 대한민국 gives 大韓民國;
|
|
a syllable already committed is the application's text, not the engine's,
|
|
and cannot be converted. Leaving the field or
|
|
clicking elsewhere commits what is pending.
|
|
|
|
## Preedit and candidates
|
|
|
|
| Frontend | Preedit | Candidates |
|
|
| --- | --- | --- |
|
|
| Wayland input method | inline in the client | strans popup |
|
|
| GTK 3 and IBus | inline in the client | strans popup |
|
|
| XIM PreeditCallbacks | inline through XIM callbacks | strans popup |
|
|
| XIM PreeditPosition | strans popup | strans popup |
|
|
| XIM PreeditNothing | strans popup | strans popup |
|
|
|
|
Each XIM style is offered with StatusNothing and with StatusNone.
|
|
|
|
Dead keys and Compose sequences are composed by strans itself for the XIM
|
|
and IBus frontends, from the table `XCOMPOSEFILE` or the locale names; the
|
|
GTK 3 module leaves them to GtkIMContextSimple. Their text follows
|
|
whatever was pending.
|
|
|
|
XIM text travels as `COMPOUND_TEXT`, which carries any UTF-8. A client that
|
|
sends `XNSpotLocation` gets the popup under that spot whatever its preedit
|
|
style, and above the line when there is no room below; otherwise the popup
|
|
sits under the focus window, or under the client window when no focus
|
|
window is set.
|
|
|
|
On a compositor with `zwp_input_method_v2` the popup is a Wayland surface
|
|
that the compositor itself puts at the text cursor, flipping it above the
|
|
line when there is no room below; a client that reports no cursor location
|
|
gets it under its window instead. Everywhere else the popup is an X11 window,
|
|
placed from the XIM spot or from the GTK caret converted to root
|
|
coordinates, and XIM is X11-only. Either way `GDK_SCALE` sizes it for
|
|
HiDPI displays.
|
|
|
|
## Build and test
|
|
|
|
Initialize the bundled test library, then build in the Docker environment:
|
|
|
|
```sh
|
|
git submodule update --init
|
|
make docker-image
|
|
make docker-build
|
|
```
|
|
|
|
Build output:
|
|
|
|
```text
|
|
strans daemon with Wayland, IBus, and XIM frontends
|
|
gtk/im-strans.so GTK 3 frontend module
|
|
```
|
|
|
|
Tests have three tiers:
|
|
|
|
```sh
|
|
make check # generated-map validation and focused unit tests
|
|
make check-live # one IBus, GTK, XIM, and IPC daemon smoke
|
|
make check-stress # randomized, capacity, collision, failure, and restart tests
|
|
```
|
|
|
|
`UNITARGS` filters only the focused C unit suite, for example
|
|
`make check UNITARGS=hangul`. It does not select or skip live and stress
|
|
tests. `make docker-check` rebuilds and runs all three tiers in the
|
|
container, and `make docker-valgrind` runs the unit suite under Valgrind
|
|
there; run `make docker-image` first after changing `Dockerfile`.
|
|
|
|
Native Linux source builds require a C toolchain, Make, and pkg-config, plus
|
|
development files for:
|
|
|
|
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
|
|
- D-Bus development files;
|
|
- XCB, XCB RandR, XCB XKB, xcb-util, xcb-imdkit, xkbcommon, and xkbcommon-x11;
|
|
- the Wayland client library and `wayland-scanner`, which writes the
|
|
input-method and virtual-keyboard bindings from the XML in `proto/`;
|
|
- Pango, PangoCairo, Cairo, and Fontconfig;
|
|
- GTK 3, plus libibus and Xlib for the live clients;
|
|
- Xvfb for the X11 live tests;
|
|
- Python 3 and `cmp` for `make check` and the map generators;
|
|
- a working `C.UTF-8` locale and fonts covering Latin, CJK, and emoji.
|
|
|
|
The package names used by the canonical Arch Linux image are listed in
|
|
[`Dockerfile`](Dockerfile).
|
|
|
|
## Run
|
|
|
|
The popup uses the normal system fonts found by Pango and Fontconfig. Run
|
|
it directly from the build tree:
|
|
|
|
```sh
|
|
./run.sh
|
|
# equivalently
|
|
./strans map
|
|
```
|
|
|
|
`run.sh` stops every same-user process whose exact name is `strans`, starts one
|
|
build-tree daemon in the background, and returns to the shell. It requires
|
|
`pkill` from procps. Startup and frontend failures remain visible on the
|
|
caller's standard error. A service supervisor should instead run
|
|
`./strans map` directly in the foreground, with the session's
|
|
`XDG_RUNTIME_DIR` and its `WAYLAND_DISPLAY` or `DISPLAY`: the GTK module
|
|
finds the daemon at `$XDG_RUNTIME_DIR/strans.sock` (else
|
|
`/tmp/strans.UID`), and IBus clients
|
|
through the address file libibus expects under `~/.config/ibus/bus/`,
|
|
which strans writes as `ibus-daemon` would, named after `WAYLAND_DISPLAY`
|
|
when there is one and `DISPLAY` otherwise.
|
|
|
|
The build does not copy or install the daemon or its data. `./strans DIR`
|
|
opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and
|
|
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
|
|
directory. Only the GTK subdirectory has install and uninstall targets.
|
|
|
|
strans shows one popup per session, so it picks its frontend at startup: a
|
|
session whose compositor offers `zwp_input_method_v2` gets that frontend,
|
|
and neither XIM nor the X11 popup is started; every other session gets XIM
|
|
as before. The IBus endpoint and the GTK 3 module are served in both.
|
|
|
|
On Wayland, then, set none of the variables below. GTK, Qt and Firefox
|
|
speak text-input-v3 themselves and need nothing — GTK 4 binds it without
|
|
`GTK_IM_MODULE`, checked with 4.22 under sway 1.12 — and `GTK_IM_MODULE`
|
|
is what pushes them off the path that works. A client that reaches strans
|
|
over IBus or the GTK 3 module while the Wayland frontend runs still
|
|
composes inline, but shows no candidate list, because the popup belongs to
|
|
that frontend. Chromium is helped by neither: it still asks for
|
|
`text-input-v1`, which wlroots does not implement at all.
|
|
|
|
Configure clients as needed:
|
|
|
|
```sh
|
|
# XIM
|
|
export XMODIFIERS=@im=strans
|
|
|
|
# GTK 3 module
|
|
doas make -C gtk install
|
|
export GTK_IM_MODULE=strans
|
|
|
|
# IBus client example
|
|
GLFW_IM_MODULE=ibus kitty
|
|
```
|
|
|
|
Building `gtk/im-strans.so` requires GTK development files; installing one
|
|
built elsewhere, in the container for instance, does not. The install
|
|
target uses `GTK_MODULE_DIR` when it is set, else GTK's pkg-config
|
|
variables, else the directory the installed `gtk-query-immodules-3.0`
|
|
reports. Packagers and cross builds should set `GTK_MODULE_DIR`, and a
|
|
prebuilt module must still match the target architecture and library ABI.
|
|
|
|
`DESTDIR` staging is supported and deliberately skips the host module-cache
|
|
update. A native install needs the GTK runtime query utility so it can refresh
|
|
that cache. Remove a native installation with:
|
|
|
|
```sh
|
|
doas make -C gtk uninstall
|
|
```
|
|
|
|
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
|
required. IBus clients that process keys synchronously — GTK 4, or GTK 3
|
|
with `IBUS_ENABLE_SYNC_MODE=1` — get their commits and preedits through
|
|
`PostProcessKeyEvent`, in order with the key. With neither
|
|
`WAYLAND_DISPLAY` nor `DISPLAY`, the daemon, its IBus endpoint and its
|
|
socket still work, but no frontend draws a popup.
|
|
|
|
## Dictionary data
|
|
|
|
After changing an input source, regenerate and verify the maps and
|
|
dictionaries:
|
|
|
|
```sh
|
|
python3 map/mktelex.py >map/telex.map
|
|
map/mkemoji >map/emoji.dict
|
|
map/mkhanja >map/hanja.dict
|
|
make verify-map
|
|
```
|
|
|
|
See [`map/README`](map/README) for the emoji, Hanja, and Japanese data and
|
|
where it comes from.
|
|
|
|
## Benchmark
|
|
|
|
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh` starts
|
|
one build-tree instance, warms it up, and records the workload with Linux
|
|
`perf`. It requires `perf` and permission to profile the daemon, and writes
|
|
`bench/perf.data`.
|
|
|
|
## Licensing
|
|
|
|
Third-party license notices are in [`LICENSES`](LICENSES). The repository does
|
|
not currently declare a license for the strans source as a whole.
|