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:
282
README.md
282
README.md
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user