backbytes copied the runes into a Str and encoded them into a 257-byte
stack buffer only to throw the bytes away and keep the length. runelen
answers over the same runes with neither.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The surrounding_text event was taken and thrown away. It carries the
text around the cursor with a byte offset into it, and belongs to the
activation like the content type, so it is pending until done and
starts empty at every activate. delete_surrounding_text counts bytes
where the engine counts runes, and the frontend holds the text those
runes are in, so it measures them itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
grabrepeat was an empty body, and under the grab the compositor feeds the
client nothing while a key is down: one press, then only repeat_info,
leaving the repeat to whoever holds the grab. So a key strans ate acted
once however long it was held. Hold Backspace over a syllable and one
jamo goes; hold a jamo key and you get one where every other window gives
four a second. X11 has none of this -- the server auto-repeats and xim.c
dispatches every press -- so the same keyboard behaved differently
depending on which frontend the window went through.
Under headless sway, which advertises repeat_info(25, 600), holding
Backspace 1.5s over 라 with 가가가나다 committed behind it:
before preedit 라 -> ㄹ, and no key at the client at all
after preedit 라 -> ㄹ -> empty, then 9 BackSpace at the client
fcitx5 and kime both keep driving the repeat after the engine stops
wanting the key, and both then must send a release before every press, or
the client sees a key held down and starts a repeat of its own on top of
theirs. That machinery exists only to undo the choice that created it.
strans does not need it: forward() already hands a key the engine did not
take to the client as a key, and the client repeats that correctly -- the
same hold with nothing pending, which takes that untouched path, delivers
24. So the rule is one sentence. strans repeats what the engine takes;
when the engine stops taking it, presskey has already passed the key on,
the deadline is dropped, and the client repeats it from there. 9 against
24 is that handover: driving every tick here would have made them one
number.
It costs one pause of the client's own delay, 600ms, where the preedit
empties. It buys no release-and-press fiction anywhere in the file, no
second repeat engine, and no timestamp arithmetic -- exactly one key is
forwarded per hold, so there is no run of stale timestamps to mend.
nsec() is gettimeofday, which steps; a deadline wants the clock ipc.c
already uses. The owner poll stands down while a key repeats, because the
repeat's own Keypress carries our owner and takes the engine back, so it
would have nothing to find.
Arming needs the engine and cannot be reached from the unit suite; ending
a repeat can, and a repeat outliving its key is the worst this could do.
wl/repeat-ends was checked by breaking it five ways -- a constant rate, a
constant delay, the empty body back again, the key's own release no longer
ending it, and a repeat into a dead context staying armed -- each failing
only its own line.
Phases A-G unchanged at both scales; 90 unit, check-live, check-stress and
valgrind clean. One limit worth writing down: the GTK probe prints the
BackSpace it receives and never deletes on one, on this path or on the
plain pass-through, so the phase proves the keys arrive and says nothing
about the text behind them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Another input method taking the seat made wlthread hide its popup and
return, leaving activeowner pointing at our context with its text still
pending, and whatever keys we had passed on still down at the client.
leave() is that cleanup and all of it: it gives the keys back, drops a
half-typed sequence, sends the Keyrelease and takes the popup down.
Nothing visible changes either way -- the compositor drops anything an
unavailable input method commits, and the next key from any other
frontend takes the engine regardless. This is the engine left tidy, in
the one word that already means it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e64e4ea took the picture out of the owner poll and argued a later take
could not show a stale one: "either our own key replaced it, or the
engine suppressed the send because our snapshot is that same picture".
The second half is the hole. redraw() returns before it drains when the
picture has not changed, and under Wayland wl is drawc's only reader, so
every IBus interaction leaves its last picture sitting there -- the mode
mark after Ctrl+T, a hanja list, whatever was up. Press a bare modifier
in a text-input-v3 window while IBus owns the engine: keymeaningful(0) is
false, the engine changes nothing, the send is suppressed, and grabkey's
takedraw puts the other window's candidates on our popup surface.
I could not make it visible. checkowner() runs later in the same loop
iteration, finds we are not the owner and hides it, and both commits
leave in one wl_display_flush, so the compositor renders nothing between
them. What is wrong is the rule: README says an IBus client under
Wayland "shows no candidates, because the popup belongs to the Wayland
frontend", and that is now true by construction rather than by luck.
leave() and flushpending() already take only while engaged, so the guard
costs nothing where it is and covers all three callers. The drain still
happens either way, which is what keeps drawc and lastdraw in step.
Phases A-G under headless sway at both scales; the phase E hanja popup
is unchanged.
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>
It took the engine's picture and drew it, then took the popup straight back
down: a buffer allocated, filled and attached for nothing, and for one
frame it was another context's candidates at our client's cursor. The
drain it was doing is not needed either. redraw() empties drawc before
every send, so the channel holds at most one picture and that picture is
always lastdraw -- what the engine wants shown now, whoever owns it. A
later take therefore cannot show something stale: either our own key
replaced it, or the engine suppressed the send because our snapshot is
that same picture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
40fd4ab was half right and the sway run said which half. The compositor
does relay the commit when a client merely disabled its text input while
holding the focus -- that much was read correctly out of sway -- but the
client has stopped listening by then. On the wire, in that order:
-> zwp_text_input_v3.disable()
-> zwp_text_input_v3.commit()
<- zwp_text_input_v3.preedit_string("")
<- zwp_text_input_v3.commit_string("가")
<- zwp_text_input_v3.done()
and the entry unchanged. GTK3 drops global->current at its own
focus-out, and any client that follows text-input-v3 does the same, since
events between disable and the next enable are to be ignored. So the
composition is unrecoverable there, by no fault of ours, and the commit
was a request nobody could take.
The code goes back to what it was; the comment does not. The old one
said the compositor drops the commit, which is true only when the focus
moved, and that half-truth is what invited the change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both popups read it, each just before its own textinit, and the second
copy needed a comment to say why it was there at all. One frontend runs
per session and popupscale is one global, so threadmain reads it before it
starts either -- which is before any setfont, the only ordering the two
copies were keeping.
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>
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>
An activate and a deactivate in one batch left the grab taken with nothing
active, which is the one state the grab must never be in: sway hands keys
to the grab holder without asking whether the input method is active.
sway cannot produce that batch -- relay_send_im_state sends a done after
each -- so this is the table being total, not a bug that bit. The
deactivate arm was already right; only the activate arm forgot to look at
where the batch ended.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bitmap held 256, and forward dropped anything above it rather than
passing it on, so a key the engine does not want vanished instead of
reaching the client. Keys do live up there: KEY_FN is 0x1d0,
KEY_VOICECOMMAND 0x246, KEY_MACRO1 0x290, and xkeyboard-config maps
keycodes to <I709>, so they carry a keysym and are nothing special to us.
sway matches its bindings before it hands a key to the grab, so an unbound
one of those was lost for as long as any text field had the focus.
The whole evdev range costs 96 bytes of bitmap instead of 32. The test
already writes the bound rather than the number, so it still says what it
said.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pendingpurpose was never cleared, and sway sends content_type only when
the text input asked for one: active_features is frozen at the enable
commit (wlr_text_input_v3.c) and relay_send_im_state tests it before
sending. So a client that sets no content type leaves the last client's
purpose standing -- and after a GTK password entry that purpose is
password, so every key in the next window was forwarded raw and strans
looked dead there until a client that does declare one took the focus.
A purpose is per activation, as it is per context in ibus.c. Clearing it
on activate is enough: the content type, if any, arrives after the
activate and before the done that applies both.
The sway runs could not have found this; the password client was GTK,
which always declares.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things arrive as a deactivate. When the focus moved, the compositor
sent the text input its leave first, and sway's handle_im_commit finds no
focused text input and drops the whole commit -- which is what the old
comment described. But a client that merely disabled its text input
still holds the focus: sway relays that deactivate only in that case
(handle_text_input_disable returns early once the surface is unfocused),
so the commit would have been delivered, and half a syllable was thrown
away instead. A GtkEntry losing the focus to a button in its own window
is that case.
XIM and IBus both hand the text back there and drop it only when the
client itself is gone -- ibus.c keeps the two apart as flushcontext and
releasecontext. This gives the Wayland frontend the same halves. The
commit that goes nowhere costs one empty request.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One wl_surface and one zwp_input_popup_surface_v2, made once; the
compositor makes them visible on activate, puts them at the text cursor
and takes them down again, so popuparea, popupposition and Caret stay
X11-only and popuplayout gets a constant area. Two shm buffers are used
in turn, each busy from attach until the compositor releases it, because
win.c's single grow-only image would be redrawn while the compositor was
still reading it; a picture that arrives while both are busy waits for
the next release, and the engine will not send it twice. drawthread no
longer runs, so wl.c reads GDK_SCALE itself and calls textinit after it,
since setfont reads Fontsz. The buffer is rounded up to a multiple of
the scale: one that is not is an invalid_size error at attach, and a
preedit alone really does lay out to an odd width at GDK_SCALE=2.
drawc is taken where the engine's picture and our surface can part:
after every key, after the Keyrelease a deactivate sends, and after the
owner poll, hiding for the last two. The poll is not an optimisation --
once another frontend takes the engine, its pictures go unconsumed and
no later send will ever say hide.
main.c now starts either the Wayland frontend or the X11 popup and XIM,
which is where the two popups would otherwise collide.
Checked by hand under headless sway 1.12, reading grim screenshots: the
hanja list for 가 with its selection and 1-9/125 marker, the emoji
search, the popup gone after Escape, and the same at GDK_SCALE=2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
zwp_input_method_v2 over one connection, in one process: activate,
deactivate and content type applied at done, whose count is the serial a
commit must carry. The keyboard is grabbed at every activate and
released at every deactivate, because sway hands keys to the grab holder
without asking whether the input method is active, and a grab held while
inactive would take every key in the session. Keys come as evdev codes,
go through xkb and the Compose table the other frontends already share,
and reach the engine as an ipckeysym and a modifier mask; the grab's
modifiers go on to the virtual keyboard as well, since wlroots derives no
state from a virtual keyboard's own keys. A key the engine does not eat
goes back as a key, not as text, and whatever is still down is released
when the context goes. A password or a PIN purpose never reaches the
engine at all.
No popup yet: the X11 one still owns drawc, and with no Keycaret from us
it follows the pointer.
Checked by hand under headless sway 1.12 with a GTK3 entry driven by
wtype: ascii passes through, Ctrl+s selects Korean, rk shows the preedit
and Enter commits 가 and lets the Return on, Ctrl+c arrives with its
modifier, focus moving between two clients re-grabs and keeps typing, a
password entry types literally, a second strans says so and stops, and
WAYLAND_DISPLAY without a compositor falls back to XIM.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>