Commit Graph

177 Commits

Author SHA1 Message Date
46ab39ab95 test: the popup's stacking and its map state are one observation
popupabove() took the stacking order from XQueryTree and then the map
state of each child from a round trip of its own.  The daemon raises and
maps between those, in that order and in one flush, so the loop could pair
a stale order -- popup still below the client, where it was created -- with
a fresh IsViewable, and report the raise that had already happened as a
popup stacked below the client.  1 run in 25 under load, which is the worst
kind: often enough to teach you to re-run a red instead of reading it.

Widening the gap to 300ms shows it with no load at all, and shows the fix
is the right one.  Same test, same widener, 15 runs each:

	before	pass=11 fail=4
	after	pass=15 fail=0

XGrabServer is what makes the two one observation, and the widener is then
harmless because nothing can raise inside it.  Unwidened, 20 runs clean.

Nothing about win.c was wrong: the popup does go above, and the test now
only says so when it is looking at a single moment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 00:22:34 +09:00
650f00812d compose: a sequence belongs to one context
One xkb_compose_state served the whole session, so a dead key left half
typed in one window was completed by the first key in the next.  Two IBus
contexts through the real processkey(), before this:

	want length 0, got length 2; byte 0: want <end>, got 0xc3  (text)
	want 0, got 1                                              (req.op)

Context A pressed dead_acute; context B pressed e, got é, and went down
the composed-text branch instead of sending a key.  Only COMPOSING is
sticky -- xkbcommon starts over by itself after COMPOSED and CANCELLED,
which the table in compose_test already pins -- and nothing here ever
called xkb_compose_state_reset.

That static was also fed from three procs, two of them live at once, with
no lock, which xkbcommon forbids.  Reaching it needs two focused windows,
so it cannot be made to fail on demand and has no test: it goes because
the sharing goes, not because anything guards it.

So a state per frontend, each starting over when the key comes from
another context.  All are made in composeinit(), on threadmain, before
proccreate, because xkb_compose_state_new refs the table and that ref is
a plain increment --

	b160: mov (%rdi),%edx   b16a: add $0x1,%edx   b170: mov %edx,(%rdi)

-- so making a state from a shared table on two procs would race in place
of the feed.  Made before the procs exist they can still share the one
table.  An owner address that gets reused says so with composedrop: ibus
hands out a contexts[] slot again, xim can malloc an Ic at a freed one,
and wl's single context, which serves every client in turn, drops at
every activate and deactivate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 23:25:22 +09:00
aef577026d dat: one pair of content purposes
Two frontends read the same two numbers, and wl.c carried four lines of
comment to say why it kept its own copy of them.  IBus and text-input-v3
number the purposes alike because both took them from GTK, so the header
is where they belong and the explanation goes away with the copy.  hidden
stays in both files: one asks about a context, the other about the seat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 21:26:48 +09:00
8472474332 wl: modmask's caller has already looked at the keymap
grabkey returns before it asks for the mask when there is no keyboard
state, so the guard inside modmask answered a question nobody put to it --
except the test, which was the only caller that could reach it.  A check
kept alive by the test that reaches it is one line of code and one line of
test to delete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 21:22:36 +09:00
ad2f2fc5eb test: the xkb modifier mask and the forwarded key bitmap
The two pure things in the Wayland frontend, in the shape the XIM adapter
test already uses: wl.c included behind one define that makes the virtual
keyboard inert, and a keymap built from a string, so no compositor and no
xkb data files are wanted.  The mask test holds Caps Lock apart from
Shift, which is what a Korean key turns on; the bitmap test holds a
release of a key we never passed on, a code the bitmap cannot hold, and
the release of everything still down when the context goes.

