Skip to content

Cordial 0.7.0 — typing works

Choose a tag to compare

@luohoa97 luohoa97 released this 26 Aug 03:16
· 69 commits to main since this release

Six days, 193 commits. The headline is that text entry works — you can type
in a Roblox text box and see the characters — and that Cordial now has three
host audio backends, a verified in-experience web view, and a great deal more
instrumentation than it had.

Typing works, and it is tested rather than hoped

A focused Roblox TextBox now gets a real gtk::Text placed on it, in the box's
own font and colour, with a caret where a caret belongs. The engine sends the
geometry every time; Cordial could not receive it because the
NativeTextBoxInfo constructor hook was one argument short of the dex's
fifteen, so showKeyboard was handed a null and the whole path looked as though
Roblox volunteered nothing. It volunteers everything.

tools/text-input-e2e.py asserts thirty-six things about it and fails rather
than warns — typing, backspace, Home, End, insertion at the caret, shift-arrow
selection replaced by the next key, select-all overtyped, double-click word
selection, Delete, cut, a clipboard round trip, click-to-position at both ends
of the field through a real compositor click, Escape, refocus, and no text key
reaching the game while a box has focus. Four consecutive clean runs.

Two instruments had to be built before any of that could be believed. There was
no readback of typed text at all — the editor is a GTK widget and
cordial_screenshot photographs the engine's Vulkan swapchain, which cannot see
one — so cordial_textbox now reports what the field actually holds. And the
old harness ran under cage, whose headless seat never gains a keyboard, so
Cordial never bound its own wl_keyboard and the guard against double insertion
never ran. Under sway with a keyboard held open across the whole run, five keys
produce five characters. Measured, not inferred.

Audio: three backends, each verified against a real device

CORDIAL_AUDIO_HOST selects between pipewire (the default), pulse and
alsa. Each is dlopen'd and never linked, so none adds a runtime dependency,
and each reports what the device actually granted rather than what was asked
for — the engine opens a stream and reads back what it got, and FMOD multiplies
the burst by nine to size itself, so a guessed figure is a buffer nine times the
wrong size.

ALSA owns its own writer thread, because unlike the other two it is handed no
callback: snd_pcm_writei blocking is the pacing. docs/adr/ADR-023 records
the design, including what does not survive the port — an empty audio-sink
setting on PipeWire and PulseAudio means "follow the default sink and keep
following it", and ALSA resolves default once at open.

Two probes come with them, because waiting for the engine to open audio is not a
test: across six launches that reached a signed-in Home page, it opened a stream
on exactly one.

The in-experience web view, proved end to end

Account settings, buying Robux, anything the client keeps inside itself: the
dialog opens over the engine's canvas, a real page renders in it, and the
JavaScript bridge round-trips into signalJavascriptCallback. That last part
was invisible until now, because it only fires when a Roblox page decides to use
it; CORDIAL_WEBVIEW_BRIDGE_TEST=1 makes the page call it directly, through the
same handler, the same origin and size policy, and the same sink.

One real limitation found in the process and reported loudly rather than
silently: this build of WebKitGTK has no WebAuthn at all, so a passkey sign-in
cannot complete in that window.

X11 is a supported backend again

ADR-011 said X11 was not developed further and scheduled its deletion. ADR-024
reverses that, because the argument it was decided on never contemplated a user
who cannot run Wayland — and one turned up. Wayland stays primary. X11 does not
have the editor yet, so typing there is still invisible; the ADR says so and
says what it will cost.

A whole core back

The engine busy-polls ALooper_pollOnce(0) with no blocking point, so it
free-runs at whatever rate the machine sustains. After sixty-four consecutive
empty polls Cordial now sleeps 250µs before looking again, and any real event,
any wake and any new descriptor resets it. Against a control in one session:
9,670,516 polls a second at 99.7% of a core, down to 3,261 at 1.2%, with the
median frame rate unchanged at 240 against 237.

It is gated on the engine having drawn 120 frames, so it cannot touch startup.

Also

  • The window no longer leaves a trail, and the canvas no longer goes black when
    it is restacked — five commits of opaque-region and subsurface-stacking work.
  • Linking.openURL is answered, so external links open in your browser.
  • Held mouse buttons are released when the pointer leaves the canvas, and a
    pointer lock the compositor switched off is noticed and recovered.
  • The Android ABI is a property of the build now rather than a string hardcoded
    in two crates, which is the groundwork an ARM64 port needs.
  • cargo test --workspace runs in CI. It ran nowhere before.

Known and unfixed

A startup freeze on roughly a third of launches, measured over eighty runs
rather than guessed at: the client reaches the home page, presents a frame or
two, and stops. Relaunching is the only remedy today. docs/NEXT.md §0 has
everything established about it, including four theories killed by measurement
and one of mine retracted two commits after I published it.

The canvas goes black when a TextBox focuses inside an experience, which is
the top open bug.

Full history in docs/NEXT.md, which is candid about what is broken and keeps
its retractions rather than editing them away.