Skip to content

fix(jni): hand the native bridges standard UTF-8, not modified UTF-8 - #442

Open
xchacha20-poly1305 wants to merge 1 commit into
NucleusFramework:masterfrom
xchacha20-poly1305:fix-utf8
Open

xchacha20-poly1305 wants to merge 1 commit into
NucleusFramework:masterfrom
xchacha20-poly1305:fix-utf8

Conversation

@xchacha20-poly1305

Copy link
Copy Markdown

Fixes #441.

Problem

All three JNI bridges read Java strings with GetStringUTFChars, which returns JNI's modified UTF-8: a supplementary character (most emoji) comes back as its two UTF-16 surrogates encoded separately, 3 bytes each, instead of one 4-byte sequence. The bytes were then handed to consumers that expect real UTF-8:

  • WindowsMultiByteToWideChar(CP_UTF8, ...) treats the surrogate sequences as invalid and substitutes replacement characters, so the label shows mojibake.
  • Linux — sd-bus validates s values as UTF-8 and fails append_menu_layout with -EINVAL, which fails the whole GetLayout call, so the menu does not open at all.
  • macOS — same input, same helper.

Fix

A shared src/native/shared/jni_utf8.h provides jni_utf8_dup(), which reads UTF-16 with GetStringChars / GetStringLength and encodes standard UTF-8 itself. Unpaired surrogates have no UTF-8 form and become U+FFFD, so the result is always valid UTF-8 whatever the Java string contains — sd-bus can never reject a label again.

Every string entry point in the three bridges now goes through it: labels, tooltips, titles, icon paths and shortcut keys. On Windows this also replaces the jni_strdup helper; on macOS it replaces the GetStringUTFChars + strdup pairs and folds away the null checks, since the helper maps a null jstring to NULL.

The three build configurations gained the shared include path.

Tests

src/native/shared/test_jni_utf8.c (plus run_jni_utf8_test.sh, wired into the Linux CI job next to the #436 test) exercises the helper against a stub JNIEnv, so it needs no JVM: ASCII, BMP CJK, a supplementary character in a label, two consecutive surrogate pairs, unpaired high and low surrogates, the 2- and 3-byte boundaries, and a null jstring.

End-to-end on GNOME, driving the built libLinuxTray.so from a JNI harness with an emoji label and querying the menu over D-Bus:

  • before: busctl ... com.canonical.dbusmenu GetLayoutCall failed: Invalid argument (-EINVAL), matching the report
  • after: the full layout, with label s "\360\237\232\200 Launch" — the correct 4-byte encoding of U+1F680

The Windows and macOS bridges were not compiled here (Linux-only machine); the change is the same substitution on all three, and tray_windows.c already converts with CP_UTF8 and renders through the W APIs, so correct input is all it needs.

…ucleusFramework#441)

GetStringUTFChars returns JNI's modified UTF-8, where a supplementary
character arrives as its two UTF-16 surrogates encoded separately. All
three bridges passed that straight to consumers that expect real UTF-8:
MultiByteToWideChar on Windows turned an emoji label into mojibake, and
sd-bus rejected the GetLayout reply with -EINVAL on Linux, which cost the
whole menu rather than one item.

Replace the per-bridge string handling with a shared jni_utf8_dup, which
reads UTF-16 via GetStringChars and encodes it itself, mapping unpaired
surrogates to U+FFFD so the result is always valid UTF-8. Labels,
tooltips, titles, icon paths and shortcuts all go through it.

Verified on GNOME: with the previous library GetLayout failed with
-EINVAL for an emoji label; it now returns the layout with correct UTF-8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kdroidFilter

Copy link
Copy Markdown
Collaborator

Thanks, I will check this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Emoji in menu labels are garbled on Windows and break the whole menu on Linux (Modified UTF-8 from GetStringUTFChars)

2 participants