Description
Menu item labels that contain characters outside the Basic Multilingual Plane (most emoji, e.g. 🇺🇸, 🚀) are not handled correctly by the native bridges:
- Windows: the emoji is shown as garbage characters. The rest of the label and other items are fine.
- Linux (GNOME + gnome-shell-extension-appindicator): the tray menu does not open at all as long as any item label contains an emoji.
Characters inside the BMP, such as CJK text, work correctly on both platforms.
Version: dev.nucleusframework:composenativetray 2.1.6
Reproduction
Tray(
icon = painterResource(Res.drawable.icon),
tooltip = "Demo",
menuContent = {
Item(label = "🚀 Launch") {}
Item(label = "Exit") {}
},
)
- Windows: open the tray menu. The first item shows mojibake instead of
🚀.
- GNOME with the AppIndicator extension: click the tray icon. No menu appears.
Cause
The native bridges read Java strings with GetStringUTFChars. That function returns JNI's Modified UTF-8, not standard UTF-8: a supplementary character is encoded as its two UTF-16 surrogates, 3 bytes each (6 bytes in total), instead of one 4-byte sequence. The bytes are then passed on as if they were UTF-8.
Windows — src/native/windows/jni_bridge.c:
static char *jni_strdup(JNIEnv *env, jstring jstr) {
const char *utf = (*env)->GetStringUTFChars(env, jstr, NULL);
char *copy = _strdup(utf);
...
}
tray_windows.c converts the result with MultiByteToWideChar(CP_UTF8, ...), which treats the surrogate byte sequences as invalid and emits replacement characters. The same helper is used for the tooltip and the icon path, so an emoji in the tooltip or a non-BMP character in the path would also be affected.
Linux — src/native/linux/jni_bridge.c passes the GetStringUTFChars result straight to sni.c. In append_menu_layout, sd_bus_message_append(reply, "{sv}", "label", "s", item->label) fails with -EINVAL, because sd-bus validates that s values are valid UTF-8. menu_get_layout then returns that error for the entire GetLayout call, so the host receives no layout and shows no menu, not just a broken item. Title and tooltip go through the same path.
macOS — src/native/macos/MacTrayBridge.m also uses GetStringUTFChars. I have not tested it, but it has the same kind of input.
Suggested fix
Stop using GetStringUTFChars for text and produce standard UTF-8 instead. Two possible approaches:
- In native code, read UTF-16 with
GetStringChars / GetStringLength and convert it:
- Windows: pass it to the
W APIs directly, or use WideCharToMultiByte(CP_UTF8, ...).
- Linux / macOS: a small shared UTF-16 → UTF-8 helper, or
[NSString stringWithCharacters:length:] on macOS.
- On the Kotlin side, pass
text.encodeToByteArray() (with a trailing NUL) as ByteArray to the native methods, so every platform receives standard UTF-8 without extra native conversion code.
Environment
- ComposeNativeTray 2.1.6
- Windows: Windows 11
- Linux: GNOME 50.5, gnome-shell-extension-appindicator 65
Description
Menu item labels that contain characters outside the Basic Multilingual Plane (most emoji, e.g.
🇺🇸,🚀) are not handled correctly by the native bridges:Characters inside the BMP, such as CJK text, work correctly on both platforms.
Version:
dev.nucleusframework:composenativetray2.1.6Reproduction
🚀.Cause
The native bridges read Java strings with
GetStringUTFChars. That function returns JNI's Modified UTF-8, not standard UTF-8: a supplementary character is encoded as its two UTF-16 surrogates, 3 bytes each (6 bytes in total), instead of one 4-byte sequence. The bytes are then passed on as if they were UTF-8.Windows —
src/native/windows/jni_bridge.c:tray_windows.cconverts the result withMultiByteToWideChar(CP_UTF8, ...), which treats the surrogate byte sequences as invalid and emits replacement characters. The same helper is used for the tooltip and the icon path, so an emoji in the tooltip or a non-BMP character in the path would also be affected.Linux —
src/native/linux/jni_bridge.cpasses theGetStringUTFCharsresult straight tosni.c. Inappend_menu_layout,sd_bus_message_append(reply, "{sv}", "label", "s", item->label)fails with-EINVAL, because sd-bus validates thatsvalues are valid UTF-8.menu_get_layoutthen returns that error for the entireGetLayoutcall, so the host receives no layout and shows no menu, not just a broken item. Title and tooltip go through the same path.macOS —
src/native/macos/MacTrayBridge.malso usesGetStringUTFChars. I have not tested it, but it has the same kind of input.Suggested fix
Stop using
GetStringUTFCharsfor text and produce standard UTF-8 instead. Two possible approaches:GetStringChars/GetStringLengthand convert it:WAPIs directly, or useWideCharToMultiByte(CP_UTF8, ...).[NSString stringWithCharacters:length:]on macOS.text.encodeToByteArray()(with a trailing NUL) asByteArrayto the native methods, so every platform receives standard UTF-8 without extra native conversion code.Environment