Exercises the runtime paths that had never run (details in docs/rewrite/notes/verification-VR.md): - IME: replay steps drive the surface's real GtkIMContext handlers with a real preedit + attributes; preedit renders under the cursor and commits reach the pty, incl. dead-key and compose orderings. - Paste and OSC 52 read/write confirmation dialogs: allow, deny and remember flows. - Quick terminal on niri via wlr-layer-shell: positions, sizes, toggle and autohide. Fixes a real bug: link order could put libwayland-client ahead of libgtk4-layer-shell, silently disabling the quick terminal; apprt-gtk now loads libwayland-client via wayland-sys dlopen. - Flatpak host commands against a zbus mock of org.freedesktop.Flatpak.Development under dbus-run-session: spawn, exit status, cwd/env/fd forwarding and SIGHUP on stop, both directly and through termio Exec (flatpak-info path seam added). - Horizontal-scroll tab switching thresholds and reset. tests/gtk_replay.rs runs the real binary against the session display (skips without one). Test Plan: - cargo test --test gtk_replay (needs a Wayland/X11 display) - cargo test -p ghostty-os --test flatpak_mock; -p ghostty-termio --test flatpak_exec
6.7 KiB
Verification VR (IME, dialogs, quick terminal, flatpak host command, hscroll)
Replay hook additions (crates/ghostty-apprt-gtk/src/debug_input.rs, class/surface.rs)
- IME:
ime-preedit <text>,ime-preedit-end,ime-commit <text>,ime-state,ime-key <preedit|commit|commit-simple|handled-commit|pass> <key> [text]. The steps call the surface's real template callbacks (im_preedit_start/changed/end,im_commit, nowpub(super)and taking>k::IMContext) with amock_im::MockIm(a realGtkIMContextsubclass whosepreedit_string()returns text + underline attrs + cursor) as emitter.ime-keyruns the realin_keyevent/im_composingbookkeeping: the IM half ofSurface::key_eventwas extracted intoSurface::im_filter(filter)(behaviour unchanged,key_eventpassesfilter_keypress). Orders covered: ibus (commit then preedit-end), simple (preedit-end then commit), dead key (handled while composing -> not encoded), plain translation commit (handled, encoded with the key), pass. Zig has noretrieve-surrounding/delete-surroundinghandlers (checked surface.zig), so there is nothing to port. - Dialogs:
dialog-info,dialog-remember,dialog-click <label>(clicks the real GtkButton in the AdwAlertDialog, so the AdwAlertDialog response ->confirm/denysignals run).animations off|on. - Quick terminal:
windows-dumpnow logs per GhosttyWindow: quick_terminal, visible, mapped, active, size and the layer-shell state (layer, keyboard mode, namespace, anchors, exclusive zone);quick-focus-outemitsnotify::is-activeon the quick window (the window is never compositor-focused here) to run the autohide handler. hscroll <dx>(the handler body was moved toSurface::scroll_horizontal_impl; the controller keeps the unit check),tabs-dump. Replay target window: if GTK's active window is hidden (autohidden quick terminal) the first visible Ghostty window is used.
Results
| Area | Result |
|---|---|
| IME preedit | PASS. ime-preedit かn renders the underlined preedit at the cursor (screenshot); core preedit cleared on commit/end. |
| IME commit / dead keys | PASS. pty receives こんáxyé (cat -v also shows the exact UTF-8 bytes); preedit text never reaches the pty; composing/consumed state sequence matches Zig's logic. |
| Unsafe paste dialog | PASS. Dialog (heading, body, Deny/Allow, contents) verified structurally; Deny writes nothing, Allow writes the text once. |
| OSC 52 write=ask | PASS. Deny keeps the clipboard, Allow writes it, Allow+remember (switch) makes later writes silent (clipboard_write -> allow). |
| OSC 52 read=ask | PASS. Allow: pty gets ESC]52;c;c2VjcmV0...; Deny: nothing sent. |
| Quick terminal | PASS after the link-order fix below: layer surface (visible in niri msg layers: Top/Overlay, namespace ghostty-quick-terminal, keyboard interactivity), anchors/size for top/bottom/left/right/center match Zig's quick-terminal-size and anchor logic, toggle hides and re-shows, autohide on focus loss (default false on Linux, as Zig) hides, autohide=false stays. |
| hscroll tabs | PASS (threshold 120, direction, no wrap at the ends, 500 ms accumulator reset). |
| Flatpak host command | PASS against a mock service (see below). |
Not verifiable here: real keyboard focus on the layer surface (quick-terminal-keyboard-interactivity=exclusive is
applied, niri msg layers shows it, but GtkWindow::is_active stays false because the replayed windows are never
compositor-focused; the autohide path was driven with quick-focus-out); KDE slide animation (needs the KDE protocol,
niri has none); rendering of AdwDialog in screenshots (window-screenshot does not capture the dialog layer; use
dialog-info); a real input method (ibus/fcitx are not available).
Bug found and fixed: gtk4-layer-shell load order
libgtk4-layer-shell must be loaded before libwayland-client. The Rust binary's DT_NEEDED order depends on how
rustc orders native libs, which flipped between builds (adding an unrelated gtk4_layer_shell:: path in a file was
enough); with the wrong order is_supported() is false and the quick terminal is silently disabled ("compositor does
not support wlr-layer-shell"). Fix: wayland-sys with features client,dlopen is a direct dependency of apprt-gtk,
so libwayland-client is no longer a direct DT_NEEDED of the binary (it is only pulled in transitively by GTK, which
is loaded after all direct libraries, same effect as Zig's explicit link order). Check: readelf -d target/*/ghostty | grep NEEDED has no libwayland-client. Regression: quick_terminal_layer_surface_toggle_and_autohide.
Flatpak host command
- Seam:
ghostty_os::flatpak::set_flatpak_info_path(path)(OnceLock, production code, unused by the app) makesis_flatpak()check that path instead of/.flatpak-info. - Mock service:
crates/ghostty-os/tests/common/flatpak_mock.rs(zbus, claimsorg.freedesktop.Flatpak, implementsDevelopment.HostCommand/HostCommandSignal, really runs the command locally with the received stdio fds in its own process group, emitsHostCommandExitedwith the raw wait status, records spawns and signals). Tests re-exec themselves underdbus-run-session(skipped if it is missing). crates/ghostty-os/tests/flatpak_mock.rs: spawn returns host pid, cwd/env/fds forwarded, stdout via passed fd, exit status propagated (7),waitstate reset,wait_callback, host spawn failure ->FlatpakSpawnFail,signal_handle().signal(1, true)after the command moved into the watcher -> recorded(pid, 1, true)and the host process exits.crates/ghostty-termio/tests/flatpak_exec.rs(dev-deps: zbus, rustix): termioExecflatpak branch end to end (output on the pty, exit code 5 propagated as ChildExited, input echoed through the pty,stop()sends SIGHUP to the host process group and the host process dies). One test per binary: the override is process-global.
Regression tests
tests/gtk_replay.rs(root package, runs the real binary on the current Wayland/X11 session; skips without a display or withGHOSTTY_SKIP_GTK_REPLAY): IME flow, paste deny/confirm, OSC 52 write/read ask (+remember), quick terminal layer surface/toggle/autohide/no-autohide, horizontal scroll tab switching. ~45 s total, serialized (they share the session clipboard).- Parser unit tests for the new steps in
debug_input.rs.
Notes
screenshotcan be one frame stale right after input (wait >= 1 s);textafterkey escapein bash is meta-prefixed by readline (not a bug).- Child programs reading the tty from
sh -cneedtimeout --foreground(job-control SIGTTIN otherwise). quick-terminal-autohidedefaults to false on Linux (as Zig), so the quick terminal does not hide on focus loss by default.