wayland-scanner's output is the parent Makefile's to write, as ../strans
already is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 19:51:37 +09:00
27180bc0fe test: a key passed through is not committed text
A Korean Enter commits the syllable and lets the key on, so xim.c sends
the commit and then forwards the key.  pumpinput took the text of either
and kept the last, so under load the Return's own carriage return
overwrote 가: 22 of 25 runs failed on a loaded machine and none on an
idle one.  An XIM commit arrives as XLookupChars; a forwarded key comes
back as XLookupBoth, and is not a commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:38:12 +09:00
f6477cb97d test: the unit build takes the dialect the daemon is built with
9c compiles with -std=gnu11 and UNIT_CFLAGS overrode that with -std=c99,
so the tests built the daemon's own sources under a dialect the daemon
never sees.  Under it glibc hides lstat, and server_test.c reaches
<sys/stat.h> before srv.c pulls in u.h -- it must, since thread.h
defines recv as chanrecv and the socket recv it calls has to be declared
first.  So make all was green while make check would not compile.
HOST_CFLAGS keeps -std=c99: those are host programs with no Plan 9
headers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:38:12 +09:00
059e8bbc75 ibus: SetCursorLocationRelative is not a caret
A GTK client on the Wayland backend sends this where an X11 one sends
SetCursorLocation, and ictab did not list it, so libdbus answered
UnknownMethod.  Accept it and throw it away: the absolute translation
sits inside GDK_IS_X11_DISPLAY, so the coordinates are surface local,
and ibus's own daemon will not hand them to an engine either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:38:04 +09:00
ae7dbae993 engine: Shift on the okurigana asks for that reading, as SKK does
kaku offers the twenty-two readings of かく before 書く, because the SKK
dictionary keys a verb by its stem and gives no frequency to rank the two
lists by.  SKK's own answer is the shift key: kaKu says where the
okurigana starts, and that split now comes first — the reading's own
candidates still follow it.  Caps Lock sends no Shift, so it marks
nothing, and a word typed without Shift is unchanged.
2026-08-17 12:53:12 +09:00
2e53627b7d data(hanja): words, not only syllables
The import kept only readings of one syllable, so the Hanja search could
convert 한 but never 한자, 학교, or 대한민국 — the conversion every other
Korean input method offers.  libhangul's table has 187k readings; both
scripts now keep them all, and the search finds a word as readily as a
syllable.  The daemon pays for it: 24 MB instead of 12, and 170 ms to
start instead of 30.
2026-08-17 12:50:05 +09:00
6a6749e824 engine: a mode switch shows the mode it switched to
Ctrl+S, Ctrl+N and the rest changed the mode with nothing to see; the
next key was the only way to tell.  The popup now shows A, 한, あ, ア, or
ă until a key is typed, beside the ☺ and 漢 a search already shows.
2026-08-17 12:38:00 +09:00
c2aec44aa9 engine: the digits type until the candidate list is engaged
Every complete reading shows its candidates, so 1-9 were candidate picks
in the middle of composing: ka then 5 committed 家, and heya then 2
typed a 2 the application never asked for.  They pick only once Space,
Tab, or an arrow has chosen a candidate, as in Mozc; before that they are
ordinary keys.  A search is unchanged: its digits pick from the start.
2026-08-17 12:36:23 +09:00
d129f406d5 test: one Xvfb launcher and one daemon for the live tests
The XIM test carried its own Xvfb and daemon spawner, its own child
struct, log files and process-group teardown, and the GTK test a second
Xvfb launcher, because live.c's startdaemon always unset DISPLAY.  Live
now carries the display its own startxvfb reports, startdaemon passes it
on, and both tests use the shared pair: 300 lines fewer, one place that
knows how a child is started, watched, and stopped.
2026-08-17 12:24:12 +09:00
e54783dc92 test: Telex goes in the language table like every other map
The unit suite built a Lang of its own for Telex and loaded telex.map
into it, so the tests that drive the real table had to swap the map in
and out around themselves.  testmapinit loads it where it belongs.
2026-08-17 12:20:39 +09:00
0ba7dc7f50 build, data: the Greek capitals by name; one way to run python3; bench.sh checks its own binary
Δ Γ Λ Ω Φ Ψ Σ Θ answered only to De, Ga, La, Om, Ph, Ps, Si, Th, while
their small letters answered to delta, gamma and the rest; they answer to
the names too now.  mkemoji and cldr2emoji write UTF-8 whatever the
locale, as the other generators already did; verify-map calls python3 the
one way; the tests' include path drops a directory nothing includes
through; and bench.sh says which binary is missing instead of blaming the
daemon ten seconds later.
2026-08-17 12:17:18 +09:00
ef7fb627c6 compose: one Compose table for the XIM and IBus frontends
An IBus client throws away a dead key its engine did not take — the GTK
module's own comment says so, and Qt does the same — so é and ü were
lost in every IBus application, while XIM composed them with a table of
its own.  That table moves to compose.c, which both frontends now use;
xim.c is the shorter for it.

