mkemoji wrote a row for every prefix of every alias, so that a query
matched as it was typed; the trie is a prefix index already, and with a
real emoji list those rows would be four times the aliases themselves.
Now emoji.dict has one row per alias, trienode() names a key's node, and
dictlookup walks the entries at and below it, the key's own first, up to
Maxkouho. Trie children are appended rather than pushed, so the walk
keeps the file's order and a bare digit still picks the superscript or
subscript it always did.
The hand-written symbol rows move to symbol.src; emoji.src is left to
the emoji. mkemoji reads both by default, or the files it is given.
hira.map and kata.map knew 152 keys of the 320 every Japanese IME
accepts: no nn, no small kana by x or l (xtu, xya, la, ...), no sya, tya,
zya, jya, dya, cya, tsa, thi, dhi, twu, kye, ye, fya, wha, and zi gave ぢ.
Both are now Mozc's default table in full, kata.map in Katakana, so that
they agree (kata.map alone had di → ディ and a lone v). With Mozc, wi
and we are うぃ and うぇ (wyi and wye the old kana), the v row is ゔ, nn
is ん, and [ ] ~ and the z-prefixed symbols type 「 」 〜 ・ … ← ↓ ↑ →.
n' is a row now, not a case in transjp. A row whose value is っ keeps
its consonant pending only when it is a doubled consonant or tch: xtu
and ltu are っ itself, so a lone っ can finally be typed.
A Korean keyboard sends Hangul and Hangul_Hanja for its two extra keys;
they did nothing but end the composition. Now 한/영 toggles Korean and
English and 한자 opens the Hanja search, by standing in for Ctrl+S,
Ctrl+T, and Ctrl+H, as fcitx5-hangul binds them.
A pending Hangul syllable is real text, and Escape is what vi users
press to leave insert mode; eating it and dropping the syllable served
nobody. fcitx5-hangul commits and passes any key it does not consume,
Escape included; do the same for Korean and Telex. A Japanese reading
is still cancelled: there Escape is the IME's own key.
A complete Japanese reading showed its candidates with the first row
highlighted, yet Enter committed the reading unless the user had moved
to a candidate: the highlight lied. Now sel is -1 while nothing is
chosen, and it alone says what Enter commits; candidatechosen goes.
Every key that ends a composition — Enter, Tab, a language switch, a
special key, a modifier chord, typing on — now commits the chosen
candidate through one flush; before, only Enter and Tab did, and Ctrl+S
after choosing 漢字 committed かんじ.
Space in a Japanese mode is the conversion key, as in every other
Japanese IME: it steps through the candidates (Shift+Space backwards,
both wrapping) and commits a reading that has none, without typing a
space. Backspace and Escape on a chosen candidate go back to the
reading first. Tab in a search wraps through the same cyclekouho.
reset() no longer forgets the caret: a chosen candidate confirmed by
typing on would otherwise redraw the next reading at the pointer.
A stopped daemon left its socket and IBus address file behind, since
plan9port ends a process on SIGTERM without running exit handlers. Both
files now go on a note as well as on exit, and only while they are still
the ones this daemon made, so a successor is never robbed of its own.
Xlib does no local composing for a client on an input method server,
so under XIM a dead key was forwarded as a bare keysym and vanished.
The XIM frontend now feeds keys through xkbcommon's Compose table for
the daemon's locale and commits the composed character itself.
Without a caret the popup sat at pointer+10 and was clamped upward at
the bottom of the work area, covering the pointer; it now goes above the
pointer like it goes above a caret.
The popup was 32-pixel rows whatever the display; next to 2x application
text it was small. Its metrics now scale with the daemon's GDK_SCALE,
the setting the applications use.
32 hid the tail of common readings: きょう has 41 kanji, 구 has 352
hanja. 128 covers every kanji entry and the hanja dictionary now keeps
that many per reading.
Shift or Caps Lock made a Japanese key pass through as a Latin capital,
so nothing composed with Caps Lock on; Korean already folds case. Latin
text still has Ctrl+T.
Ctrl+H committed the syllable being composed and opened an empty search,
so converting 한 meant typing it twice; the pending syllable now seeds the
query. Backspace on an already empty search ended it and then reached the
application, deleting a character; it now only ends the search.
Enter with a pending syllable committed it and stopped there, so a chat
message needed Enter twice; Korean input methods commit and pass the key
on. Japanese keeps consuming the key that confirms a reading or a chosen
candidate, as Japanese input methods do.
Clicking elsewhere, changing focus, or any client reset dropped the
composition in the GTK module and IBus (only XIM ResetIC handed it
back), so typing 안녕 and clicking Send lost 녕. The engine now returns
the pending text — a moved-to candidate first, as Enter would — on
Keyreset and Keyrelease; the GTK module asks for it before closing on
focus-out and commits it, IBus commits it on FocusOut, Reset and a
switch to a password field, and XIM commits it on focus loss and hands
it back on ResetIC without a separate capability probe.
Hiragana spelled out 42 doubled syllables (kka, ssha, ttsu, ...) and so
knew none it did not list: matcha, baggu and beddo came out as ma-tc-ha,
ba-gg-u. Katakana already used the engine's small-tsu rule for a few
doubles. Both maps now list every doubled consonant but n, plus tch, and
hiragana gains di, che and fa/fi/fe/fo like katakana.
The map is the syllable grammar: a key sequence stays pending only
while it is a prefix of some entry, so a rime the table lacked split
the syllable and a trailing tone key landed on the wrong letter (vowis
gave vơí, rooif rôì, thaays thâý), ua+tone was a valueless prefix that
committed the raw keys (cuar gave cuar, muaf muaf), uyê and ươ typed as
uwow took no coda (nguyễn, được), and ưa toned the a (cửa gave cưả).
Add âu ây êu ôi ơi eo ia ưi ưu uê ươu uây oeo uya uyu, the uyê, uwow,
uê and oe codas, and tone ưa on ư.
Ctrl+E during an emoji search, Ctrl+H during a hanja search, or either
during the other search dropped the typed query, while every other
Ctrl key committed it as the README says. Escape is the way to cancel;
the search keys now commit the shown text like a language switch.
Up, Down, PageUp and PageDown with a composition pending and nothing to
move through returned the key uneaten but left the text pending, so the
client moved its cursor and reset the context: the pending Korean
syllable vanished, where Left or Home would have committed it. Let them
fall through to the special-key path that commits and passes the key.
At the limit the key that did not fit was handed back to the client as
plain ASCII, so a long Japanese reading ended in a Latin letter, and a
pending romaji consonant was flushed as ASCII with it (…あka for か).
Commit the completed kana, keep the pending syllable, and transliterate
the key as usual; other languages commit and go on the same way.
libibus destroys an input context with Destroy on
org.freedesktop.IBus.Service, not on the InputContext interface, so the
call went unanswered and the context slot stayed taken until the whole
connection closed. A long-lived GTK process that opens and closes text
widgets exhausted the 1024 slots and then got LimitsExceeded for every
new field. The live contract test now destroys a context that way and
checks the path is gone.
The popup window is created once at startup and only mapped afterwards,
and MapWindow does not restack, so every application window (or WM
frame) opened after the daemon sat above it: candidates and preedit were
invisible in those windows until the daemon restarted. Configure the
window above its siblings on every show. The XIM live test now checks
the popup stacks above a client window created after the daemon.
Shift+Tab arrives from every frontend as ISO_Left_Tab (0xfe20), which
has no Unicode value and lies outside the function keysym range, so
ipckeysym gave key 0 and the documented Shift-Tab wrap through search
results never reached the engine; GTK moved focus instead. KP_Enter and
KP_Tab likewise became unknown special keys, so the keypad Enter
committed the reading and passed a newline instead of taking the
highlighted candidate.
sinit's nil and negative-length tests and stoutf's zero-size test only
had test callers; every real caller passes a buffer and its size. The
len > n check after fullrune succeeded could never fire.
The Dockerfile installs valgrind and the ld.so debug info it needs, but
no target used them; docker-valgrind runs the unit suite under it, so
the dependency is visible and the run is one command.
numx, textx and selx were compile-time constants stored per layout;
the number column starts at PopupPad and text at PopupPad+PopupNumw,
which dat.h now says once. The height fallback tried dropping the
marker, then the preedit, then the padding as three separate refits;
below one padded row it now shows as many bare rows as fit, at least
one, which is what those steps added up to.
Toolkits keep one IBus context per widget that ever took focus, so 64
across every client could run out in a long session, after which new
widgets silently got no input method. The table is static and small
(about 100 bytes a slot).
rejectold forked and re-execed the test binary with --reject-address
just to call dbus_connection_open_private once against the crashed
daemon's abstract address, then waited on the helper, drained its
stderr pipe and checked its exit status. An abstract socket with no
listener refuses the connection immediately, so the call cannot block
and needs no separate process.
Open the address in-process and require a null connection with the
D-Bus error set, freeing the error afterwards. This drops the helper
process, its stderr capture, the Test.helper fields and the argv
dispatch in main. killdaemon stays; the daemons still need it.
Six live tests carried private copies of the same daemon harness: the
private XDG_RUNTIME_DIR, the fork/exec with captured stderr, the
readiness waits, the SIGTERM and SIGKILL paths, the IBus address file
reader, the socket connect, and the IPC probe. Three of them also
carried the same libdbus helpers.
Extract one implementation into tests/live.c and, so that the binaries
that do not link dbus-1 keep not linking it, the D-Bus half into
tests/livebus.c. Both are compiled into each binary the way ../ipc.c
already is. gtk_live_test keeps its own fake-server harness and only
takes fail, nowms and leftms; xim_live_test keeps its own Xvfb and
process-group daemon spawn, which must inherit DISPLAY, and takes the
temporary directory, timing and cleanup halves.
The copies had drifted; the harness keeps the stricter behaviour.
- nowms reports a broken clock (-1) instead of pretending it read
zero, and leftms turns that into an expired deadline, so a loop
ends in a timeout failure rather than spinning. livesetup checks
the clock once up front, as daemon_restart_test did.
- Timeouts compare with <= 0, not == 0.
- readuntil keeps the three-way result (complete, peer closed, error)
from ipc_live_test and daemon_restart_test rather than folding
peer closure into ECONNRESET.
- The daemon's stdout and stderr are both captured, and a failing
setenv is reported, for every daemon; daemon_failure_test captured
stderr only and said nothing about setenv.
- The child keeps daemon_restart_test's careful redirect that also
works when the pipe lands on fd 1 or 2.
- parseaddress requires a positive declared PID.
- killdaemon reports ECHILD after SIGKILL as a failure; one copy
accepted it. It now reaps with a blocking waitpid, which SIGKILL
guarantees will return, instead of daemon_restart_test's polled
wait with its own timeout diagnostic.
- stopdaemon tolerates ESRCH on SIGTERM, a benign race two copies
reported as an error.
- liveclean removes the socket and address files and then rmdirs
each directory, reporting leftovers, rather than deleting the
temporary root recursively.
- ibus_live_test now waits for the IPC socket and the address file
by polling, dropping its inotify variant; it asserted only the
address file before.
server_test, ibus_test and xim_adapter_test each ran their own copy of
the same alt loop that stands in for imthread, and two of them had
private checkstr/checkenginepreedit variants. test_util.c now provides
Pump (trace, optional hold on a chosen op, stop) in its own proc so a
test may block in socket I/O while the engine runs, and test.h declares
the engine hooks once; every .c includes dat.h and fn.h itself as the
rest of the tree does. server_test's three copies of fixture setup and
teardown became serverbegin/serverend.
ko_test and vi_test replayed keys with private copies of transstr's
loop, and test_util reimplemented the shown preedit; the engine exports
impre(Im*, Str*) and transstr() instead, so a change to composition
rules is one edit.
font.c exported three wrappers for one function, and the fit == -1 mode
existed only for the tests; textdraw() takes fit and colour. win.c
tested the empty picture in the thread and again in winshow, interned
two atoms by hand next to getatom(), kept a consumer-side drain that the
producer's already guarantees never finds anything, zeroed ten globals
before returning from the only thread that read them, and re-checked
sizes that resizebacking had just established. The page marker is laid
out once and drawn from the layout instead of being recomputed.
Six handlers built method returns by hand and only two of them checked
for a nil message; reply() builds them all and dies on OOM like the
rest of the daemon. DBusError objects were initialised and freed but
never read: libdbus accepts nil. Properties.Get always errored, so it
is one line in onmsg; the introspection XML served nobody (libibus
never asks) and had to be kept in sync by hand. Ibushinthidden was not
an IBus hint, so that term of hidden() never fired. writeaddr uses the
syscalls directly and the address file's dev/ino is the ownership
proof; buildaddrpath makes the directory in place. An empty
UpdatePreeditText goes out only to the context that shows a preedit.
kinit() sets up the XKB extension, after which the server stops sending
core MappingNotify to this client, so the refresh branch never fired and
strans kept its startup keymap; setxkbmap after launch left every XIM
key resolved against the old layout. Select XKB NewKeyboardNotify and
MapNotify (with the map parts, or MapNotify is never delivered) and
rebuild the keymap on those events instead.
XIM text always went out through imdkit's COMPOUND_TEXT converter,
which already wraps every UTF-8 string in ESC%G; the UTF8_STRING
negotiation, the per-client encoding list, ximtext.c's fallback and its
wrapper never changed a byte on the wire. The spot location is honoured
for every style now (GTK's XIM module sends it with PreeditCallbacks)
and an unset focus window means the client window, as the spec says,
so those clients get the popup at the caret. readattrs/place kept four
transient fields to pass values between them; ximclose tore down state
right before die(); the OOM passthrough context was a third policy for
one small calloc where emalloc dies like everything else. Both frontends
now poll for engine-owner loss only while a preedit shows instead of
XIM waking on a pipe the engine had to know about; ibus stops waking
five times a second when idle. keymeaningful() is the engine's own
predicate. The standalone xim_test moved into the unit suite, so
xim/Makefile is gone.
mapget and maplookup were one trie probe with two predicates; mapmatch
returns the match kind and mapget is its exact case. movedelta is a
switch. searchkey's modifier test was unreachable behind transition's,
searchlang's Alt/Super test behind both callers' earlier returns, and
picksearch's range check behind numberedkouho. Korean key folding runs
once at the top of transition. commit() on an empty preedit is a no-op,
so the Ctrl branch and startsearch lose their haspre guards and repeated
commit/reset pairs. Ctrl+language inside a search now commits the shown
query as it does elsewhere instead of dropping it. Language ids are
documented as the Ctrl codes they are; Im parameters no longer shadow
the file's Im.
Every key copied the whole Im and Search rune by rune, compared them
after the transition, and combined the answer with 'eaten', a commit
count that is always zero, and a force flag for caret and owner changes,
while redraw() kept a separate 'visible' bit to know when to send an
empty popup. redraw() now compares the snapshot with the last one sent
and treats two empty popups as equal, so imhandlekey just ends in
redraw(). keystroke() was only a test entry point and moves there.
Japanese was never dispatched through the Lang table: dotrans and
transstr both special-cased it into transjp, which edited Im in place
and wrote commits through a side argument, while transko popped and
cleared a pre that every caller overwrote anyway, and Vietnamese kept
its key history in dotrans. Emit now carries the new raw state next to
the new pre; transjp, transko and transvi are pure functions of (Im,
key), and dotrans and transstr have a single path. Vietnamese history
bookkeeping lives in vi.c, where the full-history flush no longer
resets the caret as a side effect; istone was toneidx() >= 0.
The dictionary thread ran in imthread's own proc, so a lookup could
only start once the engine blocked, and it was a trie probe anyway; the
emoji and Hanja searches already called dictlookup directly. The
request/result channels, sequence numbers, staleness checks and the
second draw per Japanese key are gone; dictqjp fills the candidates
in place. Emit.dict and Lang.dictq only ever triggered lookups for
Vietnamese, which has no dictionary. dictlookup(Lang*, key, out, max)
returns the count. The Hanja lookup no longer pre-checks for a single
syllable; a reading either has an entry or it does not.
For Vietnamese the preedit is the map key ('as', 'oong'); only the popup
and the commit path looked it up. Clients with inline preedit therefore
showed 'as' while Enter committed 'á'. impre() now maps once for every
reader, and snapshot() no longer maps a second time.
The hash map served a single exact-match lookup that the trie already
answers, at the price of a second container, a second file loader with
its own drift, and a Str-to-UTF-8 conversion on every chain probe. The
files are small (kanji.dict is 7.5k lines), so the trie holds both.
trieopen validates keys and each space-separated value word against Str
and reports path:line; trielookup takes the Str every caller holds and
treats a nil trie as an unloaded map; trienew/trieput exist for tests.
The overflow guards on growth, Trie.root (always 0) and the per-language
init loop written twice are gone.
ipc.c: an AF_UNIX nonblocking connect completes at once or fails with
EAGAIN, so the EINPROGRESS/poll/SO_ERROR path and the fcntl juggling
were dead; the deadline plumbing checked a clock that cannot fail and
re-tested the deadline before every transfer although only waitfd
blocks; readfield's truncation and discard loop served the unit test,
since every caller owns char[Ipcfieldmax+1]; NULL-argument checks on
in-tree encoders are gone (peer validation stays). The primary key frame
is Ipckey, not 'legacy'; ipckeysym names the keysym mapping.
srv.c: the per-connection capability and caret already lived in the
persistent Keyreq; the mirror locals and 'negotiated' flag guarded a
protocol rule no client relied on. proccreate never fails in libthread.
gtk: focus-out no longer round-trips a reset before closing the socket
that releases the engine; the caret dedup compares the packed frame;
srvconnect's preedit no-ops and the insimple flag are gone.