From 385f4ab2c53a83bf407a8fb47699ce31b955c3b3 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:38:53 +0800 Subject: [PATCH] Step 5-1: Move the KPM interface to the settings - Avoid multiple page re-rendering - Add hook type information - Clean up code --- manager/app/src/main/cpp/jni.c | 5 - manager/app/src/main/cpp/ksu.c | 8 -- manager/app/src/main/cpp/ksu.h | 4 - manager/app/src/main/cpp/legacy.c | 6 - .../src/main/java/com/sukisu/ultra/Natives.kt | 2 - .../java/com/sukisu/ultra/ui/MainActivity.kt | 38 ++---- .../sukisu/ultra/ui/component/BottomBar.kt | 27 +--- .../java/com/sukisu/ultra/ui/screen/Home.kt | 116 ++++++++++++------ .../com/sukisu/ultra/ui/screen/Settings.kt | 32 +++++ .../java/com/sukisu/ultra/ui/util/KsuCli.kt | 16 +-- .../src/main/res/values-zh-rCN/strings.xml | 5 +- manager/app/src/main/res/values/strings.xml | 5 +- 12 files changed, 139 insertions(+), 125 deletions(-) diff --git a/manager/app/src/main/cpp/jni.c b/manager/app/src/main/cpp/jni.c index 2d064558..85f92090 100644 --- a/manager/app/src/main/cpp/jni.c +++ b/manager/app/src/main/cpp/jni.c @@ -335,11 +335,6 @@ NativeBridge(getUserName, jstring, jint uid) { return NULL; } -// Check if KPM is enabled -NativeBridgeNP(isKPMEnabled, jboolean) { - return is_KPM_enable(); -} - // Get HOOK type NativeBridgeNP(getHookType, jstring) { char hook_type[32] = { 0 }; diff --git a/manager/app/src/main/cpp/ksu.c b/manager/app/src/main/cpp/ksu.c index 044c4919..a0f5b871 100644 --- a/manager/app/src/main/cpp/ksu.c +++ b/manager/app/src/main/cpp/ksu.c @@ -259,14 +259,6 @@ void get_full_version(char* buff) { } } -bool is_KPM_enable(void) { - struct ksu_enable_kpm_cmd cmd = {}; - if (ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled) { - return true; - } - return legacy_is_KPM_enable(); -} - void get_hook_type(char *buff) { struct ksu_hook_type_cmd cmd = {0}; if (ksuctl(KSU_IOCTL_HOOK_TYPE, &cmd) == 0) { diff --git a/manager/app/src/main/cpp/ksu.h b/manager/app/src/main/cpp/ksu.h index b5bac7df..536815f7 100644 --- a/manager/app/src/main/cpp/ksu.h +++ b/manager/app/src/main/cpp/ksu.h @@ -229,10 +229,6 @@ struct ksu_hook_type_cmd { char hook_type[32]; // Output: hook type string }; -struct ksu_enable_kpm_cmd { - uint8_t enabled; // Output: true if KPM is enabled -}; - struct ksu_dynamic_manager_cmd { struct dynamic_manager_user_config config; // Input/Output: dynamic manager config }; diff --git a/manager/app/src/main/cpp/legacy.c b/manager/app/src/main/cpp/legacy.c index de72a326..458588c7 100644 --- a/manager/app/src/main/cpp/legacy.c +++ b/manager/app/src/main/cpp/legacy.c @@ -88,12 +88,6 @@ bool legacy_is_su_enabled() { return enabled; } -bool legacy_is_KPM_enable() { - int enabled = false; - ksuctl(CMD_ENABLE_KPM, &enabled, NULL); - return enabled; -} - bool legacy_get_hook_type(char* hook_type, size_t size) { if (hook_type == NULL || size == 0) { return false; diff --git a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt index 867a0331..f2fd72db 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt @@ -121,8 +121,6 @@ object Natives { */ external fun isSuLogEnabled(): Boolean external fun setSuLogEnabled(enabled: Boolean): Boolean - - external fun isKPMEnabled(): Boolean external fun getHookType(): String /** diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt index cd12a940..d4e90c89 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt @@ -1,7 +1,6 @@ package com.sukisu.ultra.ui import android.content.SharedPreferences -import androidx.compose.ui.graphics.Color import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity @@ -31,6 +30,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.navigation.NavBackStackEntry import androidx.navigation.compose.rememberNavController import com.ramcosta.composedestinations.DestinationsNavHost @@ -47,13 +47,11 @@ import kotlinx.coroutines.launch import com.sukisu.ultra.Natives import com.sukisu.ultra.ui.component.BottomBar import com.sukisu.ultra.ui.screen.HomePager -import com.sukisu.ultra.ui.screen.KpmScreen import com.sukisu.ultra.ui.screen.ModulePager import com.sukisu.ultra.ui.screen.SettingPager import com.sukisu.ultra.ui.screen.SuperUserPager import com.sukisu.ultra.ui.theme.KernelSUTheme import com.sukisu.ultra.ui.util.install -import com.sukisu.ultra.ui.util.rememberKpmAvailable import top.yukonga.miuix.kmp.basic.Scaffold import top.yukonga.miuix.kmp.theme.MiuixTheme @@ -94,6 +92,7 @@ class MainActivity : ComponentActivity() { android.graphics.Color.TRANSPARENT ) { darkMode }, ) + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> when (key) { "color_mode" -> colorMode = prefs.getInt("color_mode", 0) @@ -161,11 +160,7 @@ val LocalHandlePageChange = compositionLocalOf<(Int) -> Unit> { error("No handle fun MainScreen(navController: DestinationsNavigator) { val activity = LocalActivity.current val coroutineScope = rememberCoroutineScope() - - val isKpmAvailable = rememberKpmAvailable() - val pageCount = if (isKpmAvailable) 5 else 4 - - val pagerState = rememberPagerState(initialPage = 0, pageCount = { pageCount }) + val pagerState = rememberPagerState(initialPage = 0, pageCount = { 4 }) val hazeState = remember { HazeState() } val hazeStyle = HazeStyle( backgroundColor = MiuixTheme.colorScheme.background, @@ -193,7 +188,7 @@ fun MainScreen(navController: DestinationsNavigator) { ) { Scaffold( bottomBar = { - BottomBar(hazeState, hazeStyle, isKpmAvailable) + BottomBar(hazeState, hazeStyle) }, ) { innerPadding -> HorizontalPager( @@ -202,26 +197,13 @@ fun MainScreen(navController: DestinationsNavigator) { beyondViewportPageCount = 2, userScrollEnabled = false ) { - when { - isKpmAvailable -> { - when (it) { - 0 -> HomePager(pagerState, navController, innerPadding.calculateBottomPadding()) - 1 -> KpmScreen(bottomInnerPadding = innerPadding.calculateBottomPadding()) - 2 -> SuperUserPager(navController, innerPadding.calculateBottomPadding()) - 3 -> ModulePager(navController, innerPadding.calculateBottomPadding()) - 4 -> SettingPager(navController, innerPadding.calculateBottomPadding()) - } - } - else -> { - when (it) { - 0 -> HomePager(pagerState, navController, innerPadding.calculateBottomPadding()) - 1 -> SuperUserPager(navController, innerPadding.calculateBottomPadding()) - 2 -> ModulePager(navController, innerPadding.calculateBottomPadding()) - 3 -> SettingPager(navController, innerPadding.calculateBottomPadding()) - } - } + when (it) { + 0 -> HomePager(pagerState, navController, innerPadding.calculateBottomPadding()) + 1 -> SuperUserPager(navController, innerPadding.calculateBottomPadding()) + 2 -> ModulePager(navController, innerPadding.calculateBottomPadding()) + 3 -> SettingPager(navController, innerPadding.calculateBottomPadding()) } } } } -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/BottomBar.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/BottomBar.kt index ba4c2d5c..ff8dec08 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/BottomBar.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/BottomBar.kt @@ -2,7 +2,6 @@ package com.sukisu.ultra.ui.component import androidx.annotation.StringRes import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Code import androidx.compose.material.icons.rounded.Cottage import androidx.compose.material.icons.rounded.Extension import androidx.compose.material.icons.rounded.Security @@ -28,8 +27,7 @@ import top.yukonga.miuix.kmp.basic.NavigationItem @Composable fun BottomBar( hazeState: HazeState, - hazeStyle: HazeStyle, - isKpmAvailable: Boolean = false + hazeStyle: HazeStyle ) { val isManager = Natives.isManager val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() @@ -39,24 +37,12 @@ fun BottomBar( if (!fullFeatured) return - val destinations = if (isKpmAvailable) { - BottomBarDestination.entries - } else { - BottomBarDestination.entries.filter { it != BottomBarDestination.KPM } - } - - val item = destinations.mapIndexed { index, destination -> + val item = BottomBarDestination.entries.mapIndexed { index, destination -> NavigationItem( label = stringResource(destination.label), icon = destination.icon, ) } - - val bottomBarIndex = if (!isKpmAvailable) { - page.coerceIn(0, item.size - 1) - } else { - page.coerceIn(0, item.size - 1) - } NavigationBar( modifier = Modifier @@ -67,10 +53,8 @@ fun BottomBar( }, color = Color.Transparent, items = item, - selected = bottomBarIndex, - onClick = { index -> - handlePageChange(index) - } + selected = page, + onClick = handlePageChange ) } @@ -79,8 +63,7 @@ enum class BottomBarDestination( val icon: ImageVector, ) { Home(R.string.home, Icons.Rounded.Cottage), - KPM(R.string.kpm_title, Icons.Rounded.Code), SuperUser(R.string.superuser, Icons.Rounded.Security), Module(R.string.module, Icons.Rounded.Extension), Setting(R.string.settings, Icons.Rounded.Settings) -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt index 5f7279fb..4aa683f7 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt @@ -37,11 +37,14 @@ import androidx.compose.material.icons.rounded.CheckCircleOutline import androidx.compose.material.icons.rounded.ErrorOutline import androidx.compose.material.icons.rounded.Link import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -149,8 +152,6 @@ fun HomePager( val lkmMode = ksuVersion?.let { if (kernelVersion.isGKI()) Natives.isLkmMode else null } - - val isKpmAvailable = rememberKpmAvailable() Column( modifier = Modifier.padding(vertical = 12.dp), @@ -179,12 +180,12 @@ fun HomePager( }, onClickSuperuser = { coroutineScope.launch { - pagerState.animateScrollToPage(getSuperuserPageIndex(isKpmAvailable)) + pagerState.animateScrollToPage(1) } }, onclickModule = { coroutineScope.launch { - pagerState.animateScrollToPage(getModulePageIndex(isKpmAvailable)) + pagerState.animateScrollToPage(2) } }, themeMode = themeMode @@ -609,6 +610,19 @@ fun DonateCard() { private fun InfoCard() { val manualHookText = stringResource(R.string.manual_hook) val inlineHookText = stringResource(R.string.inline_hook) + val TracepointHookText = stringResource(R.string.tracepoint_hook) + val unknownHookText = stringResource(R.string.selinux_status_unknown) + val susfsInfo = rememberSusfsInfo(manualHookText, inlineHookText) + val isSusfsSupported = susfsInfo.status == SusfsStatus.Supported + val hookTypeLabel = remember(manualHookText, inlineHookText, TracepointHookText) { + val localized = when (val rawType = Natives.getHookType()) { + "Manual" -> manualHookText + "Tracepoint" -> TracepointHookText + else -> rawType + } + localized.ifBlank { unknownHookText } + } + @Composable fun InfoText( title: String, @@ -632,23 +646,6 @@ private fun InfoCard() { val context = LocalContext.current val uname = Os.uname() val managerVersion = getManagerVersion(context) - val susfsPair by produceState(initialValue = "" to "") { - value = withContext(Dispatchers.IO) { - val rawFeature = getSuSFSFeatures() - val status = if (rawFeature.isNotEmpty() && !rawFeature.startsWith("[-]")) "Supported" else rawFeature - if (status == "Supported") { - val version = getSuSFSVersion() - val hook = when (Natives.getHookType()) { - "Manual" -> "($manualHookText)" - "Inline" -> "($inlineHookText)" - else -> "(${Natives.getHookType()})" - } - status to "$version $hook".trim() - } else { - "" to "" - } - } - } Card { Column( @@ -689,27 +686,26 @@ private fun InfoCard() { ) } } - InfoText( - title = stringResource(R.string.home_fingerprint), - content = Build.FINGERPRINT - ) - if (susfsPair.first == "Supported" && susfsPair.second.isNotEmpty()) { - InfoText( - title = stringResource(R.string.home_selinux_status), - content = getSELinuxStatus(), - ) + if (isSusfsSupported) { InfoText( title = stringResource(R.string.home_susfs_version), - content = susfsPair.second, - bottomPadding = 0.dp + content = susfsInfo.detail ) } else { InfoText( - title = stringResource(R.string.home_selinux_status), - content = getSELinuxStatus(), - bottomPadding = 0.dp + title = stringResource(R.string.hook_type), + content = hookTypeLabel ) } + InfoText( + title = stringResource(R.string.home_selinux_status), + content = getSELinuxStatus(), + ) + InfoText( + title = stringResource(R.string.home_fingerprint), + content = Build.FINGERPRINT, + bottomPadding = 0.dp + ) } } } @@ -720,10 +716,52 @@ fun getManagerVersion(context: Context): Pair { return Pair(packageInfo.versionName!!, versionCode) } -fun getSuperuserPageIndex(isKpmAvailable: Boolean): Int { - return if (isKpmAvailable) 2 else 1 +private enum class SusfsStatus { + Idle, Loading, Supported, Unsupported, Error } -fun getModulePageIndex(isKpmAvailable: Boolean): Int { - return if (isKpmAvailable) 3 else 2 +private data class SusfsInfoState( + val status: SusfsStatus = SusfsStatus.Idle, + val detail: String = "", +) + +@Composable +private fun rememberSusfsInfo( + manualHookLabel: String, + inlineHookLabel: String, +): SusfsInfoState { + var susfsInfo by remember { mutableStateOf(SusfsInfoState(status = SusfsStatus.Loading)) } + + LaunchedEffect(manualHookLabel, inlineHookLabel) { + val info = withContext(Dispatchers.IO) { + runCatching { + val rawFeature = getSuSFSFeatures() + val supported = rawFeature.isNotEmpty() && !rawFeature.startsWith("[-]") + if (supported) { + val version = getSuSFSVersion().trim() + val hookLabel = when (val type = Natives.getHookType()) { + "Manual" -> manualHookLabel + "Inline" -> inlineHookLabel + else -> type + }.takeIf { it.isNotBlank() }?.let { "($it)" }.orEmpty() + SusfsInfoState( + status = SusfsStatus.Supported, + detail = listOf(version, hookLabel).filter { it.isNotBlank() }.joinToString(" ") + ) + } else { + SusfsInfoState( + status = SusfsStatus.Unsupported, + detail = rawFeature + ) + } + }.getOrElse { + SusfsInfoState(status = SusfsStatus.Error) + } + } + if (susfsInfo != info) { + susfsInfo = info + } + } + + return susfsInfo } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 36b8d0a0..9ce8822d 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -16,6 +16,7 @@ import androidx.compose.material.icons.rounded.Palette import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Adb import androidx.compose.material.icons.rounded.BugReport +import androidx.compose.material.icons.rounded.Code import androidx.compose.material.icons.rounded.ContactPage import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.DeleteForever @@ -49,6 +50,7 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AboutScreenDestination import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination import com.ramcosta.composedestinations.generated.destinations.LogViewerDestination +import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination import com.ramcosta.composedestinations.generated.destinations.PersonalizationDestination import com.ramcosta.composedestinations.generated.destinations.ToolsDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator @@ -64,6 +66,7 @@ import com.sukisu.ultra.ui.component.SendLogDialog import com.sukisu.ultra.ui.component.UninstallDialog import com.sukisu.ultra.ui.component.rememberLoadingDialog import com.sukisu.ultra.ui.util.execKsud +import com.sukisu.ultra.ui.util.rememberKpmAvailable import top.yukonga.miuix.kmp.basic.Card import top.yukonga.miuix.kmp.basic.Icon import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior @@ -94,6 +97,8 @@ fun SettingPager( tint = HazeTint(colorScheme.surface.copy(0.8f)) ) + val isKpmAvailable = rememberKpmAvailable() + Scaffold( topBar = { TopAppBar( @@ -265,6 +270,33 @@ fun SettingPager( } } + if (isKpmAvailable) { + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + val kpmTitle = stringResource(id = R.string.kpm_title) + SuperArrow( + title = kpmTitle, + summary = stringResource(id = R.string.settings_kpm_summary), + leftAction = { + Icon( + Icons.Rounded.Code, + modifier = Modifier.padding(end = 16.dp), + contentDescription = kpmTitle, + tint = colorScheme.onBackground + ) + }, + onClick = { + navigator.navigate(KpmScreenDestination) { + launchSingleTop = true + } + } + ) + } + } + KsuIsValid { Card( modifier = Modifier diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt index b20ea8c7..c8c97b5c 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt @@ -12,7 +12,10 @@ import android.system.Os import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ShellUtils @@ -727,14 +730,13 @@ fun applyUmountConfigToKernel(): Boolean { // 检查 KPM 版本是否可用 @Composable fun rememberKpmAvailable(): Boolean { - val kpmVersion by produceState(initialValue = "") { - value = withContext(Dispatchers.IO) { - try { - getKpmVersion() - } catch (_: Exception) { - "" - } + var cachedVersion by rememberSaveable { mutableStateOf("") } + val kpmVersion by produceState(initialValue = cachedVersion) { + val result = withContext(Dispatchers.IO) { + runCatching { getKpmVersion() }.getOrElse { "" } } + cachedVersion = result + value = result } return kpmVersion.isNotEmpty() && !kpmVersion.contains("Error", ignoreCase = true) } diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index 6b79ad52..38ec7231 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -178,9 +178,8 @@ 粉色 棕色 + 钩子类型 SuSFS 版本 - Manual Hook - Inline Hook 活跃管理器 SukiSU Dynamic @@ -320,7 +319,7 @@ AnyKernel3 内核 刷入AnyKernel3格式的内核zip包 - KPM + 使用 KPM 管理内核模块 当前没有安装内核模块 版本 作者 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 02519e0c..2831f5a3 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -180,9 +180,11 @@ Pink Brown - SuSFS Version + Hook Type Manual Hook Inline Hook + Tracepoint Hook + SuSFS Version Active Manager SukiSU Dynamic @@ -325,6 +327,7 @@ Flash AnyKernel3 format kernel zip KPM + Manage kernel modules with KPM No installed kernel modules at this time Version Author