Draw Steam's CJK and Thai text with the device's fonts (#374)

Steam ships no CJK font and the rootfs only has DejaVu, so a phone set to
Chinese, Japanese or Korean got Steam in that language with every character
drawn as a box. Bind Android's /system/fonts into the guest, index it again
whenever it changes, and prefer the matching Noto CJK face for each language.
This commit is contained in:
MaxsTechReview authored and GitHub committed 2026-10-07 02:23:27 -04:00
1 parent 61953c4f7b
commit ef38c975b9
3 files changed
+33 -1

No files matched your search

@@ -42,6 +42,8 @@ public final class LinuxRuntime {
* session ended at once. A long host path behind it is proot's to shorten.
*/
public static final String GUEST_RUNTIME_DIR = "/run/droiddeck";
private static final String SYSTEM_FONTS = "/system/fonts";
private static final String GUEST_SYSTEM_FONTS = "/usr/local/share/fonts/android";
/** Where every Linux session's debug log lands: public, so a user can just hand the folder over. */
public static final String DEBUG_LOG_DIR = "DroidDeck";
@@ -207,6 +209,7 @@ public final class LinuxRuntime {
File shm = new File(context.getCacheDir(), "shm");
shm.mkdirs();
bind(cmd, shm.getPath() + ":/dev/shm");
bindSystemFonts(cmd, root);
// Android denies apps these; glibc, Steam and libcap read them at startup.
File fakeProc = new File(root, "etc/droiddeck/proc");
@@ -465,6 +468,15 @@ public final class LinuxRuntime {
return byType;
}
private static void bindSystemFonts(List<String> cmd, File root) {
File fonts = new File(SYSTEM_FONTS);
File target = new File(root, GUEST_SYSTEM_FONTS.substring(1));
if (!fonts.isDirectory() || !fonts.canRead() || !new File(root, "usr/local").isDirectory()) return;
if (target.isDirectory() || target.mkdirs()) {
bind(cmd, SYSTEM_FONTS + ":" + GUEST_SYSTEM_FONTS);
}
}
private static void bind(List<String> cmd, String spec) {
cmd.add("-b");
cmd.add(spec);
+1 -1
View File
@@ -92,7 +92,7 @@ find "$XDG_RUNTIME_DIR" -mindepth 1 -maxdepth 1 ! -name 'wayland-0*' ! -name 'ke
# fontconfig's rules (sans-serif -> DejaVu Sans and the rest) live as links in /etc/fonts/conf.d,
# and the rootfs has none: applications then find fonts but no default font. Firefox logs
# "no fonts" and draws pages without text.
if [ -d /usr/share/fontconfig/conf.default ] && ! ls /etc/fonts/conf.d/*.conf >/dev/null 2>&1; then
if [ -d /usr/share/fontconfig/conf.default ] && ! ls /etc/fonts/conf.d/*.conf 2>/dev/null | grep -qv droiddeck; then
for f in /usr/share/fontconfig/conf.default/*.conf; do ln -sf "$f" /etc/fonts/conf.d/; done
echo "== fontconfig: linked $(ls /etc/fonts/conf.d/*.conf | wc -l) default rules"
fi
@@ -136,6 +136,26 @@ program_cores() {
}
if [ -z "${BL_INSIDE:-}" ]; then
device_fonts=/usr/local/share/fonts/android
if [ -d "$device_fonts" ] && [ -d /etc/fonts/conf.d ] && command -v fc-cache >/dev/null 2>&1; then
fonts_rules=$(
echo '<?xml version="1.0"?><!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"><fontconfig>'
for pair in "zh-cn=Noto Sans CJK SC" "zh-sg=Noto Sans CJK SC" "zh-tw=Noto Sans CJK TC" "zh-hk=Noto Sans CJK HK" \
"zh-mo=Noto Sans CJK HK" "ja=Noto Sans CJK JP" "ko=Noto Sans CJK KR" "th=Noto Sans Thai"; do
printf '<match target="pattern"><test name="lang" compare="contains"><string>%s</string></test><edit name="family" mode="prepend_first"><string>%s</string></edit></match>\n' \
"${pair%%=*}" "${pair#*=}"
done
echo '</fontconfig>'
)
fonts_conf=/etc/fonts/conf.d/66-droiddeck-device-fonts.conf
[ "$fonts_rules" = "$(cat "$fonts_conf" 2>/dev/null)" ] || printf '%s\n' "$fonts_rules" > "$fonts_conf"
fonts_seen=$(ls -lL "$device_fonts" 2>/dev/null | md5sum)
fonts_stamp=/var/cache/fontconfig/.droiddeck-android-fonts
if [ "$fonts_seen" != "$(cat "$fonts_stamp" 2>/dev/null)" ]; then
fc-cache -f "${device_fonts%/*}" >/dev/null 2>&1 && printf '%s\n' "$fonts_seen" > "$fonts_stamp"
echo "== fontconfig: indexed $(ls "$device_fonts" | wc -l) device fonts, zh falls back to $(fc-match -f '%{family[0]}' ':lang=zh-cn' 2>/dev/null)"
fi
fi
# The compositor may still be starting, and a socket left by an earlier session is not
# a compositor: probe until a connection succeeds.
for _ in $(seq 1 100); do