srvnote and addrnote unlinked on any note at all, and plan9port marks
SIGPIPE Ignore: notify.c:59 lists it, and signotify runs the handler
chain first and only then finds the Ignore flag and returns. So one
broken pipe took the IPC socket and the IBus address file away from a
daemon that went on running -- measured on a private runtime dir, a
single kill -PIPE left the process in state Ssl with both files gone,
so every client that focused a widget afterwards silently had no input
method and only a restart brought it back.
libxcb writes with writev, so the note is a broken X connection away;
today xim.c's die() masks it by taking the daemon down on the same
event, which is exactly why the two must not depend on each other.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
The trie walked a key byte by byte, so a Hangul syllable cost three
nodes and every lookup first spelled its key back into UTF-8. It walks
runes now: a third of the nodes for Korean data, and no buffer in the
lookup. Children are kept in rune order, so both the search and the
insert stop early, and a trie remembers the path of the last key put,
which a file sorted by key — hanja.dict is one — walks straight down.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.