A finished sequence is text, not a key: the engine is asked to hand back
what it had pending, and the composed text follows it, so the composed
character can no longer land before the syllable typed before it.
2026-08-17 12:16:07 +09:00
a0e83f98c6 engine: a takeover keeps the text it took for the context it took it from
Two applications' focus events cross — ibus-daemon documents the case —
so the first key of the new one can arrive before the old one says it
lost focus.  The engine dropped whatever that context was composing; it
now keeps it and hands it back with the reset or release that follows,
which every frontend already commits.  Normal ordering is unchanged.
2026-08-17 12:07:19 +09:00
0f684ae6d3 gtk: a dead key hands the pending text back first
GtkIMContextSimple composes dead keys, and while it did the daemon still
held the syllable typed before them: the composed é was inserted first
and 하 reappeared after it.  A key the daemon does not take now resets
the composition when Simple begins composing on it — a modifier press,
which is also no key, leaves it alone.
2026-08-17 11:59:42 +09:00
c102c87d55 ibus: a key stands for the FocusIn a client may never send
A context that had not sent FocusIn had every key rejected outright, so
the input method was silently dead in a client that omits or delays it,
and in the documented case of two applications whose focus events cross.
ibus-daemon and fcitx5 both treat a key as focus; strans, which plays the
daemon here, now does too.  A release still does not focus, and the
engine's own owner rule is unchanged.
2026-08-17 11:58:08 +09:00
bfa919f623 ibus: name the address file as libibus does under Wayland
libibus looks for the file under WAYLAND_DISPLAY when a session has one,
DISPLAY only otherwise; strans always used DISPLAY, so in a Wayland
session it wrote <machine-id>-unix-0 while every IBus client looked for
<machine-id>-unix-wayland-0 and found nothing.  The machine id now comes
from D-Bus's copy first, as libibus reads it, and a host with neither
file gets libibus's own "machine-id" rather than a daemon that dies.
2026-08-17 11:53:40 +09:00
88ad5f7630 engine: the one capability is a flag
Cclientpreedit was a one-bit mask that every producer set as "want ?
Cclientpreedit : 0" and every consumer masked out again; nothing else
was ever going to join it.  Keyreq carries clientpre, an int that says
whether the client draws the preedit, and the engine and the XIM
context keep it under that name.
2026-08-17 10:57:15 +09:00
33023d7f51 ipc: a modifier keysym is no key
Shift, Control, and their kin were mapped to special keys that the
engine then had a range and a check to ignore, and the GTK module made a
round trip to the daemon for each press.  ipckeysym maps them to key 0,
which was already the "no key" every frontend and the engine skip;
Kmodfirst, Kmodlast, and ismodkey go.
2026-08-17 10:56:03 +09:00
b979afc3f7 xim: a spot is a baseline; a popup flipped above it clears the line
An XIM spot has no height, so a popup that flips above it at the bottom
of the screen ended on the baseline and covered the line being typed.
The spot now stands for the row above it, one popup row tall, as GTK
and IBus carets carry their line height; below the spot nothing moves.
2026-08-17 01:59:44 +09:00
dd389edc4f x11: xcb_aux_get_screen instead of two hand-rolled screen walks
win.c and xim.c each iterated the setup's roots to find the screen
xcb_connect had chosen; xcb-util, already linked through imdkit, has
xcb_aux_get_screen for that.
2026-08-17 01:58:30 +09:00
e32cea3296 engine: Backspace deletes the last kana shown; Tab converts like Space
Backspace undid a keystroke: な became ん, かんじ became かんj, きゃ
became ky.  Like every Japanese IME it now deletes the last kana as
shown — な goes, かんじ becomes かん, きゃ becomes き — while a romaji
letter that never became kana still goes one at a time.  Tab stepped
through candidates in a search and committed in Japanese; it steps
through them there too, with Space, and Shift+Tab steps back.
2026-08-17 01:55:25 +09:00
42568ab020 build, data: skk2ktrans keeps the SKK header; the recipe names every generator; an inert config line goes
An SKK dictionary's ";;" header carries its license notice, which
LICENSES/README.md relies on for kanji.dict, and skk2ktrans dropped
every ";;" line: a re-import as map/README describes lost the grant.
The leading comment block is kept now.  README's regenerate-and-verify
recipe omitted mktelex.py although verify-map checks its output; and
the Dockerfile set a shell variable named egrep in plan9's config that
9c never reads, calling egrep by name.
2026-08-17 01:49:45 +09:00
7c9e736996 xim: Compose results of any length, StatusNone styles; ibus: no cursor size check
A Compose sequence whose result is more than one character has no
keysym, so xkb_compose_state_get_one_sym gave nothing and the result was
lost; the UTF-8 the compose state holds is committed instead.  Clients
that ask for a StatusNone style — fcitx5 offers them — failed to create
an input context; the three preedit styles come in both status flavours
now.  IBus SetCursorLocation returned an error for a negative width or
height that nothing reads and no client ever waits for.
2026-08-17 01:49:06 +09:00
0cb395579f xim: one file at the top level, keymaplookup in it
xim/keymap.c held one 37-line function apart from xim.c, with its own
prototype typed by hand in xim.c and nelem spelt out because it avoided
dat.h — all so that a test could link it without imdkit, though the XIM
adapter test #includes xim.c whole anyway.  It lives in xim.c now, and
xim.c, the last file of its directory, sits beside ibus.c.
2026-08-17 01:47:40 +09:00
47f9801ad8 engine: a search just begun shows itself
Ctrl+E and Ctrl+H gave no sign until a key produced candidates; with
nothing pending the popup even went away.  An empty search now shows
☺ or 漢 in the popup, so the user knows the next keys are a query.
2026-08-17 01:45:27 +09:00
0c4271b8b5 engine: an emoji query shows as typed and Space picks
The emoji search folded the keys for its lookup and then showed and
committed the folded copy: SMILE became smile in the text.  And it
showed the transliteration only while that alone matched, so a Korean
query flipped between 웃 and key soup as it grew.  Now the query shown
is the keys while they match anything, else what they type in the
current language, both as typed; only the lookups fold.  Space in a
search picks the highlighted result as Enter does, instead of adding a
space no alias needs.
2026-08-17 01:44:29 +09:00
bab40c95a7 popup: a border, a preedit as wide as its text, one place by the pointer, and no dying
The popup was a white box on the usually white text it covered, with
no edge to tell them apart; it has a border in the separator's colour.
A syllable being composed came in a bar twelve ems wide, wiping out the
line under it: only candidate rows share that steady width now, a
preedit alone hugs its text.  A popup placed by the pointer, when the
client sends no caret, was placed again on every key and so followed
the mouse; it stays where it came up.  Clicks on it no longer fall
through to the root window and its menu.  And one failed draw — a lost
pointer reply, a pixmap the server refused — ended the draw thread and
the popup for the rest of the session; only a dead connection does now.
2026-08-17 01:41:54 +09:00
87cb03fdf8 popup: no fallbacks for work areas no desktop has; text setup cannot fail
popuplayout had two refit stages for a work area shorter than one
padded row — 40 pixels — and popupdraw re-checked the layout it had
just been handed, against an Imgh that bounded no buffer any more.
Pango and Cairo abort rather than return nil, so textinit is void and
the layout is never nil; the stride Cairo computes for RGB24 is w*4 by
definition; and textdraw set the layout's width and ellipsis twice.
2026-08-17 01:36:16 +09:00
cd637be4ca ibus: die where startup fails; print the Plan 9 way; drop what said nothing
ibusinit unwound its server and un-registered its atexit handler on a
failure whose only sequel was ibusthread dying anyway; now each failure
dies with its own message, and the test that existed to walk that
unwind goes with it.  Also gone: an empty watch-toggle callback where
libdbus takes nil, Maxconns as a second name for Maxclients, fprintf
and strerror where the rest of the daemon says fprint and %r, USED()
where a parameter can simply be unnamed, a second findcontext() for the
Properties branch, and emitcommit() taking a connection and path apart
from the context that has both.  Ownerpoll lives once, in dat.h.
2026-08-17 01:34:06 +09:00
5faefd9a87 dict: lookups take a trie; a prefix search is its own function; no self-key rule
dictlookup took a Lang for two reasons that were not its business: to
know whether to walk below the key (the emoji dictionary) and to drop a
candidate equal to the key except there.  No dictionary lists its key
among its candidates — the rule was left from a first version that put
the reading first itself — so it goes, and the walk is dictprefix(),
called by the emoji search alone.
2026-08-17 01:31:51 +09:00
3d862bdc0d engine: keys outside a map pass on like any other; one candidate query; the caret rides on every key
Space and non-ASCII keys with a Korean or Telex syllable pending were
committed as text along with the syllable, while '.', ',' and digits
committed the syllable and passed on: one path for both now, through the
language's own trans, which commits and passes on.  Japanese Space is
its own key already.

