local: MaliBridge rc4, auto local DNS, KWin render node

This commit is contained in:
openMali committed 2026-11-01 15:17:41 +08:00
1 parent 69ffc0966b
commit 9419a19c66
11 files changed
+86 -14

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
# Copy to .signing.env, fill in, chmod 600 .signing.env
RELEASE_KEYSTORE=/path/to/release.p12
RELEASE_STORE_PASSWORD=change-me
RELEASE_KEY_ALIAS=droiddeck
+1 -2
View File
@@ -219,8 +219,7 @@ android {
jniLibs { jniLibs {
useLegacyPackaging = true useLegacyPackaging = true
pickFirsts += ['**/libtermux.so'] pickFirsts += ['**/libtermux.so']
// Shipped in the proot and tracking packs (tools/packs) and run or loaded from there. excludes += ['**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so']
excludes += ['**/libproot.so', '**/libproot-loader.so', '**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so']
} }
} }
@@ -65,7 +65,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
} }
fun state() = GpuDriversState( fun state() = GpuDriversState(
gpuName = gpu.displayName(activity), gpuFamily = gpu.family.label(activity), soc = gpu.soc, supportText = gpu.supportText(activity), gpuName = gpu.displayName(activity), gpuFamily = gpu.family.label(activity), soc = gpu.soc, supportText = gpu.supportText(activity, com.droiddeck.launcher.core.DeviceSupport.vkBridge(activity)),
supported = gpu.support == GpuInfo.Support.TESTED, unsupported = gpu.support == GpuInfo.Support.UNSUPPORTED, supported = gpu.support == GpuInfo.Support.TESTED, unsupported = gpu.support == GpuInfo.Support.UNSUPPORTED,
auto = mode == SessionPrefs.GPU_DRIVERS_AUTO, pairs = pairRows, busy = pairBusy, percent = pairPercent, auto = mode == SessionPrefs.GPU_DRIVERS_AUTO, pairs = pairRows, busy = pairBusy, percent = pairPercent,
autoStatus = autoStatus, releaseStatus = releaseStatus.ifEmpty { activity.getString(R.string.drivers_not_checked) }, checking = releaseChecking, autoStatus = autoStatus, releaseStatus = releaseStatus.ifEmpty { activity.getString(R.string.drivers_not_checked) }, checking = releaseChecking,
@@ -264,7 +264,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
return return
} }
if (DriverPairs.recommendedKey(gpu, emptyList()) == null) { if (DriverPairs.recommendedKey(gpu, emptyList()) == null) {
autoStatus = activity.getString(R.string.drivers_none_to_set, gpu.supportText(activity).lowercase()) autoStatus = activity.getString(R.string.drivers_none_to_set, gpu.supportText(activity, com.droiddeck.launcher.core.DeviceSupport.vkBridge(activity)).lowercase())
return return
} }
releaseChecking = true releaseChecking = true
@@ -209,7 +209,7 @@ object DeviceReport {
h("Drivers") h("Drivers")
val gpu = com.droiddeck.launcher.gpu.GpuInfo.detect() val gpu = com.droiddeck.launcher.gpu.GpuInfo.detect()
k("GPU", "${gpu.name} · ${gpu.family.label} · ${gpu.supportText}" + if (gpu.oneUi8Gen2) " · One UI 8 Gen 2" else "") k("GPU", "${gpu.name} · ${gpu.family.label} · ${gpu.supportText(SessionPrefs.vkBridge(context))}" + if (gpu.oneUi8Gen2) " · One UI 8 Gen 2" else "")
if (gpu.model == 740) { if (gpu.model == 740) {
val hint = com.droiddeck.launcher.gpu.TpUbwcHint val hint = com.droiddeck.launcher.gpu.TpUbwcHint
k("TP UBWC hint", hint.forThisDevice(context) ?: "driver default") k("TP UBWC hint", hint.forThisDevice(context) ?: "driver default")
@@ -16,7 +16,7 @@ object DeviceInfo {
val vk = VulkanInfo.query() val vk = VulkanInfo.query()
val gpu = GpuInfo.detect() val gpu = GpuInfo.detect()
return listOf( return listOf(
chip(), gpu(gpu), cpu(), memory(context), system(), vulkan(context, vk), drivers(context, gpu), chip(), gpu(context, gpu), cpu(), memory(context), system(), vulkan(context, vk), drivers(context, gpu),
).filter { it.rows.isNotEmpty() } ).filter { it.rows.isNotEmpty() }
} }
@@ -51,7 +51,7 @@ object DeviceInfo {
add("Family", read("/sys/devices/soc0/family")) add("Family", read("/sys/devices/soc0/family"))
}) })
private fun gpu(gpu: GpuInfo) = Section("GPU", buildList<Pair<String, String>> { private fun gpu(context: Context, gpu: GpuInfo) = Section("GPU", buildList<Pair<String, String>> {
add("GPU", gpu.name) add("GPU", gpu.name)
add("Family", gpu.family.label) add("Family", gpu.family.label)
add("Model found from", when (gpu.modelSource) { add("Model found from", when (gpu.modelSource) {
@@ -66,7 +66,7 @@ object DeviceInfo {
add("Clock range", gpuRange()) add("Clock range", gpuRange())
add("Load", read("$kgsl/gpu_busy_percentage")?.let { if (it.endsWith("%")) it else "$it %" }) add("Load", read("$kgsl/gpu_busy_percentage")?.let { if (it.endsWith("%")) it else "$it %" })
add("Temperature", read("$kgsl/temp")?.toLongOrNull()?.let { if (it > 1000) "${it / 1000} °C" else "$it °C" }) add("Temperature", read("$kgsl/temp")?.toLongOrNull()?.let { if (it > 1000) "${it / 1000} °C" else "$it °C" })
add("Support", gpu.supportText) add("Support", gpu.supportText(SessionPrefs.vkBridge(context)))
}) })
private fun cpu() = Section("CPU", buildList<Pair<String, String>> { private fun cpu() = Section("CPU", buildList<Pair<String, String>> {
@@ -53,6 +53,18 @@ data class GpuInfo(
Support.UNSUPPORTED -> "Not supported: DroidDeck needs an Adreno (Snapdragon) GPU" Support.UNSUPPORTED -> "Not supported: DroidDeck needs an Adreno (Snapdragon) GPU"
} }
fun supportText(bridge: Boolean): String = when {
support != Support.UNSUPPORTED -> supportText
bridge -> "Runs through the Mali bridge, on the phone's own Vulkan driver"
else -> "Not supported with the Mali bridge off: Turnip needs an Adreno (Snapdragon) GPU"
}
fun supportText(context: Context, bridge: Boolean): String = when {
support != Support.UNSUPPORTED -> supportText(context)
bridge -> context.getString(R.string.gpuinfo_bridge)
else -> context.getString(R.string.gpuinfo_bridge_off)
}
fun supportText(context: Context): String = context.getString(when (support) { fun supportText(context: Context): String = context.getString(when (support) {
Support.TESTED -> R.string.gpuinfo_supported Support.TESTED -> R.string.gpuinfo_supported
Support.UNTESTED -> when { Support.UNTESTED -> when {
@@ -251,12 +251,36 @@ class LinuxNetworkLinkComponent(
} }
} }
private fun localDnsServers(): List<String> {
val servers = LinkedHashSet<String>()
for (network in connectivity.allNetworks) {
val capabilities = connectivity.getNetworkCapabilities(network) ?: continue
if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) continue
if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) continue
val link = connectivity.getLinkProperties(network) ?: continue
for (address in link.dnsServers) {
if (address.isLinkLocalAddress || address.isLoopbackAddress || address.isAnyLocalAddress) continue
address.hostAddress?.substringBefore('%')?.let { servers.add(it) }
}
link.routes.firstOrNull { it.isDefaultRoute }?.gateway?.let { gateway ->
if (gateway is Inet4Address && !gateway.isLoopbackAddress && !gateway.isAnyLocalAddress) {
gateway.hostAddress?.substringBefore('%')?.let { servers.add(it) }
}
}
}
return servers.sortedWith(compareBy<String>(
{ if (it.startsWith("192.168.") || it.startsWith("10.")) 0 else 1 },
{ if (it.contains(':')) 1 else 0 },
)).take(6)
}
private fun writeResolver(properties: LinkProperties?) { private fun writeResolver(properties: LinkProperties?) {
val own = properties?.dnsServers.orEmpty() val own = properties?.dnsServers.orEmpty()
.filter { !it.isLinkLocalAddress && !it.isLoopbackAddress && !it.isAnyLocalAddress } .filter { !it.isLinkLocalAddress && !it.isLoopbackAddress && !it.isAnyLocalAddress }
.sortedBy { if (it is Inet4Address) 0 else 1 } .sortedBy { if (it is Inet4Address) 0 else 1 }
.mapNotNull { it.hostAddress?.substringBefore('%') } .mapNotNull { it.hostAddress?.substringBefore('%') }
val servers = (own + listOf("8.8.8.8", "1.1.1.1", "2001:4860:4860::8888")).distinct().take(6) val servers = (localDnsServers() + own + listOf("8.8.8.8", "1.1.1.1", "2001:4860:4860::8888")).distinct().take(6)
val text = buildString { val text = buildString {
append("# Written by the app from the device's active network; edits are overwritten.\n") append("# Written by the app from the device's active network; edits are overwritten.\n")
for (server in servers) append("nameserver $server\n") for (server in servers) append("nameserver $server\n")
@@ -65,6 +65,7 @@ public final class LinuxRuntime {
private static final String HOST_DIR = "opt/android-host"; private static final String HOST_DIR = "opt/android-host";
/** The downloaded add-on pack. The APK does not ship this binary. */ /** The downloaded add-on pack. The APK does not ship this binary. */
public static final String PROOT_PACK = "add-on pack"; public static final String PROOT_PACK = "add-on pack";
public static final String PROOT_PACKAGED = "packaged";
/** Termux's unpatched proot inside the rootfs. Developer switch only. */ /** Termux's unpatched proot inside the rootfs. Developer switch only. */
public static final String PROOT_ROOTFS = "rootfs fallback"; public static final String PROOT_ROOTFS = "rootfs fallback";
public static final String PROOT_MISSING = "missing"; public static final String PROOT_MISSING = "missing";
@@ -81,18 +82,32 @@ public final class LinuxRuntime {
return rootfsProot(context).isFile() && rootfsLoader(context).isFile(); return rootfsProot(context).isFile() && rootfsLoader(context).isFile();
} }
private static File packagedProot(Context context) {
return new File(context.getApplicationInfo().nativeLibraryDir, "libproot.so");
}
private static File packagedLoader(Context context) {
return new File(context.getApplicationInfo().nativeLibraryDir, "libproot-loader.so");
}
private static boolean packagedProotReady(Context context) {
return packagedProot(context).isFile() && packagedLoader(context).isFile();
}
/** Patched proot is the add-on pack. The rootfs copy is not that binary. */ /** Patched proot is the add-on pack. The rootfs copy is not that binary. */
public static boolean patchedProot(Context context) { public static boolean patchedProot(Context context) {
return AddonPack.PROOT.installed(context); return AddonPack.PROOT.installed(context);
} }
public static File prootBinary(Context context) { public static File prootBinary(Context context) {
if (packagedProotReady(context)) return packagedProot(context);
if (AddonPack.PROOT.installed(context)) return AddonPack.PROOT.file(context, "libproot.so"); if (AddonPack.PROOT.installed(context)) return AddonPack.PROOT.file(context, "libproot.so");
if (SessionPrefs.allowStockProot(context)) return rootfsProot(context); if (SessionPrefs.allowStockProot(context)) return rootfsProot(context);
return AddonPack.PROOT.file(context, "libproot.so"); return AddonPack.PROOT.file(context, "libproot.so");
} }
public static File prootLoader(Context context) { public static File prootLoader(Context context) {
if (packagedProotReady(context)) return packagedLoader(context);
if (AddonPack.PROOT.installed(context)) return AddonPack.PROOT.file(context, "libproot-loader.so"); if (AddonPack.PROOT.installed(context)) return AddonPack.PROOT.file(context, "libproot-loader.so");
if (SessionPrefs.allowStockProot(context)) return rootfsLoader(context); if (SessionPrefs.allowStockProot(context)) return rootfsLoader(context);
return AddonPack.PROOT.file(context, "libproot-loader.so"); return AddonPack.PROOT.file(context, "libproot-loader.so");
@@ -102,6 +117,8 @@ public final class LinuxRuntime {
public static String prootSource(Context context) { public static String prootSource(Context context) {
File bin = prootBinary(context); File bin = prootBinary(context);
if (!bin.isFile()) return PROOT_MISSING; if (!bin.isFile()) return PROOT_MISSING;
if (packagedProotReady(context)
&& bin.getPath().equals(packagedProot(context).getPath())) return PROOT_PACKAGED;
if (AddonPack.PROOT.installed(context) if (AddonPack.PROOT.installed(context)
&& bin.getPath().equals(AddonPack.PROOT.file(context, "libproot.so").getPath())) return PROOT_PACK; && bin.getPath().equals(AddonPack.PROOT.file(context, "libproot.so").getPath())) return PROOT_PACK;
return PROOT_ROOTFS; return PROOT_ROOTFS;
@@ -113,7 +130,7 @@ public final class LinuxRuntime {
} }
public static boolean isInstalled(Context context) { public static boolean isInstalled(Context context) {
return hasRootfs(context) && (patchedProot(context) || rootfsProotReady(context)); return hasRootfs(context) && (packagedProotReady(context) || patchedProot(context) || rootfsProotReady(context));
} }
/** The rootfs alone: enough to read the game library, while the proot pack can still be downloading. */ /** The rootfs alone: enough to read the game library, while the proot pack can still be downloading. */
@@ -284,11 +301,21 @@ public final class LinuxRuntime {
return false; return false;
} }
private static final String MALI_DEVICE = "/dev/mali0"; static final String MALI_DEVICE = "/dev/mali0";
static final String[] DRM_RENDER_NODES = {
"/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/renderD130", "/dev/dri/renderD131",
};
public static String gpuStandIn(Context context) { public static String gpuStandIn(Context context) {
if (new File(KGSL_DEVICE).exists()) return KGSL_DEVICE; if (new File(KGSL_DEVICE).exists()) return KGSL_DEVICE;
if (com.droiddeck.launcher.core.DeviceSupport.INSTANCE.vkBridge(context) && new File(MALI_DEVICE).exists()) return MALI_DEVICE; if (!com.droiddeck.launcher.core.DeviceSupport.INSTANCE.vkBridge(context)) return null;
return bridgeStandIn(path -> new File(path).exists());
}
static String bridgeStandIn(java.util.function.Predicate<String> exists) {
if (exists.test(MALI_DEVICE)) return MALI_DEVICE;
for (String node : DRM_RENDER_NODES) if (exists.test(node)) return node;
return null; return null;
} }
@@ -2032,6 +2032,8 @@
<string name="gpuinfo_a850_experimental">Adreno 850 实验支持:需要配套的实验版显示驱动和游戏驱动;尚未在此设备上验证</string> <string name="gpuinfo_a850_experimental">Adreno 850 实验支持:需要配套的实验版显示驱动和游戏驱动;尚未在此设备上验证</string>
<string name="gpuinfo_below_tested">低于已测试的硬件(Adreno 725 及更新):可能无法运行</string> <string name="gpuinfo_below_tested">低于已测试的硬件(Adreno 725 及更新):可能无法运行</string>
<string name="gpuinfo_unsupported">不受支持:DroidDeck 需要 Adreno(骁龙)GPU</string> <string name="gpuinfo_unsupported">不受支持:DroidDeck 需要 Adreno(骁龙)GPU</string>
<string name="gpuinfo_bridge">通过 Mali 桥接运行,使用手机自带的 Vulkan 驱动</string>
<string name="gpuinfo_bridge_off">关闭 Mali 桥接时不受支持:Turnip 需要 Adreno(骁龙)GPU</string>
<string name="turnip_rate_limited">已达到 GitHub 的请求频率限制 — 请稍后重试</string> <string name="turnip_rate_limited">已达到 GitHub 的请求频率限制 — 请稍后重试</string>
<string name="turnip_github_http">GitHub 返回 HTTP %1$d</string> <string name="turnip_github_http">GitHub 返回 HTTP %1$d</string>
+2
View File
@@ -2111,6 +2111,8 @@
<string name="gpuinfo_a850_experimental">Experimental Adreno 850 support: requires compatible experimental display and game drivers; not verified on this device</string> <string name="gpuinfo_a850_experimental">Experimental Adreno 850 support: requires compatible experimental display and game drivers; not verified on this device</string>
<string name="gpuinfo_below_tested">Below tested hardware (Adreno 725 and newer): it may not run</string> <string name="gpuinfo_below_tested">Below tested hardware (Adreno 725 and newer): it may not run</string>
<string name="gpuinfo_unsupported">Not supported: DroidDeck needs an Adreno (Snapdragon) GPU</string> <string name="gpuinfo_unsupported">Not supported: DroidDeck needs an Adreno (Snapdragon) GPU</string>
<string name="gpuinfo_bridge">Runs through the Mali bridge, on the phone\'s own Vulkan driver</string>
<string name="gpuinfo_bridge_off">Not supported with the Mali bridge off: Turnip needs an Adreno (Snapdragon) GPU</string>
<string name="turnip_rate_limited">GitHub\'s rate limit was hit - try again later</string> <string name="turnip_rate_limited">GitHub\'s rate limit was hit - try again later</string>
<string name="turnip_github_http">GitHub answered HTTP %1$d</string> <string name="turnip_github_http">GitHub answered HTTP %1$d</string>
+4 -2
View File
@@ -107,8 +107,10 @@ fi
if [ "$(cat /usr/share/droiddeck/kwin-compositor 2>/dev/null)" = opengl ]; then if [ "$(cat /usr/share/droiddeck/kwin-compositor 2>/dev/null)" = opengl ]; then
unset LIBGL_ALWAYS_SOFTWARE QT_QUICK_BACKEND unset LIBGL_ALWAYS_SOFTWARE QT_QUICK_BACKEND
export KWIN_COMPOSE=O2 MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink LIBGL_KOPPER_DRI2=true export KWIN_COMPOSE=O2 MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink LIBGL_KOPPER_DRI2=true
if [ -z "${KWIN_RENDER_NODES:-}" ] && [ -c /dev/dri/renderD0 ]; then if [ -z "${KWIN_RENDER_NODES:-}" ]; then
export KWIN_RENDER_NODES=/dev/dri/renderD0 for node in /dev/dri/renderD0 /dev/dri/renderD307 /dev/dri/renderD*; do
if [ -c "$node" ]; then export KWIN_RENDER_NODES="$node"; break; fi
done
fi fi
mkdir -p /run/droiddeck/kwin mkdir -p /run/droiddeck/kwin
printf '#!/bin/sh\nGALLIUM_THREAD=0 exec /usr/bin/kwin_wayland "$@"\n' > /run/droiddeck/kwin/kwin_wayland printf '#!/bin/sh\nGALLIUM_THREAD=0 exec /usr/bin/kwin_wayland "$@"\n' > /run/droiddeck/kwin/kwin_wayland