isprivate() reads the GTK input purpose and nothing else, but the purpose
is not the only way an application marks a password field, and it is not
the older one. GTK 3.24.52, measured:
gtk_entry_set_visibility(entry, FALSE) purpose stays FREE_FORM
gtk_entry_set_input_purpose(PASSWORD) purpose PASSWORD
so an entry hidden the first way looks like ordinary text to the module.
This machine runs one. /usr/libexec/xfce-polkit, the XFCE authentication
dialog, is up right now under GTK_IM_MODULE=strans, and its binary calls
gtk_entry_set_visibility and never gtk_entry_set_input_purpose, so every
password typed into it goes through the engine. Against the real daemon,
in Korean mode:
typed hunter2 stored ㅗㅕ숟ㄱ2
typed correcthorse stored 책ㄱㄷㅊ쇅ㄴㄷ
typed P4ssw0rd stored ㅔ4ㄴㄵ0ㄱㅇ
The field draws bullets, so nothing on screen says why the authentication
failed -- except the pending syllable, which is drawn as itself: three
keys into such an entry the old module leaves 한 on screen where the
field should read ●●●. With this it reads ●●●, and the entry holds gks.
set_visibility sends the input context no signal, so the question cannot
be answered where the purpose is, in init and notify::input-purpose. It
has to be asked at the key, of the client window, which is the entry's
own: gdk_window_get_user_data on it returns the GtkEntry. fcitx5-gtk asks
it the same way in all three of its GTK versions -- gtk3/fcitximcontext
.cpp:1155-1162, under the comment "seems visibility != PASSWORD hint".
It costs one field read and one type check per key, on a path that then
does a socket round trip anyway. It does not cover XIM: that protocol has
no attribute for this, and no fcitx5 frontend answers it either -- only
its GTK and Qt client modules do -- so an X client reached over XIM still
composes in its password field. Nor does it notice a "show password" box
switched off in the middle of a composition: there is no signal for
visibility, so the pending text stays in the daemon until the next focus
change. Noticing that needs a signal connection on a widget the module
does not own, which is a bigger thing than the hole is.
The test is a live one and it earns its line: with the fix removed it says
gtk_live_test: hidden-entry key reached daemon or did not commit
Both guards were checked by removing them. Without GTK_IS_ENTRY the run
takes a Gtk-CRITICAL and fails; without the im->win test it exits 139, on
the stalled-peer context, which never gets a client window. An ishidden
that always answers yes fails earlier still, at the initial protocol
frames. Breaking the test's own helper so it hands back the toplevel's
window instead of the entry's fails too, which is what says the assertion
watches the right window rather than merely counting no events.
That count is the one thing that had to be made deterministic. A new
client window invalidates the caret, and the fake daemon records that
frame on its own thread, so sampling the event count straight after
set_client_window raced it: 2 failures in 8 runs. The block now waits for
that frame and names it, and there were 0 in 20 after.
90 unit, check-live, check-stress and valgrind clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
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.
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.
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.
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.