Compare commits

...

6 Commits

Author SHA1 Message Date
Greyh4t
94c99d7c08
Merge 2f9ddada9292d46f0f154c7c0d83c856cf6aa075 into 6e3d5569601e89b3edff657a9c9eec016a0e581a 2025-02-19 14:35:08 +08:00
weishu
6e3d556960
manager: refine flashing module confirm dialog
Some checks failed
Build Manager / build-lkm (push) Has been cancelled
Build Manager / build-ksud (macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Build Manager / build-ksud (macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Build Manager / build-ksud (ubuntu-latest, aarch64-linux-android) (push) Has been cancelled
Build Manager / build-ksud (ubuntu-latest, aarch64-unknown-linux-musl) (push) Has been cancelled
Build Manager / build-ksud (ubuntu-latest, x86_64-linux-android) (push) Has been cancelled
Build Manager / build-ksud (ubuntu-latest, x86_64-pc-windows-gnu) (push) Has been cancelled
Build Manager / build-ksud (ubuntu-latest, x86_64-unknown-linux-musl) (push) Has been cancelled
Build Manager / build-manager (push) Has been cancelled
2025-02-19 14:19:42 +08:00
weishu
1186259365
manager: refine flash utilities 2025-02-19 12:55:55 +08:00
ukriu
e681c8fbec
manager: allow multiple modules to be installed sequentially (#2451)
cherry picked from KernelSU-Next by rifsxd at [commit
2f42ebd](2f42ebd620)
  
Looks like this:


https://github.com/user-attachments/assets/2c759305-be2f-4855-976a-a87b9d920177

Co-authored-by: Rifat Azad <rifat.44.azad.rifs@gmail.com>
2025-02-19 12:22:38 +08:00
Greyh4t
2f9ddada92 Update installer.sh
fix bug
2024-01-24 21:39:33 +08:00
Greyh4t
3047a7463e Update installer.sh 2024-01-24 21:39:33 +08:00
5 changed files with 98 additions and 46 deletions

View File

@ -53,6 +53,7 @@ import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.FlashResult
import me.weishu.kernelsu.ui.util.LkmSelection import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.flashModule import me.weishu.kernelsu.ui.util.flashModule
@ -65,23 +66,40 @@ import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
/**
* @author weishu
* @date 2023/1/1.
*/
enum class FlashingStatus { enum class FlashingStatus {
FLASHING, FLASHING,
SUCCESS, SUCCESS,
FAILED FAILED
} }
/** // Lets you flash modules sequentially when mutiple zipUris are selected
* @author weishu fun flashModulesSequentially(
* @date 2023/1/1. uris: List<Uri>,
*/ onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
for (uri in uris) {
flashModule(uri, onStdout, onStderr).apply {
if (code != 0) {
return FlashResult(code, err, showReboot)
}
}
}
return FlashResult(0, "", true)
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Destination<RootGraph> @Destination<RootGraph>
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
var text by rememberSaveable { mutableStateOf("") } var text by rememberSaveable { mutableStateOf("") }
var tempText : String var tempText: String
val logContent = rememberSaveable { StringBuilder() } val logContent = rememberSaveable { StringBuilder() }
var showFloatAction by rememberSaveable { mutableStateOf(false) } var showFloatAction by rememberSaveable { mutableStateOf(false) }
@ -98,16 +116,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
return@LaunchedEffect return@LaunchedEffect
} }
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
flashIt(flashIt, onFinish = { showReboot, code -> flashIt(flashIt, onStdout = {
if (code != 0) {
text += "Error: exit code = $code.\nPlease save and check the log.\n"
}
if (showReboot) {
text += "\n\n\n"
showFloatAction = true
}
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
}, onStdout = {
tempText = "$it\n" tempText = "$it\n"
if (tempText.startsWith("")) { // clear command if (tempText.startsWith("")) { // clear command
text = tempText.substring(6) text = tempText.substring(6)
@ -117,7 +126,16 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
logContent.append(it).append("\n") logContent.append(it).append("\n")
}, onStderr = { }, onStderr = {
logContent.append(it).append("\n") logContent.append(it).append("\n")
}) }).apply {
if (code != 0) {
text += "Error code: $code.\n $err Please save and check the log.\n"
}
if (showReboot) {
text += "\n\n\n"
showFloatAction = true
}
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
}
} }
} }
@ -193,31 +211,36 @@ sealed class FlashIt : Parcelable {
data class FlashModule(val uri: Uri) : FlashIt() data class FlashModule(val uri: Uri) : FlashIt()
data class FlashModules(val uris: List<Uri>) : FlashIt()
data object FlashRestore : FlashIt() data object FlashRestore : FlashIt()
data object FlashUninstall : FlashIt() data object FlashUninstall : FlashIt()
} }
fun flashIt( fun flashIt(
flashIt: FlashIt, onFinish: (Boolean, Int) -> Unit, flashIt: FlashIt,
onStdout: (String) -> Unit, onStdout: (String) -> Unit,
onStderr: (String) -> Unit onStderr: (String) -> Unit
) { ): FlashResult {
when (flashIt) { return when (flashIt) {
is FlashIt.FlashBoot -> installBoot( is FlashIt.FlashBoot -> installBoot(
flashIt.boot, flashIt.boot,
flashIt.lkm, flashIt.lkm,
flashIt.ota, flashIt.ota,
onFinish,
onStdout, onStdout,
onStderr onStderr
) )
is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr) is FlashIt.FlashModule -> flashModule(flashIt.uri, onStdout, onStderr)
FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr) is FlashIt.FlashModules -> {
flashModulesSequentially(flashIt.uris, onStdout, onStderr)
}
FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr) FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr)
FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr)
} }
} }