dictqjp() knows which languages have candidates; its two callers no
longer choose between it and clearkouho().  Every request carries its
owner's caret as the frontend knows it, so a Keypress copies it whether
valid or not, and the XIM frontend need not send a Keycaret to say that
its spot went away.
2026-08-17 01:30:56 +09:00
97a838eda1 engine: the Japanese keyboard's own keys; keypad arrows move through candidates
半角/全角, ひらがな/カタカナ, 変換, and 無変換 committed the reading and did
nothing, like any function key.  They now stand for what a Japanese
keyboard means by them: toggling Japanese and English, switching kana
modes, converting (or turning Japanese on), and turning it off; the
Korean keys became a switch of the same shape.  The keypad's arrows
and page keys, with NumLock off, fold onto the plain ones as its Enter
and Tab already did.
2026-08-17 01:29:07 +09:00
1c8d9fac07 engine: Ctrl+Shift chords belong to the application
Ctrl+Shift+V switched to Vietnamese and ate the key, in every terminal
where it pastes; Ctrl+Shift+T and N opened no tab.  Only a plain
Ctrl+letter is a strans command now; a chord with Shift, Alt, or Super
commits what is pending and passes, as Alt and Super chords already did.
Caps Lock still works: the keysym's case never mattered.
2026-08-17 01:27:47 +09:00
97e6f2cf1a engine: one ASCII fold, commitim through impre, no second modifier check
Four places lower-cased ASCII by hand; commitim repeated impre's three
lines to compute the text it commits; and transition() began by
rejecting modifier keys that imhandlekey's keymeaningful() had already
turned away, kept alive only by tests calling transition() directly.
The test helper now goes through the same gate.
2026-08-17 01:24:56 +09:00
852e129f62 engine: the Hanja search composes from its seed, and Escape gives it back
Ctrl+H took the pending syllable as the query, but the keys typed after
it started a composition of their own: gk, Ctrl+H, s showed 하ㄴ, and r,
Ctrl+H, k showed ㄱㅏ.  transstr now composes from a pending text, so
the search goes on where the syllable left off.  Escape ended the search
and dropped the syllable it had taken; it puts it back as pending text.
2026-08-17 01:24:14 +09:00
1285476b41 korean: ㄳ from two consonants, 가 from a vowel typed first; index jamo by scanning
libhangul, and so fcitx5-hangul, joins two lone consonants into the
compound jong they make (rt → ㄳ, split again by a vowel: rtk → ㄱ사),
and by default reorders a vowel typed before its consonant (kr → 가);
strans committed the first jamo and started over.  Backspace on a lone
ㄳ leaves ㄱ, as on a syllable.

