Mainline dwl has no input method: dwl.c creates a virtual keyboard manager and never a wlr_input_method_manager_v2, so IME there is a third-party patch and strans falls back to XIM through Xwayland -- not what the line promised. Being a wlroots or smithay compositor is the wrong criterion, which is the general lesson: the compositor has to wire the protocol up. river and labwc do, and were checked; Wayfire, niri and COSMIC never were. So the opening says the criterion instead of a list to maintain, and the Run section already says how the choice is made. The README says it once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
174 lines
7.1 KiB
Markdown
174 lines
7.1 KiB
Markdown
# strans
|
|
|
|
strans is a small, single-user input method for Korean, Japanese, English,
|
|
emoji and symbols, with Vietnamese Telex as a compatibility mode. One
|
|
engine serves four frontends: `zwp_input_method_v2` on Wayland — sway and
|
|
any other compositor that offers it — IBus, which GTK 4 and Qt use, XIM,
|
|
and a GTK 3 module.
|
|
|
|
## Modes
|
|
|
|
| Key | Mode |
|
|
| --- | --- |
|
|
| `Ctrl+S` | Korean Hangul (2-beolsik) |
|
|
| `Ctrl+N` | Japanese Hiragana, converting to Kanji |
|
|
| `Ctrl+K` | Japanese Katakana |
|
|
| `Ctrl+T` | English |
|
|
| `Ctrl+V` | Vietnamese Telex |
|
|
| `Ctrl+E` | Emoji and symbol search |
|
|
| `Ctrl+H` | One-shot Hanja search |
|
|
|
|
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. The mode switched to — `한`, `あ`, `ア`, `ă`,
|
|
`A` — shows in the popup until the next key. A Korean keyboard's 한/영 and
|
|
한자 keys stand for `Ctrl+S`/`Ctrl+T` and `Ctrl+H`; a Japanese one's
|
|
半角/全角, ひらがな/カタカナ, 変換 and 無変換 for the kana modes, `Space`
|
|
and English.
|
|
|
|
## Composing
|
|
|
|
Japanese composes a whole reading and then offers its Kanji with none
|
|
chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
|
|
reverses), the reading in Katakana last, so a word the dictionary lacks
|
|
converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without
|
|
wrapping. `Enter` commits the chosen candidate, or the reading, as `0` and
|
|
typing on always do. Once a candidate is chosen `1`-`9` take that row of
|
|
the page shown; before that the digits type. `Backspace` deletes the last
|
|
kana shown, and romaji that made no kana stays as typed until it is mended;
|
|
`Esc` cancels the reading, and on a chosen candidate both go back to it.
|
|
The dictionary keys verbs and adjectives by stem and okurigana, so `kaku`
|
|
offers かく's readings and then 書く and its kin — typing the okurigana
|
|
with `Shift`, as SKK does, `kaKu`, asks for that split first. Katakana
|
|
mode does not convert.
|
|
|
|
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.
|
|
|
|
A search takes the keys until `Enter`, `Space` or `1`-`9` picks a result or
|
|
`Esc` cancels, then returns to the previous mode. Emoji matches the typed
|
|
keys, and what they spell in the current mode, against a prefix of every
|
|
emoji's CLDR name and keywords in English, Korean and Japanese, and against
|
|
ASCII aliases such as `->` and `<=`. `Ctrl+H` takes the syllable being
|
|
composed as its query and composes on from it, converting a word as well as
|
|
a syllable — 한자 gives 漢字, 대한민국 gives 大韓民國 — and `Esc` gives the
|
|
syllable back. Text already committed belongs to the application and
|
|
cannot be converted.
|
|
|
|
Dead keys and Compose sequences are composed by strans itself for the
|
|
Wayland, XIM and IBus frontends, from `XCOMPOSEFILE` or the locale; the
|
|
GTK 3 module leaves them to GtkIMContextSimple.
|
|
|
|
## Preedit and candidates
|
|
|
|
| Frontend | Preedit | Candidates |
|
|
| --- | --- | --- |
|
|
| Wayland input method | inline in the client | popup |
|
|
| GTK 3 and IBus | inline in the client | popup |
|
|
| XIM PreeditCallbacks | inline through XIM callbacks | popup |
|
|
| XIM PreeditPosition, PreeditNothing | popup | popup |
|
|
|
|
On Wayland the popup is a surface the compositor places at the text cursor,
|
|
flipping it above the line when there is no room below. Elsewhere it is an
|
|
X11 window, placed from the XIM spot or from the GTK caret, and XIM text
|
|
travels as `COMPOUND_TEXT`. Either way `GDK_SCALE` sizes it for HiDPI
|
|
displays.
|
|
|
|
## Build and test
|
|
|
|
```sh
|
|
git submodule update --init
|
|
make docker-image
|
|
make docker-build
|
|
```
|
|
|
|
That leaves `strans`, the daemon, and `gtk/im-strans.so`, the GTK 3 module.
|
|
Tests come in three tiers:
|
|
|
|
```sh
|
|
make check # generated-map validation and unit tests
|
|
make check-live # one IBus, GTK, XIM and IPC daemon smoke each
|
|
make check-stress # randomized, capacity, collision, failure and restart
|
|
```
|
|
|
|
`make docker-check` runs all three in the container, and
|
|
`make docker-valgrind` the unit suite under Valgrind; rerun
|
|
`make docker-image` after changing `Dockerfile`. `UNITARGS=hangul` filters
|
|
the unit suite alone, not the other tiers.
|
|
|
|
A native build wants a C toolchain, Make, pkg-config and Plan 9 port, plus
|
|
development files for D-Bus, XCB and xcb-imdkit, Wayland and
|
|
`wayland-scanner`, xkbcommon, Pango and Cairo, and GTK 3; the tests also
|
|
want Xvfb, Python 3 and fonts covering Latin, CJK and emoji.
|
|
[`Dockerfile`](Dockerfile) names the exact packages.
|
|
|
|
## Run
|
|
|
|
```sh
|
|
./run.sh # restart the daemon in the background
|
|
./strans map # or run it in the foreground
|
|
```
|
|
|
|
`./strans DIR` reads `hira.map`, `kata.map`, `telex.map`, `kanji.dict`,
|
|
`emoji.dict` and `hanja.dict` from `DIR` at runtime; nothing but the GTK
|
|
module is installed. A service supervisor wants 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.
|
|
|
|
strans shows one popup per session, so it picks a frontend at startup: a
|
|
compositor offering `zwp_input_method_v2` gets the Wayland frontend, and
|
|
then neither XIM nor the X11 popup runs; every other session gets XIM. 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 — GTK 4 binds it with `GTK_IM_MODULE` unset
|
|
— and setting these is what pushes them off the path that works: such a
|
|
client still composes inline but shows no candidates, because the popup
|
|
belongs to the Wayland frontend. Chromium is beyond help either way, since
|
|
it asks for `text-input-v1`, which wlroots does not implement.
|
|
|
|
```sh
|
|
export XMODIFIERS=@im=strans # XIM
|
|
doas make -C gtk install # GTK 3 module
|
|
export GTK_IM_MODULE=strans
|
|
GLFW_IM_MODULE=ibus kitty # IBus client example
|
|
```
|
|
|
|
strans provides its own IBus endpoint, so `ibus-daemon` and fcitx are not
|
|
needed. Building the GTK module wants GTK development files; installing
|
|
one built elsewhere does not. The install target takes `GTK_MODULE_DIR`
|
|
when set, else asks GTK where its modules live; `doas make -C gtk uninstall`
|
|
removes it. With neither `WAYLAND_DISPLAY` nor `DISPLAY` the daemon still
|
|
serves IBus and its socket, but nothing draws a popup.
|
|
|
|
## Dictionary data
|
|
|
|
After changing an input source, regenerate and verify:
|
|
|
|
```sh
|
|
python3 map/mktelex.py >map/telex.map
|
|
map/mkemoji >map/emoji.dict
|
|
map/mkhanja >map/hanja.dict
|
|
make verify-map
|
|
```
|
|
|
|
[`map/README`](map/README) says where the emoji, Hanja and Japanese data
|
|
comes from.
|
|
|
|
## Benchmark
|
|
|
|
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh`
|
|
starts one, warms it up and records the workload with Linux `perf` into
|
|
`bench/perf.data`.
|
|
|
|
## Licensing
|
|
|
|
Third-party notices are in [`LICENSES`](LICENSES). The repository declares
|
|
no license for the strans source as a whole.
|