From 841d93ac35557fa34c814c4ae2080a07c6654013 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 17 Aug 2026 20:45:04 +0900 Subject: [PATCH] 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) --- README.md | 282 +++++++++++++++++++----------------------------------- 1 file changed, 100 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index 7ef9982..104bd2e 100644 --- a/README.md +++ b/README.md @@ -1,236 +1,155 @@ # 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. +emoji and symbols, with Vietnamese Telex as a compatibility mode. One +engine serves four frontends: `zwp_input_method_v2` on Wayland — the +wlroots and smithay compositors, so sway, river, labwc, Wayfire, dwl, niri +and COSMIC — IBus, which GTK 4 and Qt use, XIM, and a GTK 3 module. -## Input modes +## Modes | Key | Mode | | --- | --- | -| `Ctrl+N` | Japanese Hiragana and Kanji conversion | -| `Ctrl+K` | Japanese Katakana | | `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 | -Switching mode shows the mode switched to — `A`, `한`, `あ`, `ア`, `ă` — in -the popup until the next key. +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. -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. +## Composing -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. +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 +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 +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. +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 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 | 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 | +| 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 | -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. +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 -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: +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 focused unit tests -make check-live # one IBus, GTK, XIM, and IPC daemon smoke -make check-stress # randomized, capacity, collision, failure, and restart tests +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 ``` -`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`. +`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. -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). +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 -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 # restart the daemon in the background +./strans map # or run it in the foreground ``` -`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 +`./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, named after `WAYLAND_DISPLAY` -when there is one and `DISPLAY` otherwise. +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. -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. +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 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: +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 -# XIM -export XMODIFIERS=@im=strans - -# GTK 3 module -doas make -C gtk install +export XMODIFIERS=@im=strans # XIM +doas make -C gtk install # GTK 3 module export GTK_IM_MODULE=strans - -# IBus client example -GLFW_IM_MODULE=ibus kitty +GLFW_IM_MODULE=ibus kitty # IBus client example ``` -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. +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 the maps and -dictionaries: +After changing an input source, regenerate and verify: ```sh python3 map/mktelex.py >map/telex.map @@ -239,17 +158,16 @@ 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. +[`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 build-tree instance, warms it up, and records the workload with Linux -`perf`. It requires `perf` and permission to profile the daemon, and writes +`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 license notices are in [`LICENSES`](LICENSES). The repository does -not currently declare a license for the strans source as a whole. +Third-party notices are in [`LICENSES`](LICENSES). The repository declares +no license for the strans source as a whole.