View File

@ -36,6 +36,7 @@ import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.PlayArrow import androidx.compose.material.icons.outlined.PlayArrow
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
@ -56,6 +57,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
@ -103,6 +105,7 @@ import me.weishu.kernelsu.ui.util.hasMagisk
import me.weishu.kernelsu.ui.util.reboot import me.weishu.kernelsu.ui.util.reboot
import me.weishu.kernelsu.ui.util.toggleModule import me.weishu.kernelsu.ui.util.toggleModule
import me.weishu.kernelsu.ui.util.uninstallModule import me.weishu.kernelsu.ui.util.uninstallModule
import me.weishu.kernelsu.ui.util.getFileName
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
import me.weishu.kernelsu.ui.webui.WebUIActivity import me.weishu.kernelsu.ui.webui.WebUIActivity
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -200,6 +203,12 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
floatingActionButton = { floatingActionButton = {
if (!hideInstallButton) { if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install) val moduleInstall = stringResource(id = R.string.module_install)
val confirmTitle = stringResource(R.string.module)
var zipUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
val confirmDialog = rememberConfirmDialog(onConfirm = {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(zipUris)))
viewModel.markNeedRefresh()
})
val selectZipLauncher = rememberLauncherForActivityResult( val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { ) {
@ -207,20 +216,38 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
return@rememberLauncherForActivityResult return@rememberLauncherForActivityResult
} }
val data = it.data ?: return@rememberLauncherForActivityResult val data = it.data ?: return@rememberLauncherForActivityResult
val uri = data.data ?: return@rememberLauncherForActivityResult val clipData = data.clipData
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) val uris = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
clipData.getItemAt(i)?.uri?.let { uris.add(it) }
}
} else {
data.data?.let { uris.add(it) }
}
viewModel.markNeedRefresh() if (uris.size == 1) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uris.first())))
Log.i("ModuleScreen", "select zip result: ${it.data}") } else if (uris.size > 1) {
// multiple files selected
val moduleNames = uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }.joinToString("")
val confirmContent = context.getString(R.string.module_install_prompt_with_name, moduleNames)
zipUris = uris
confirmDialog.showConfirm(
title = confirmTitle,
content = confirmContent,
markdown = true
)
}
} }
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
onClick = { onClick = {
// select the zip file to install // Select the zip files to install
val intent = Intent(Intent.ACTION_GET_CONTENT).apply { val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip" type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
} }
selectZipLauncher.launch(intent) selectZipLauncher.launch(intent)
}, },
@ -232,6 +259,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
snackbarHost = { SnackbarHost(hostState = snackBarHost) } snackbarHost = { SnackbarHost(hostState = snackBarHost) }
) { innerPadding -> ) { innerPadding ->
when { when {
hasMagisk -> { hasMagisk -> {
Box( Box(

View File

@ -33,6 +33,11 @@ private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
} }
data class FlashResult(val code: Int, val err: String, val showReboot: Boolean) {
constructor(result: Shell.Result, showReboot: Boolean) : this(result.code, result.err.joinToString("\n"), showReboot)
constructor(result: Shell.Result) : this(result, result.isSuccess)
}
object KsuCli { object KsuCli {
val SHELL: Shell = createRootShell() val SHELL: Shell = createRootShell()
val GLOBAL_MNT_SHELL: Shell = createRootShell(true) val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
@ -167,10 +172,9 @@ private fun flashWithIO(
fun flashModule( fun flashModule(
uri: Uri, uri: Uri,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit, onStdout: (String) -> Unit,
onStderr: (String) -> Unit onStderr: (String) -> Unit
): Boolean { ): FlashResult {
val resolver = ksuApp.contentResolver val resolver = ksuApp.contentResolver
with(resolver.openInputStream(uri)) { with(resolver.openInputStream(uri)) {
val file = File(ksuApp.cacheDir, "module.zip") val file = File(ksuApp.cacheDir, "module.zip")
@ -183,8 +187,7 @@ fun flashModule(
file.delete() file.delete()
onFinish(result.isSuccess, result.code) return FlashResult(result)
return result.isSuccess
} }
} }
@ -213,21 +216,19 @@ fun runModuleAction(
} }
fun restoreBoot( fun restoreBoot(
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean { ): FlashResult {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr) val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
onFinish(result.isSuccess, result.code) return FlashResult(result)
return result.isSuccess
} }
fun uninstallPermanently( fun uninstallPermanently(
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean { ): FlashResult {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr) val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
onFinish(result.isSuccess, result.code) return FlashResult(result)
return result.isSuccess
} }
suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) { suspend fun shrinkModules(): Boolean = withContext(Dispatchers.IO) {
@ -245,10 +246,9 @@ fun installBoot(
bootUri: Uri?, bootUri: Uri?,
lkm: LkmSelection, lkm: LkmSelection,
ota: Boolean, ota: Boolean,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit, onStdout: (String) -> Unit,
onStderr: (String) -> Unit, onStderr: (String) -> Unit,
): Boolean { ): FlashResult {
val resolver = ksuApp.contentResolver val resolver = ksuApp.contentResolver
val bootFile = bootUri?.let { uri -> val bootFile = bootUri?.let { uri ->
@ -311,8 +311,7 @@ fun installBoot(
lkmFile?.delete() lkmFile?.delete()
// if boot uri is empty, it is direct install, when success, we should show reboot button // if boot uri is empty, it is direct install, when success, we should show reboot button
onFinish(bootUri == null && result.isSuccess, result.code) return FlashResult(result, bootUri == null && result.isSuccess)
return result.isSuccess
} }
fun reboot(reason: String = "") { fun reboot(reason: String = "") {

View File

@ -23,8 +23,10 @@
<string name="module_failed_to_disable">Failed to disable module: %s</string> <string name="module_failed_to_disable">Failed to disable module: %s</string>
<string name="module_empty">No module installed</string> <string name="module_empty">No module installed</string>
<string name="module">Module</string> <string name="module">Module</string>
<string name="module_install_prompt_with_name">Do you want to continue installing module %1$s?</string>
<string name="module_sort_action_first">Sort (Action first)</string> <string name="module_sort_action_first">Sort (Action first)</string>
<string name="module_sort_enabled_first">Sort (Enabled first)</string> <string name="module_sort_enabled_first">Sort (Enabled first)</string>
<string name="confirm">Confirm</string>
<string name="uninstall">Uninstall</string> <string name="uninstall">Uninstall</string>
<string name="module_install">Install</string> <string name="module_install">Install</string>
<string name="install">Install</string> <string name="install">Install</string>

View File

@ -313,7 +313,7 @@ handle_partition() {
ui_print "- Handle partition /$1" ui_print "- Handle partition /$1"
# we create a symlink if module want to access $MODPATH/system/$1 # we create a symlink if module want to access $MODPATH/system/$1
# but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly) # but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly)
mv -f $MODPATH/system/$1 $MODPATH/$1 && ln -sf ../$1 $MODPATH/system/$1 cp -rf $MODPATH/system/$1 $MODPATH/ && rm -rf $MODPATH/system/$1 && ln -sf ../$1 $MODPATH/system/$1
fi fi
} }