docs: the README says it once

It had grown to 256 lines, a third of them the engine's fine behaviour
told twice and packaging trivia that belongs in the Makefile.  Every
user-facing fact is kept; the prose around it is not.  The dependency
list is one sentence and a pointer at the Dockerfile that already names
the packages exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 20:45:04 +09:00
parent f6dd056ffb
commit 841d93ac35

282
README.md
View File

@@ -1,236 +1,155 @@
# strans # strans
strans is a small, single-user input method for Korean, Japanese, English, strans is a small, single-user input method for Korean, Japanese, English,
emoji, and symbols. It provides one engine for Wayland compositors that emoji and symbols, with Vietnamese Telex as a compatibility mode. One
speak `zwp_input_method_v2` — the wlroots and smithay families, so sway, engine serves four frontends: `zwp_input_method_v2` on Wayland — the
river, labwc, Wayfire, dwl, niri and COSMIC — and for IBus (which GTK 4 wlroots and smithay compositors, so sway, river, labwc, Wayfire, dwl, niri
and Qt use), XIM, and GTK 3. Vietnamese Telex is also available as a and COSMIC — IBus, which GTK 4 and Qt use, XIM, and a GTK 3 module.
compatibility mode.
## Input modes ## Modes
| Key | Mode | | Key | Mode |
| --- | --- | | --- | --- |
| `Ctrl+N` | Japanese Hiragana and Kanji conversion |
| `Ctrl+K` | Japanese Katakana |
| `Ctrl+S` | Korean Hangul (2-beolsik) | | `Ctrl+S` | Korean Hangul (2-beolsik) |
| `Ctrl+N` | Japanese Hiragana, converting to Kanji |
| `Ctrl+K` | Japanese Katakana |
| `Ctrl+T` | English | | `Ctrl+T` | English |
| `Ctrl+V` | Vietnamese Telex | | `Ctrl+V` | Vietnamese Telex |
| `Ctrl+E` | Emoji and symbol search | | `Ctrl+E` | Emoji and symbol search |
| `Ctrl+H` | One-shot Hanja search | | `Ctrl+H` | One-shot Hanja search |
Switching mode shows the mode switched to — `A`, `한`, `あ`, `ア`, `ă` — in Only a plain `Ctrl` chord is a strans key: with `Shift`, `Alt` or `Super`
the popup until the next key. 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.
A Korean keyboard's 한/영 key toggles Korean and English, and its 한자 key ## Composing
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 Japanese composes a whole reading and then offers its Kanji with none
with none chosen: the exact readings of the dictionary, then the verbs and chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
adjectives SKK keys by stem and okurigana (かく offers 確 and 書く), and reverses), the reading in Katakana last, so a word the dictionary lacks
under `Space` the reading in Katakana last, so a word the dictionary lacks converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without
converts to Katakana with one `Space`. `Space` or `Tab` chooses the first wrapping. `Enter` commits the chosen candidate, or the reading, as `0` and
candidate and then steps on, wrapping (with `Shift`, back); `Up`/`Down` and typing on always do. Once a candidate is chosen `1`-`9` take that row of
`PageUp`/`PageDown` move without wrapping. Once a candidate is chosen, an unmodified `1`-`9` commits that row of the the page shown; before that the digits type. `Backspace` deletes the last
current page; before that the digits type. `Enter` commits the chosen candidate, or the kana shown, and romaji that made no kana stays as typed until it is mended;
reading when none is chosen, as `0` always does; so does typing on, or any `Esc` cancels the reading, and on a chosen candidate both go back to it.
key that ends the composition. `Backspace` deletes the last kana shown; The dictionary keys verbs and adjectives by stem and okurigana, so `kaku`
romaji that made no kana stays in the reading as typed until it is mended. offers かく's readings and then 書く and its kin — typing the okurigana
The dictionary keys a verb or adjective by its stem, so `kaku` offers the with `Shift`, as SKK does, `kaKu`, asks for that split first. Katakana
readings of かく and then 書く and its kin; typing the okurigana with mode does not convert.
`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 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 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 `Esc` still leaves insert mode. Two lone consonants join into the compound
final they make (rt → ㄳ) and a vowel typed before its consonant is final they make (rt → ㄳ), and a vowel typed before its consonant is
reordered under it (kr → 가), as in libhangul. reordered under it (kr → 가), as in libhangul.
Emoji and Hanja searches take over the keys until `Enter`, `Space`, or A search takes the keys until `Enter`, `Space` or `1`-`9` picks a result or
`1`-`9` picks a result or `Esc` cancels; they return to the previous mode `Esc` cancels, then returns to the previous mode. Emoji matches the typed
after use, and switching language during a search commits the shown keys, and what they spell in the current mode, against a prefix of every
query. `Tab`/`Shift-Tab` wrap through the results, and a search with emoji's CLDR name and keywords in English, Korean and Japanese, and against
nothing typed yet shows ☺ or 漢 in the popup. The Emoji search matches the ASCII aliases such as `->` and `<=`. `Ctrl+H` takes the syllable being
typed keys, and what they spell in the current language, against a prefix composed as its query and composes on from it, converting a word as well as
of every emoji's CLDR name and keywords in English, Korean, and Japanese, a syllable — 한자 gives 漢字, 대한민국 gives 大韓民國 — and `Esc` gives the
and against ASCII symbol aliases such as `->` and `<=`. `Ctrl+H` takes the syllable back. Text already committed belongs to the application and
syllable being composed as its query and composes on from it; `Esc` gives cannot be converted.
the syllable back. The Hanja dictionary converts a word as well as a
syllable, so `Ctrl+H` and then 한자 gives 漢字, and 대한민국 gives 大韓民國; Dead keys and Compose sequences are composed by strans itself for the XIM
a syllable already committed is the application's text, not the engine's, and IBus frontends, from `XCOMPOSEFILE` or the locale; the GTK 3 module
and cannot be converted. Leaving the field or leaves them to GtkIMContextSimple.
clicking elsewhere commits what is pending.
## Preedit and candidates ## Preedit and candidates
| Frontend | Preedit | Candidates | | Frontend | Preedit | Candidates |
| --- | --- | --- | | --- | --- | --- |
| Wayland input method | inline in the client | strans popup | | Wayland input method | inline in the client | popup |
| GTK 3 and IBus | inline in the client | strans popup | | GTK 3 and IBus | inline in the client | popup |
| XIM PreeditCallbacks | inline through XIM callbacks | strans popup | | XIM PreeditCallbacks | inline through XIM callbacks | popup |
| XIM PreeditPosition | strans popup | strans popup | | XIM PreeditPosition, PreeditNothing | popup | popup |
| XIM PreeditNothing | strans popup | strans popup |
Each XIM style is offered with StatusNothing and with StatusNone. 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
Dead keys and Compose sequences are composed by strans itself for the XIM X11 window, placed from the XIM spot or from the GTK caret, and XIM text
and IBus frontends, from the table `XCOMPOSEFILE` or the locale names; the travels as `COMPOUND_TEXT`. Either way `GDK_SCALE` sizes it for HiDPI
GTK 3 module leaves them to GtkIMContextSimple. Their text follows displays.
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 ## Build and test
Initialize the bundled test library, then build in the Docker environment:
```sh ```sh
git submodule update --init git submodule update --init
make docker-image make docker-image
make docker-build make docker-build
``` ```
Build output: That leaves `strans`, the daemon, and `gtk/im-strans.so`, the GTK 3 module.
Tests come in three tiers:
```text
strans daemon with Wayland, IBus, and XIM frontends
gtk/im-strans.so GTK 3 frontend module
```
Tests have three tiers:
```sh ```sh
make check # generated-map validation and focused unit tests make check # generated-map validation and unit tests
make check-live # one IBus, GTK, XIM, and IPC daemon smoke make check-live # one IBus, GTK, XIM and IPC daemon smoke each
make check-stress # randomized, capacity, collision, failure, and restart tests make check-stress # randomized, capacity, collision, failure and restart
``` ```
`UNITARGS` filters only the focused C unit suite, for example `make docker-check` runs all three in the container, and
`make check UNITARGS=hangul`. It does not select or skip live and stress `make docker-valgrind` the unit suite under Valgrind; rerun
tests. `make docker-check` rebuilds and runs all three tiers in the `make docker-image` after changing `Dockerfile`. `UNITARGS=hangul` filters
container, and `make docker-valgrind` runs the unit suite under Valgrind the unit suite alone, not the other tiers.
there; run `make docker-image` first after changing `Dockerfile`.
Native Linux source builds require a C toolchain, Make, and pkg-config, plus A native build wants a C toolchain, Make, pkg-config and Plan 9 port, plus
development files for: development files for D-Bus, XCB and xcb-imdkit, Wayland and
`wayland-scanner`, xkbcommon, Pango and Cairo, and GTK 3; the tests also
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`); want Xvfb, Python 3 and fonts covering Latin, CJK and emoji.
- D-Bus development files; [`Dockerfile`](Dockerfile) names the exact packages.
- 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 ## Run
The popup uses the normal system fonts found by Pango and Fontconfig. Run
it directly from the build tree:
```sh ```sh
./run.sh ./run.sh # restart the daemon in the background
# equivalently ./strans map # or run it in the foreground
./strans map
``` ```
`run.sh` stops every same-user process whose exact name is `strans`, starts one `./strans DIR` reads `hira.map`, `kata.map`, `telex.map`, `kanji.dict`,
build-tree daemon in the background, and returns to the shell. It requires `emoji.dict` and `hanja.dict` from `DIR` at runtime; nothing but the GTK
`pkill` from procps. Startup and frontend failures remain visible on the module is installed. A service supervisor wants the session's
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 `XDG_RUNTIME_DIR` and its `WAYLAND_DISPLAY` or `DISPLAY`: the GTK module
finds the daemon at `$XDG_RUNTIME_DIR/strans.sock` (else finds the daemon at `$XDG_RUNTIME_DIR/strans.sock`, else
`/tmp/strans.UID`), and IBus clients `/tmp/strans.UID`, and IBus clients through the address file libibus
through the address file libibus expects under `~/.config/ibus/bus/`, expects under `~/.config/ibus/bus/`, which strans writes as `ibus-daemon`
which strans writes as `ibus-daemon` would, named after `WAYLAND_DISPLAY` would.
when there is one and `DISPLAY` otherwise.
The build does not copy or install the daemon or its data. `./strans DIR` strans shows one popup per session, so it picks a frontend at startup: a
opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and compositor offering `zwp_input_method_v2` gets the Wayland frontend, and
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/` then neither XIM nor the X11 popup runs; every other session gets XIM. The
directory. Only the GTK subdirectory has install and uninstall targets. IBus endpoint and the GTK 3 module are served in both.
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 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 speak text-input-v3 themselves — GTK 4 binds it with `GTK_IM_MODULE` unset
`GTK_IM_MODULE`, checked with 4.22 under sway 1.12 — and `GTK_IM_MODULE` — and setting these is what pushes them off the path that works: such a
is what pushes them off the path that works. A client that reaches strans client still composes inline but shows no candidates, because the popup
over IBus or the GTK 3 module while the Wayland frontend runs still belongs to the Wayland frontend. Chromium is beyond help either way, since
composes inline, but shows no candidate list, because the popup belongs to it asks for `text-input-v1`, which wlroots does not implement.
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 ```sh
# XIM export XMODIFIERS=@im=strans # XIM
export XMODIFIERS=@im=strans doas make -C gtk install # GTK 3 module
# GTK 3 module
doas make -C gtk install
export GTK_IM_MODULE=strans export GTK_IM_MODULE=strans
GLFW_IM_MODULE=ibus kitty # IBus client example
# IBus client example
GLFW_IM_MODULE=ibus kitty
``` ```
Building `gtk/im-strans.so` requires GTK development files; installing one strans provides its own IBus endpoint, so `ibus-daemon` and fcitx are not
built elsewhere, in the container for instance, does not. The install needed. Building the GTK module wants GTK development files; installing
target uses `GTK_MODULE_DIR` when it is set, else GTK's pkg-config one built elsewhere does not. The install target takes `GTK_MODULE_DIR`
variables, else the directory the installed `gtk-query-immodules-3.0` when set, else asks GTK where its modules live; `doas make -C gtk uninstall`
reports. Packagers and cross builds should set `GTK_MODULE_DIR`, and a removes it. With neither `WAYLAND_DISPLAY` nor `DISPLAY` the daemon still
prebuilt module must still match the target architecture and library ABI. serves IBus and its socket, but nothing draws a popup.
`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 ## Dictionary data
After changing an input source, regenerate and verify the maps and After changing an input source, regenerate and verify:
dictionaries:
```sh ```sh
python3 map/mktelex.py >map/telex.map python3 map/mktelex.py >map/telex.map
@@ -239,17 +158,16 @@ map/mkhanja >map/hanja.dict
make verify-map make verify-map
``` ```
See [`map/README`](map/README) for the emoji, Hanja, and Japanese data and [`map/README`](map/README) says where the emoji, Hanja and Japanese data
where it comes from. comes from.
## Benchmark ## Benchmark
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh` starts `make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh`
one build-tree instance, warms it up, and records the workload with Linux starts one, warms it up and records the workload with Linux `perf` into
`perf`. It requires `perf` and permission to profile the daemon, and writes
`bench/perf.data`. `bench/perf.data`.
## Licensing ## Licensing
Third-party license notices are in [`LICENSES`](LICENSES). The repository does Third-party notices are in [`LICENSES`](LICENSES). The repository declares
not currently declare a license for the strans source as a whole. no license for the strans source as a whole.