From 8c4f7c0e5cc99dab9ea69c6c1796ee9714ad58e2 Mon Sep 17 00:00:00 2001 From: KOWX712 Date: Mon, 5 May 2025 18:57:24 +0800 Subject: [PATCH] opt: use ksu.spawn for long script --- module/common/get_extra.sh | 28 +++- module/webui/scripts/applist.js | 187 +++++++++++++----------- module/webui/scripts/assets/kernelsu.js | 35 +++-- module/webui/scripts/boot_hash.js | 8 +- module/webui/scripts/main.js | 20 +-- module/webui/scripts/security_patch.js | 68 ++++----- module/webui/scripts/update.js | 102 ++++++------- module/webui/styles/applist.css | 6 +- 8 files changed, 248 insertions(+), 206 deletions(-) diff --git a/module/common/get_extra.sh b/module/common/get_extra.sh index 6d628f2..492041f 100644 --- a/module/common/get_extra.sh +++ b/module/common/get_extra.sh @@ -1,4 +1,7 @@ #!/bin/sh + +# This file is the backend of JavaScript + MODPATH=${0%/*} ORG_PATH="$PATH" SKIPLIST="$MODPATH/tmp/skiplist" @@ -17,13 +20,11 @@ aapt() { "$MODPATH/aapt" "$@"; } # wget = low pref, no ssl. # curl, has ssl on android, we use it if found download() { - PATH=/data/adb/ap/bin:/data/adb/ksu/bin:/data/adb/magisk:/data/data/com.termux/files/usr/bin:$PATH if command -v curl >/dev/null 2>&1; then timeout 10 curl -Ls "$1" else timeout 10 busybox wget --no-check-certificate -qO- "$1" fi - PATH="$ORG_PATH" } get_xposed() { @@ -39,6 +40,19 @@ get_xposed() { cat "$XPOSED" } +get_applist() { + pm list packages -3 | awk -F: '{print $2}' + [ -s "/data/adb/tricky_store/system_app" ] && SYSTEM_APP=$(cat "/data/adb/tricky_store/system_app" | tr '\n' '|' | sed 's/|*$//') || SYSTEM_APP="" + [ -z "$SYSTEM_APP" ] || pm list packages -s | awk -F: '{print $2}' | grep -Ex "$SYSTEM_APP" || true +} + +get_appname() { + base_apk=$(pm path $package_name | head -n1 | awk -F: '{print $2}') + app_name=$(aapt dump badging $base_apk 2>/dev/null | grep "application-label:" | sed "s/application-label://; s/'//g") + [ -z "$app_name" ] && app_name="$package_name" + echo "$app_name" +} + check_update() { [ -f "$MODDIR/disable" ] && rm -f "$MODDIR/disable" LOCAL_VERSION=$(grep '^versionCode=' "$MODPATH/update/module.prop" | awk -F= '{print $2}') @@ -68,7 +82,6 @@ get_update() { } install_update() { - PATH=/data/adb/ap/bin:/data/adb/ksu/bin:/data/adb/magisk:$PATH if command -v magisk >/dev/null 2>&1; then magisk --install-module "$MODPATH/tmp/module.zip" || exit 1 elif command -v apd >/dev/null 2>&1; then @@ -133,6 +146,15 @@ case "$1" in get_xposed exit ;; +--applist) + get_applist + exit + ;; +--appname) + package_name="$2" + get_appname + exit + ;; --check-update) REMOTE_VERSION="$2" check_update diff --git a/module/webui/scripts/applist.js b/module/webui/scripts/applist.js index 75148ea..b5af484 100644 --- a/module/webui/scripts/applist.js +++ b/module/webui/scripts/applist.js @@ -1,41 +1,42 @@ -import { exec, toast } from './assets/kernelsu.js'; -import { basePath, hideFloatingBtn, appsWithExclamation, appsWithQuestion } from './main.js'; +import { exec, spawn, toast } from './assets/kernelsu.js'; +import { basePath, loadingIndicator, hideFloatingBtn, appsWithExclamation, appsWithQuestion, applyRippleEffect } from './main.js'; const appTemplate = document.getElementById('app-template').content; const modeOverlay = document.querySelector('.mode-overlay'); export const appListContainer = document.getElementById('apps-list'); export const updateCard = document.getElementById('update-card'); +let targetList = []; + // Fetch and render applist export async function fetchAppList() { - try { - // fetch target list - let targetList = []; - await exec('cat /data/adb/tricky_store/target.txt') - .then(({ errno, stdout }) => { - if (errno === 0) { - targetList = processTargetList(stdout); - } else { - toast("Failed to read target.txt!"); - } - }); + // fetch target list + await exec('cat /data/adb/tricky_store/target.txt') + .then(({ errno, stdout }) => { + if (errno === 0) { + targetList = processTargetList(stdout); + } else { + toast("Failed to read target.txt!"); + } + }); - // fetch applist - const response = await fetch('applist.json'); - const appList = await response.json(); - const appNameMap = appList.reduce((map, app) => { - map[app.package_name] = app.app_name; - return map; - }, {}); + // Fetch cached applist + const response = await fetch('applist.json'); + const appList = await response.json(); + const appNameMap = appList.reduce((map, app) => { + map[app.package_name] = app.app_name; + return map; + }, {}); - // Get installed packages first - let appEntries = [], installedPackages = []; - const { stdout } = await exec(` - pm list packages -3 | awk -F: '{print $2}' - [ -s "/data/adb/tricky_store/system_app" ] && SYSTEM_APP=$(cat "/data/adb/tricky_store/system_app" | tr '\n' '|' | sed 's/|*$//') || SYSTEM_APP="" - [ -z "$SYSTEM_APP" ] || pm list packages -s | awk -F: '{print $2}' | grep -Ex "$SYSTEM_APP" || true - `); - installedPackages = stdout.split("\n").map(line => line.trim()).filter(Boolean); + // Get installed packages + let appEntries = [], installedPackages = []; + const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--applist']); + output.stdout.on('data', (data) => { + if (data.trim() === "") return; + installedPackages.push(data); + }); + output.on('exit', async () => { + // Create appEntries array contain { appName, packageName } appEntries = await Promise.all(installedPackages.map(async (packageName) => { if (appNameMap[packageName]) { return { @@ -43,76 +44,86 @@ export async function fetchAppList() { packageName }; } - const { stdout: appName } = await exec(` - base_apk=$(pm path ${packageName} | head -n1 | awk -F: '{print $2}') - aapt dump badging $base_apk 2>/dev/null | grep "application-label:" | sed "s/application-label://; s/'//g" - `, { env: { PATH: `$PATH:${basePath}/common:/data/data/com.termux/files/usr/bin` } }); - return { - appName: appName || packageName, - packageName - }; - })); - - // Sort - const sortedApps = appEntries.sort((a, b) => { - const aChecked = targetList.includes(a.packageName); - const bChecked = targetList.includes(b.packageName); - if (aChecked !== bChecked) { - return aChecked ? -1 : 1; - } - return (a.appName || "").localeCompare(b.appName || ""); - }); - - // Render - appListContainer.innerHTML = ""; - sortedApps.forEach(({ appName, packageName }) => { - const appElement = document.importNode(appTemplate, true); - const contentElement = appElement.querySelector(".content"); - contentElement.setAttribute("data-package", packageName); - - // Set unique names for radio button groups - const radioButtons = appElement.querySelectorAll('input[type="radio"]'); - radioButtons.forEach((radio) => { - radio.name = `mode-radio-${packageName}`; + return new Promise((resolve) => { + const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--appname', packageName], + { env: { PATH: `$PATH:${basePath}/common:/data/data/com.termux/files/usr/bin` } }); + output.stdout.on('data', (data) => { + resolve({ + appName: data, + packageName + }); + }); }); - - // Preselect the radio button based on the package name - const generateRadio = appElement.querySelector('#generate-mode'); - const hackRadio = appElement.querySelector('#hack-mode'); - const normalRadio = appElement.querySelector('#normal-mode'); - - if (appsWithExclamation.includes(packageName)) { - generateRadio.checked = true; - } else if (appsWithQuestion.includes(packageName)) { - hackRadio.checked = true; - } else { - normalRadio.checked = true; - } - - const nameElement = appElement.querySelector(".name"); - nameElement.innerHTML = ` -
-
${appName}
-
${packageName}
-
- `; - const checkbox = appElement.querySelector(".checkbox"); - checkbox.checked = targetList.includes(packageName); - appListContainer.appendChild(appElement); + })); + renderAppList(appEntries); + }); +} + +/** + * Render processed app list to the UI + * @param {Array} data - Array of objects containing appName and packageName + * @returns {void} + */ +function renderAppList(data) { + // Sort + const sortedApps = data.sort((a, b) => { + const aChecked = targetList.includes(a.packageName); + const bChecked = targetList.includes(b.packageName); + if (aChecked !== bChecked) { + return aChecked ? -1 : 1; + } + return (a.appName || "").localeCompare(b.appName || ""); + }); + + // Render + appListContainer.innerHTML = ""; + sortedApps.forEach(({ appName, packageName }) => { + const appElement = document.importNode(appTemplate, true); + const contentElement = appElement.querySelector(".content"); + contentElement.setAttribute("data-package", packageName); + + // Set unique names for radio button groups + const radioButtons = appElement.querySelectorAll('input[type="radio"]'); + radioButtons.forEach((radio) => { + radio.name = `mode-radio-${packageName}`; }); - } catch (error) { - toast("Failed to fetch app list!"); - console.error("Failed to fetch or render app list with names:", error); - } + + // Preselect the radio button based on the package name + const generateRadio = appElement.querySelector('#generate-mode'); + const hackRadio = appElement.querySelector('#hack-mode'); + const normalRadio = appElement.querySelector('#normal-mode'); + + if (appsWithExclamation.includes(packageName)) { + generateRadio.checked = true; + } else if (appsWithQuestion.includes(packageName)) { + hackRadio.checked = true; + } else { + normalRadio.checked = true; + } + + const nameElement = appElement.querySelector(".name"); + nameElement.innerHTML = ` +
+
${appName}
+
${packageName}
+
+ `; + const checkbox = appElement.querySelector(".checkbox"); + checkbox.checked = targetList.includes(packageName); + appListContainer.appendChild(appElement); + }); + + loadingIndicator.style.display = "none"; hideFloatingBtn(false); + document.querySelector('.uninstall-container').classList.remove('hidden-uninstall'); toggleableCheckbox(); if (appListContainer.firstChild !== updateCard) { appListContainer.insertBefore(updateCard, appListContainer.firstChild); } - const checkboxes = appListContainer.querySelectorAll(".checkbox"); setupRadioButtonListeners(); setupModeMenu(); updateCheckboxColor(); + applyRippleEffect(); } // Function to save app with ! and ? then process target list diff --git a/module/webui/scripts/assets/kernelsu.js b/module/webui/scripts/assets/kernelsu.js index ed09323..1413862 100644 --- a/module/webui/scripts/assets/kernelsu.js +++ b/module/webui/scripts/assets/kernelsu.js @@ -41,6 +41,27 @@ export function exec(command, options = {}) { }); } +/** + * Standard I/O stream for a child process. + * @class + */ +class Stdio { + constructor() { + this.listeners = {}; + } + on(event, listener) { + if (!this.listeners[event]) { + this.listeners[event] = []; + } + this.listeners[event].push(listener); + } + emit(event, ...args) { + if (this.listeners[event]) { + this.listeners[event].forEach(listener => listener(...args)); + } + } +} + /** * Spawn shell process with ksu.spawn * @param {string} command - The command to execute @@ -71,20 +92,6 @@ export function spawn(command, args = [], options = {}) { } } }; - function Stdio() { - this.listeners = {}; - } - Stdio.prototype.on = function(event, listener) { - if (!this.listeners[event]) { - this.listeners[event] = []; - } - this.listeners[event].push(listener); - }; - Stdio.prototype.emit = function(event, ...args) { - if (this.listeners[event]) { - this.listeners[event].forEach(listener => listener(...args)); - } - }; const callbackName = getUniqueCallbackName("spawn"); window[callbackName] = child; child.on("exit", () => delete window[callbackName]); diff --git a/module/webui/scripts/boot_hash.js b/module/webui/scripts/boot_hash.js index b362f52..99b7512 100644 --- a/module/webui/scripts/boot_hash.js +++ b/module/webui/scripts/boot_hash.js @@ -22,14 +22,12 @@ document.getElementById("boot-hash").addEventListener("click", async () => { }, 10); // read current boot hash - exec("cat /data/adb/boot_hash") + exec(`sed '/^#/d; /^$/d' /data/adb/boot_hash`) .then(({ errno, stdout }) => { if (errno !== 0) { inputBox.value = ""; } else { - const validHash = stdout - .split("\n") - .filter(line => !line.startsWith("#") && line.trim())[0]; + const validHash = stdout.trim(); inputBox.value = validHash || ""; } }); @@ -48,7 +46,7 @@ const closeBootHashMenu = () => { saveButton.addEventListener("click", async () => { const inputValue = inputBox.value.trim(); exec(` - resetprop -n ro.boot.vbmeta.digest ${inputValue} + resetprop -n ro.boot.vbmeta.digest "${inputValue}" [ -z "${inputValue}" ] && rm -f /data/adb/boot_hash || { echo "${inputValue}" > /data/adb/boot_hash chmod 644 /data/adb/boot_hash diff --git a/module/webui/scripts/main.js b/module/webui/scripts/main.js index 3e3ee2e..5571bf4 100644 --- a/module/webui/scripts/main.js +++ b/module/webui/scripts/main.js @@ -11,7 +11,7 @@ const title = document.querySelector('.header'); export const noConnection = document.querySelector('.no-connection'); // Loading, Save and Prompt Elements -const loadingIndicator = document.querySelector('.loading'); +export const loadingIndicator = document.querySelector('.loading'); const prompt = document.getElementById('prompt'); const floatingCard = document.querySelector('.floating-card'); const floatingBtn = document.querySelector('.floating-btn'); @@ -50,16 +50,12 @@ export async function refreshAppList() { appListContainer.innerHTML = ''; loadingIndicator.style.display = 'flex'; document.querySelector('.uninstall-container').classList.add('hidden-uninstall'); - await new Promise(resolve => setTimeout(resolve, 500)); window.scrollTo(0, 0); if (noConnection.style.display === "flex") { updateCheck(); exec(`rm -f "${basePath}/common/tmp/exclude-list"`); } - await fetchAppList(); - applyRippleEffect(); - loadingIndicator.style.display = 'none'; - document.querySelector('.uninstall-container').classList.remove('hidden-uninstall'); + fetchAppList(); isRefreshing = false; } @@ -201,7 +197,10 @@ function checkMMRL() { // Funtion to adapt floating button hide in MMRL export function hideFloatingBtn(hide = true) { - if (!hide) floatingCard.style.transform = 'translateY(0)'; + if (!hide) { + floatingCard.style.transform = 'translateY(0)'; + floatingBtn.style.display = 'block'; + } else floatingCard.style.transform = 'translateY(calc(var(--window-inset-bottom, 0px) + 120px))'; } @@ -299,15 +298,10 @@ document.addEventListener('DOMContentLoaded', async () => { setupMenuToggle(); setupLanguageMenu(); setupSystemAppMenu(); - await fetchAppList(); - applyRippleEffect(); + fetchAppList(); checkTrickyStoreVersion(); checkMagisk(); updateCheck(); securityPatch(); - loadingIndicator.style.display = "none"; - floatingBtn.style.display = 'block'; - hideFloatingBtn(false); document.getElementById("refresh").addEventListener("click", refreshAppList); - document.querySelector('.uninstall-container').classList.remove('hidden-uninstall'); }); diff --git a/module/webui/scripts/security_patch.js b/module/webui/scripts/security_patch.js index b212be4..6f0e611 100644 --- a/module/webui/scripts/security_patch.js +++ b/module/webui/scripts/security_patch.js @@ -1,4 +1,4 @@ -import { exec } from './assets/kernelsu.js'; +import { exec, spawn } from './assets/kernelsu.js'; import { basePath, showPrompt } from './main.js'; const overlay = document.getElementById('security-patch-overlay'); @@ -218,24 +218,29 @@ export function securityPatch() { // Auto config button autoButton.addEventListener('click', () => { - exec(`sh ${basePath}/common/get_extra.sh --security-patch`) - .then(({ errno, stdout }) => { - if (errno !== 0 || stdout.trim() === "not set") { - showPrompt('security_patch.auto_failed', false); - } else { - exec(`touch /data/adb/tricky_store/security_patch_auto_config`) - // Reset inputs - allPatchInput.value = ''; - systemPatchInput.value = ''; - bootPatchInput.value = ''; - vendorPatchInput.value = ''; + const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--security-patch']); + output.stdout.on('data', (data) => { + if (data.includes("not set")) { + showPrompt('security_patch.auto_failed', false); + } + }); + output.on('exit', (code) => { + if (code === 0) { + exec(`touch /data/adb/tricky_store/security_patch_auto_config`) + // Reset inputs + allPatchInput.value = ''; + systemPatchInput.value = ''; + bootPatchInput.value = ''; + vendorPatchInput.value = ''; - checkAdvanced(false); - showPrompt('security_patch.auto_success'); - } - hideSecurityPatchDialog(); - loadCurrentConfig(); - }); + checkAdvanced(false); + showPrompt('security_patch.auto_success'); + } else { + showPrompt('security_patch.auto_failed', false); + } + hideSecurityPatchDialog(); + loadCurrentConfig(); + }); }); // Save button @@ -308,21 +313,18 @@ export function securityPatch() { // Get button getButton.addEventListener('click', async () => { showPrompt('security_patch.fetching'); - setTimeout(() => { - exec(`sh ${basePath}/common/get_extra.sh --get-security-patch`) - .then(({ errno, stdout }) => { - if (errno !== 0) { - showPrompt('security_patch.get_failed', false); - } else { - showPrompt('security_patch.fetched', true, 1000); - checkAdvanced(true); + const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--get-security-patch']); + output.stdout.on('data', (data) => { + showPrompt('security_patch.fetched', true, 1000); + checkAdvanced(true); - allPatchInput.value = stdout.replace(/-/g, ''); - systemPatchInput.value = 'prop'; - bootPatchInput.value = stdout; - vendorPatchInput.value = stdout; - } - }) - }, 200); + allPatchInput.value = data.replace(/-/g, ''); + systemPatchInput.value = 'prop'; + bootPatchInput.value = data; + vendorPatchInput.value = data; + }); + output.on('exit', (code) => { + if (code !== 0) showPrompt('security_patch.get_failed', false); + }); }); } diff --git a/module/webui/scripts/update.js b/module/webui/scripts/update.js index 652e112..9333b84 100644 --- a/module/webui/scripts/update.js +++ b/module/webui/scripts/update.js @@ -1,4 +1,4 @@ -import { exec } from './assets/kernelsu.js'; +import { exec, spawn } from './assets/kernelsu.js'; import { basePath, showPrompt, noConnection, linkRedirect } from './main.js'; import { updateCard } from './applist.js'; @@ -52,12 +52,14 @@ export async function updateCheck() { zipURL = data.zipUrl; changelogURL = data.changelog; - const { stdout } = await exec(`sh ${basePath}/common/get_extra.sh --check-update ${remoteVersionCode}`); - if (stdout.includes("update")) { - showPrompt("prompt.new_update", true, 1500); - updateCard.style.display = "flex"; - setupUpdateMenu(); - } + const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--check-update', `${remoteVersionCode}`]); + output.stdout.on('data', (data) => { + if (data.includes("update")) { + showPrompt("prompt.new_update", true, 1500); + updateCard.style.display = "flex"; + setupUpdateMenu(); + } + }); } catch (error) { console.error("Error fetching JSON or executing command:", error); showPrompt("prompt.no_internet", false); @@ -111,43 +113,41 @@ function setupUpdateMenu() { // Update card updateCard.addEventListener('click', async () => { - try { - const { stdout } = await exec(` - [ -f ${basePath}/common/tmp/module.zip ] || echo "noModule" - [ -f ${basePath}/common/tmp/changelog.md ] || echo "noChangelog" - [ ! -f /data/adb/modules/TA_utl/update ] || echo "updated" - `); - if (stdout.trim().includes("updated")) { - installButton.style.display = "none"; - rebootButton.style.display = "flex"; - openUpdateMenu(); - } else if (stdout.trim().includes("noChangelog")) { - showPrompt("prompt.downloading"); - await downloadFile(changelogURL, "changelog.md"); - renderChangelog(); - openUpdateMenu(); - setTimeout(() => { - updateCard.click(); - }, 200); - } else if (stdout.trim().includes("noModule")) { - if (downloading) return; - downloading = true; - const { errno } = await exec(`sh ${basePath}/common/get_extra.sh --get-update ${zipURL}`); - if (errno === 0) { + const { stdout } = await exec(` + [ -f ${basePath}/common/tmp/module.zip ] || echo "noModule" + [ -f ${basePath}/common/tmp/changelog.md ] || echo "noChangelog" + [ ! -f /data/adb/modules/TA_utl/update ] || echo "updated" + `); + if (stdout.trim().includes("updated")) { + installButton.style.display = "none"; + rebootButton.style.display = "flex"; + openUpdateMenu(); + } else if (stdout.trim().includes("noChangelog")) { + showPrompt("prompt.downloading"); + await downloadFile(changelogURL, "changelog.md"); + renderChangelog(); + openUpdateMenu(); + setTimeout(() => { + updateCard.click(); + }, 200); + } else if (stdout.trim().includes("noModule")) { + if (downloading) return; + downloading = true; + const download = spawn('sh', [`${basePath}/common/get_extra.sh`, '--get-update', `${zipURL}`], + { env: { PATH: "$PATH:/data/adb/ap/bin:/data/adb/ksu/bin:/data/adb/magisk:/data/data/com.termux/files/usr/bin" } }); + download.on('exit', (code) => { + downloading = false; + if (code === 0) { showPrompt("prompt.downloaded"); installButton.style.display = "flex"; } else { showPrompt("prompt.download_fail", false); } - downloading = false; - } else { - installButton.style.display = "flex"; - renderChangelog(); - openUpdateMenu(); - } - } catch (error) { - showPrompt("prompt.download_fail", false); - console.error('Error download module update:', error); + }); + } else { + installButton.style.display = "flex"; + renderChangelog(); + openUpdateMenu(); } }); @@ -160,16 +160,20 @@ function setupUpdateMenu() { // Install button installButton.addEventListener('click', async () => { showPrompt("prompt.installing"); - await new Promise(resolve => setTimeout(resolve, 300)); - const { errno, stderr } = await exec(`sh ${basePath}/common/get_extra.sh --install-update`); - if (errno === 0) { - showPrompt("prompt.installed"); - installButton.style.display = "none"; - rebootButton.style.display = "flex"; - } else { - showPrompt("prompt.install_fail", false); - console.error('Fail to execute installation script:', stderr); - } + const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--install-update'], + { env: { PATH: "$PATH:/data/adb/ap/bin:/data/adb/ksu/bin:/data/adb/magisk" } }); + output.stderr.on('data', (data) => { + console.error('Error during installation:', data); + }) + output.on('exit', (code) => { + if (code === 0) { + showPrompt("prompt.installed"); + installButton.style.display = "none"; + rebootButton.style.display = "flex"; + } else { + showPrompt("prompt.install_fail", false); + } + }); }); // Reboot button diff --git a/module/webui/styles/applist.css b/module/webui/styles/applist.css index 9ee2e70..4986a33 100644 --- a/module/webui/styles/applist.css +++ b/module/webui/styles/applist.css @@ -58,12 +58,16 @@ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } +.update-content h1, .update-content h3 { - font-size: 24px; margin: 10px 0; margin-top: 0; } +.update-content h3 { + font-size: 24px; +} + .changelog { max-height: 65vh; overflow-y: auto;