diff --git a/.signing.env.example b/.signing.env.example new file mode 100644 index 000000000..7b497c6a7 --- /dev/null +++ b/.signing.env.example @@ -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 diff --git a/app/build.gradle b/app/build.gradle index bcfe5ba52..4ca43011d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -219,8 +219,7 @@ android { jniLibs { useLegacyPackaging = true pickFirsts += ['**/libtermux.so'] - // Shipped in the proot and tracking packs (tools/packs) and run or loaded from there. - excludes += ['**/libproot.so', '**/libproot-loader.so', '**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so'] + excludes += ['**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so'] } } diff --git a/app/src/main/java/com/droiddeck/launcher/DriverMenus.kt b/app/src/main/java/com/droiddeck/launcher/DriverMenus.kt index 344aa7452..200deb55a 100644 --- a/app/src/main/java/com/droiddeck/launcher/DriverMenus.kt +++ b/app/src/main/java/com/droiddeck/launcher/DriverMenus.kt @@ -65,7 +65,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl } 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, 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, @@ -264,7 +264,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl return } 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 } releaseChecking = true diff --git a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt index 4163eb4a3..295604411 100644 --- a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt +++ b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt @@ -209,7 +209,7 @@ object DeviceReport { h("Drivers") 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) { val hint = com.droiddeck.launcher.gpu.TpUbwcHint k("TP UBWC hint", hint.forThisDevice(context) ?: "driver default") diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/DeviceInfo.kt b/app/src/main/java/com/droiddeck/launcher/gpu/DeviceInfo.kt index 9a5fb1182..2d9aab902 100644 --- a/app/src/main/java/com/droiddeck/launcher/gpu/DeviceInfo.kt +++ b/app/src/main/java/com/droiddeck/launcher/gpu/DeviceInfo.kt @@ -16,7 +16,7 @@ object DeviceInfo { val vk = VulkanInfo.query() val gpu = GpuInfo.detect() 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() } } @@ -51,7 +51,7 @@ object DeviceInfo { add("Family", read("/sys/devices/soc0/family")) }) - private fun gpu(gpu: GpuInfo) = Section("GPU", buildList> { + private fun gpu(context: Context, gpu: GpuInfo) = Section("GPU", buildList> { add("GPU", gpu.name) add("Family", gpu.family.label) add("Model found from", when (gpu.modelSource) { @@ -66,7 +66,7 @@ object DeviceInfo { add("Clock range", gpuRange()) 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("Support", gpu.supportText) + add("Support", gpu.supportText(SessionPrefs.vkBridge(context))) }) private fun cpu() = Section("CPU", buildList> { diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt b/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt index c137e666f..9b0a1897e 100644 --- a/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt +++ b/app/src/main/java/com/droiddeck/launcher/gpu/GpuInfo.kt @@ -53,6 +53,18 @@ data class GpuInfo( 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) { Support.TESTED -> R.string.gpuinfo_supported Support.UNTESTED -> when { diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxNetworkLinkComponent.kt b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxNetworkLinkComponent.kt index 45f07471b..d16a372ac 100644 --- a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxNetworkLinkComponent.kt +++ b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxNetworkLinkComponent.kt @@ -251,12 +251,36 @@ class LinuxNetworkLinkComponent( } } + private fun localDnsServers(): List { + val servers = LinkedHashSet() + 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( + { if (it.startsWith("192.168.") || it.startsWith("10.")) 0 else 1 }, + { if (it.contains(':')) 1 else 0 }, + )).take(6) + } + private fun writeResolver(properties: LinkProperties?) { val own = properties?.dnsServers.orEmpty() .filter { !it.isLinkLocalAddress && !it.isLoopbackAddress && !it.isAnyLocalAddress } .sortedBy { if (it is Inet4Address) 0 else 1 } .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 { append("# Written by the app from the device's active network; edits are overwritten.\n") for (server in servers) append("nameserver $server\n") diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java index 3c8d90e59..4448c3bc8 100644 --- a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java +++ b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java @@ -65,6 +65,7 @@ public final class LinuxRuntime { private static final String HOST_DIR = "opt/android-host"; /** 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_PACKAGED = "packaged"; /** Termux's unpatched proot inside the rootfs. Developer switch only. */ public static final String PROOT_ROOTFS = "rootfs fallback"; public static final String PROOT_MISSING = "missing"; @@ -81,18 +82,32 @@ public final class LinuxRuntime { 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. */ public static boolean patchedProot(Context context) { return AddonPack.PROOT.installed(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 (SessionPrefs.allowStockProot(context)) return rootfsProot(context); return AddonPack.PROOT.file(context, "libproot.so"); } 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 (SessionPrefs.allowStockProot(context)) return rootfsLoader(context); return AddonPack.PROOT.file(context, "libproot-loader.so"); @@ -102,6 +117,8 @@ public final class LinuxRuntime { public static String prootSource(Context context) { File bin = prootBinary(context); if (!bin.isFile()) return PROOT_MISSING; + if (packagedProotReady(context) + && bin.getPath().equals(packagedProot(context).getPath())) return PROOT_PACKAGED; if (AddonPack.PROOT.installed(context) && bin.getPath().equals(AddonPack.PROOT.file(context, "libproot.so").getPath())) return PROOT_PACK; return PROOT_ROOTFS; @@ -113,7 +130,7 @@ public final class LinuxRuntime { } 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. */ @@ -284,11 +301,21 @@ public final class LinuxRuntime { 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) { 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 exists) { + if (exists.test(MALI_DEVICE)) return MALI_DEVICE; + for (String node : DRM_RENDER_NODES) if (exists.test(node)) return node; return null; } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7255a85bd..306174dae 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2032,6 +2032,8 @@ Adreno 850 实验支持:需要配套的实验版显示驱动和游戏驱动;尚未在此设备上验证 低于已测试的硬件(Adreno 725 及更新):可能无法运行 不受支持:DroidDeck 需要 Adreno(骁龙)GPU + 通过 Mali 桥接运行,使用手机自带的 Vulkan 驱动 + 关闭 Mali 桥接时不受支持:Turnip 需要 Adreno(骁龙)GPU 已达到 GitHub 的请求频率限制 — 请稍后重试 GitHub 返回 HTTP %1$d diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1f12625f2..4356f7f0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2111,6 +2111,8 @@ Experimental Adreno 850 support: requires compatible experimental display and game drivers; not verified on this device Below tested hardware (Adreno 725 and newer): it may not run Not supported: DroidDeck needs an Adreno (Snapdragon) GPU + Runs through the Mali bridge, on the phone\'s own Vulkan driver + Not supported with the Mali bridge off: Turnip needs an Adreno (Snapdragon) GPU GitHub\'s rate limit was hit - try again later GitHub answered HTTP %1$d diff --git a/tools/linuxfs/desktop/droiddeck-desktop b/tools/linuxfs/desktop/droiddeck-desktop index 30e95c70e..15c5b3773 100644 --- a/tools/linuxfs/desktop/droiddeck-desktop +++ b/tools/linuxfs/desktop/droiddeck-desktop @@ -107,8 +107,10 @@ fi if [ "$(cat /usr/share/droiddeck/kwin-compositor 2>/dev/null)" = opengl ]; then unset LIBGL_ALWAYS_SOFTWARE QT_QUICK_BACKEND 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 - export KWIN_RENDER_NODES=/dev/dri/renderD0 + if [ -z "${KWIN_RENDER_NODES:-}" ]; then + 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 mkdir -p /run/droiddeck/kwin printf '#!/bin/sh\nGALLIUM_THREAD=0 exec /usr/bin/kwin_wayland "$@"\n' > /run/droiddeck/kwin/kwin_wayland