The three jamo index tables and their -1 macros were the cho, jung, and
jong arrays inverted by hand, bounded by a Jrange the callers had to
respect; a linear scan of the arrays says the same in ten lines.
2026-08-17 01:22:52 +09:00
f0498868eb engine: Space offers the reading in Katakana last; 0 always commits the reading
Every Japanese IME converts a reading to Katakana on demand; strans made
the user switch to the Katakana mode and type again.  Space now adds
the reading in Katakana after the dictionary candidates, so a loanword
the dictionary lacks converts with one Space and Enter, and 漢字 users
still get 漢字 first.

0 committed the reading only while candidates showed; without them it
typed a 0 as well.  It now commits any Japanese reading, the counterpart
of 1-9 picking candidates, as the README always said.
2026-08-17 01:18:33 +09:00
ab0366b7f7 engine: verbs and adjectives through SKK's okuri-ari entries
A sixth of kanji.dict is SKK's okuri-ari entries, keyed by a stem and
the letter of the okurigana that follows it — かk: 書 描 掛 —, and no
reading ever ends in a letter, so かく offered 確 and 各 but never 書く,
and no verb or adjective could be converted at all.  Now every split of a
complete reading is tried, longest stem first, with the okurigana put
back: かく adds 書く and 描く after the exact candidates, よむ gives
読む, あかい 赤い, おおきい 大きい, いった 言った, かいた 書いた.  A stem
that ends in っ writes it in kana too, so いt and いっt agree.
2026-08-17 01:18:09 +09:00
f61f7ab5f8 engine: a romaji typo stays in the reading
A key that could not go on with the pending romaji, when that was not a
syllable either, committed the whole reading to the application and
passed the key on: one slip dumped かんj into the text with no way back.
The letters typed now stay in the reading as they are, like any other
IME shows them, and Backspace mends them; only a key that starts no
syllable at all still ends the composition.
2026-08-17 01:17:12 +09:00
abff5ed122 emoji: search the dictionary by prefix in the engine, not in the data
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.
2026-08-17 01:07:10 +09:00
221117f93d data(kana): Mozc's romaji table
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.
2026-08-17 00:58:36 +09:00
b77400fa86 engine: the Korean keyboard's 한/영 and 한자 keys
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.
2026-08-17 00:54:36 +09:00
63fc60dc06 engine: Korean and Telex let Escape through after committing
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.
2026-08-17 00:53:43 +09:00
e8519b4e06 engine: no candidate is chosen until the user moves to one; Space converts
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.
2026-08-17 00:53:32 +09:00
ea363ca792 font: initialise fontconfig ourselves
fontconfig 2.16 warns on every start when Pango reaches it before
FcInit(); calling it first keeps stderr quiet.
2026-08-16 21:41:36 +09:00
bab6077c75 srv, ibus: remove the endpoints on a signal too
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.
2026-08-16 21:40:42 +09:00