Merge some files and rewrite the update history

This commit is contained in:
ShirkNeko
2025-03-22 14:09:21 +08:00
parent b28789ac7a
commit ba26677cfc
166 changed files with 6003 additions and 4896 deletions

View File

@@ -26,13 +26,3 @@ updates:
maven: maven:
patterns: patterns:
- "*" - "*"
- package-ecosystem: npm
directory: website
schedule:
interval: daily
allow:
- dependency-type: "all"
groups:
npm:
patterns:
- "*"

View File

@@ -1,71 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--https://ci.android.com/builds/submitted/9964412/kernel_virt_x86_64/latest/manifest_9964412.xml-->
<manifest>
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
<default revision="master" remote="aosp" sync-j="4" />
<superproject name="kernel/superproject" remote="aosp" revision="common-android14-6.1" />
<project path="build/kernel" name="kernel/build" revision="b0377a072bb3f78cdacfd6d809914a9d1b0c0148">
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
<linkfile dest="build/build.sh" src="build.sh" />
<linkfile dest="build/build_abi.sh" src="build_abi.sh" />
<linkfile dest="build/build_test.sh" src="build_test.sh" />
<linkfile dest="build/build_utils.sh" src="build_utils.sh" />
<linkfile dest="build/config.sh" src="config.sh" />
<linkfile dest="build/envsetup.sh" src="envsetup.sh" />
<linkfile dest="build/_setup_env.sh" src="_setup_env.sh" />
<linkfile dest="build/multi-switcher.sh" src="multi-switcher.sh" />
<linkfile dest="build/abi" src="abi" />
<linkfile dest="build/static_analysis" src="static_analysis" />
</project>
<project path="common" name="kernel/common" revision="7e35917775b8b3e3346a87f294e334e258bf15e6">
<linkfile dest=".source_date_epoch_dir" src="." />
</project>
<project path="kernel/tests" name="kernel/tests" revision="c90a1c1b226b975cc31e709fa96fc1c6ecdbe272" />
<project path="kernel/configs" name="kernel/configs" revision="52a7267d6a9f9efabf3cb43839bb5e7f7ff05be3" />
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="0d03de3246301028775f05ea388c2c444344a268" />
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" clone-depth="1" revision="4f7e5adc160ab726ac5bafb260de98e612904c50" />
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" revision="f7b0d5b0ee369864d5ac3e96ae24ec9e2b6a52da" />
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" revision="dc92e06585a7647bf739a2309a721b82fcfa01d4" />
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="5611871963f54c688d3ac49e527aecdef21e8567" />
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" revision="2597cb1b5525e419b7fa806373be673054a68d29" />
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="2680066d0844544b3e78d6022cd21321d31837c3" />
<project path="prebuilts/bazel/linux-x86_64" name="platform/prebuilts/bazel/linux-x86_64" clone-depth="1" revision="4fdb9395071ff22118311d434d697c2b6fd887b4" />
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" clone-depth="1" revision="491e6aa056676f29c4541f71bd738e4e876e4ba2" />
<project path="prebuilts/ndk-r23" name="toolchain/prebuilts/ndk/r23" clone-depth="1" revision="19ac7e4eded12adb99d4f613490dde6dd0e72664" />
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" revision="f998e5dc13c03f0eae9e373263d3afff0932c738" />
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" revision="707b2c5fe3d0d7d934a93e00a8a4062e83557831" />
<project path="external/stardoc" name="platform/external/stardoc" revision="e83f522ee95419e55d2c5654aa6e0143beeef595" />
<project path="external/python/absl-py" name="platform/external/python/absl-py" revision="393d0b1e3f0fea3e95944a2fd3282cc9f76d4f14" />
</manifest>

View File

@@ -1,89 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- https://ci.android.com/builds/submitted/11577653/kernel_virt_aarch64/latest/manifest_11577653.xml -->
<manifest>
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
<default revision="main" remote="aosp" sync-j="4" />
<superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6" />
<project path="build/kernel" name="kernel/build" groups="ddk" revision="9a2196a1ec1048c2869750c9d3969c88ac18adcd">
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
<linkfile dest="MODULE.bazel" src="kleaf/bzlmod/bazel.MODULE.bazel" />
<linkfile dest="WORKSPACE.bzlmod" src="kleaf/bzlmod/bazel.WORKSPACE.bzlmod" />
</project>
<project path="common" name="kernel/common" revision="ac1a7c65ff1bc7ece5569d62f02b121b4f2364f8" />
<project path="kernel/common-patches" name="kernel/common-patches" revision="3807ce65081de12ef4baa2a04487306672685160">
<linkfile dest="common/patches" src="android-mainline" />
</project>
<project path="kernel/tests" name="kernel/tests" revision="ca9fd66f5b48abc92990c9c770f73380b428362b" />
<project path="kernel/configs" name="kernel/configs" revision="be625f2ccf377a75d0ea86c082c716c322b8d4c6" />
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="60a24583ac921279e40a44f818040e40abb3ef46" />
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="93a1369ba33743a87bdf0183373f590a36ff7cb1" clone-depth="1" groups="ddk" />
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="cef8f53bb61fbdb02dbf4d433004f6cb637c3bc6" />
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="5aca9957ab19d2668c7f1da1954bbe89652d5fed" />
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="69f9fb9b8e75c6f1ff01f380d5251757785bb823" />
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="b09295493adc8d804b6d24286660f6e451e387fd" />
<project path="prebuilts/rust" name="platform/prebuilts/rust" revision="adc0e5499c3ddac831ca596d12cbef8d9747f737" clone-depth="1" />
<project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" clone-depth="1" revision="a76ca09c5593e22e65b0d823d508882c6c64c13e" />
<project path="prebuilts/asuite" name="platform/prebuilts/asuite" clone-depth="1" revision="24510f175cb313a92241500efee917c2930d5d30" />
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="28b7934249c2885db8b561f1439d74663fcdce93" />
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" revision="c6c90521b7c317f13d41bbd9336a8d45ee202cec" clone-depth="1" groups="ddk" />
<project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e535051ebc04204cec44bde38f62385d63180388" />
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk" revision="6b103c40d8113f001475d5e13672922ef2aa0e5a" />
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="2a10807a06153b5862da0369f4b6b368afc2dd08" />
<project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6" />
<project path="external/libcap" name="platform/external/libcap" revision="d7d1a0a38c5be06a7e7d6391d140b54878836f48" />
<project path="external/stardoc" name="platform/external/stardoc" groups="ddk" revision="f31250f9f5b03834d9964aaee7a3794c1d73d4a2" />
<project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk" revision="9ae5a78fc57c3cd539398373ae39601a8b923e62" />
<project path="external/bazelbuild-bazel-central-registry" name="platform/external/bazelbuild-bazel-central-registry" revision="3422f064566c274ea66633442521704d4a22486d" groups="ddk" />
<project path="external/bazelbuild-platforms" name="platform/external/bazelbuild-platforms" groups="ddk" revision="e352aabd0131f3ac3f340282a43ba85ffc3fe8fa" />
<project path="external/bazelbuild-apple_support" name="platform/external/bazelbuild-apple_support" groups="ddk" revision="f6003e1e3763f8aad9fb9acae79cfa5fff9ae988" />
<project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk" revision="f0df148dbeb9b9ed3816aad328ebe7c65efaaa24" />
<project path="external/bazelbuild-rules_java" name="platform/external/bazelbuild-rules_java" groups="ddk" revision="8e548c7053dffd1717d565f0409a88992f401da1" />
<project path="external/bazelbuild-rules_license" name="platform/external/bazelbuild-rules_license" groups="ddk" revision="f578df4fd057ffe2023728444759535685631548" />
<project path="external/bazelbuild-rules_pkg" name="platform/external/bazelbuild-rules_pkg" groups="ddk" revision="429887dfd8db834498ad95e99043f771a3882af0" />
<project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk" revision="f71847ac898655b67634bb14e77a7408c4fb5e00" />
<project path="external/bazelbuild-rules_rust" name="platform/external/bazelbuild-rules_rust" groups="ddk" revision="1520b49835be9122c2424231357d4db80069cc38" />
<project path="external/pigz" name="platform/external/pigz" groups="ddk" revision="9bc9fa17d499ddde88b77820f6d063e16c0cdd42" />
<project path="external/zlib" name="platform/external/zlib" groups="ddk" revision="eff168fd731068a3faddd9aae056875e10014a51" />
<project path="external/zopfli" name="platform/external/zopfli" groups="ddk" revision="36c79f00e5229800d2aaa13fc42c301ec8ef1153" />
</manifest>

View File

@@ -1,89 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- https://ci.android.com/builds/submitted/11577653/kernel_virt_x86_64/latest/manifest_11577653.xml -->
<manifest>
<remote name="aosp" fetch="https://android.googlesource.com/" review="https://android.googlesource.com/" />
<default revision="main" remote="aosp" sync-j="4" />
<superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6" />
<project path="build/kernel" name="kernel/build" groups="ddk" revision="9a2196a1ec1048c2869750c9d3969c88ac18adcd">
<linkfile dest="tools/bazel" src="kleaf/bazel.sh" />
<linkfile dest="WORKSPACE" src="kleaf/bazel.WORKSPACE" />
<linkfile dest="MODULE.bazel" src="kleaf/bzlmod/bazel.MODULE.bazel" />
<linkfile dest="WORKSPACE.bzlmod" src="kleaf/bzlmod/bazel.WORKSPACE.bzlmod" />
</project>
<project path="common" name="kernel/common" revision="ac1a7c65ff1bc7ece5569d62f02b121b4f2364f8" />
<project path="kernel/common-patches" name="kernel/common-patches" revision="3807ce65081de12ef4baa2a04487306672685160">
<linkfile dest="common/patches" src="android-mainline" />
</project>
<project path="kernel/tests" name="kernel/tests" revision="ca9fd66f5b48abc92990c9c770f73380b428362b" />
<project path="kernel/configs" name="kernel/configs" revision="be625f2ccf377a75d0ea86c082c716c322b8d4c6" />
<project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="60a24583ac921279e40a44f818040e40abb3ef46" />
<project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" revision="93a1369ba33743a87bdf0183373f590a36ff7cb1" clone-depth="1" groups="ddk" />
<project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="cef8f53bb61fbdb02dbf4d433004f6cb637c3bc6" />
<project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="5aca9957ab19d2668c7f1da1954bbe89652d5fed" />
<project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="69f9fb9b8e75c6f1ff01f380d5251757785bb823" />
<project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="b09295493adc8d804b6d24286660f6e451e387fd" />
<project path="prebuilts/rust" name="platform/prebuilts/rust" revision="adc0e5499c3ddac831ca596d12cbef8d9747f737" clone-depth="1" />
<project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" clone-depth="1" revision="a76ca09c5593e22e65b0d823d508882c6c64c13e" />
<project path="prebuilts/asuite" name="platform/prebuilts/asuite" clone-depth="1" revision="24510f175cb313a92241500efee917c2930d5d30" />
<project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="28b7934249c2885db8b561f1439d74663fcdce93" />
<project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" revision="c6c90521b7c317f13d41bbd9336a8d45ee202cec" clone-depth="1" groups="ddk" />
<project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e535051ebc04204cec44bde38f62385d63180388" />
<project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk" revision="6b103c40d8113f001475d5e13672922ef2aa0e5a" />
<project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="2a10807a06153b5862da0369f4b6b368afc2dd08" />
<project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6" />
<project path="external/libcap" name="platform/external/libcap" revision="d7d1a0a38c5be06a7e7d6391d140b54878836f48" />
<project path="external/stardoc" name="platform/external/stardoc" groups="ddk" revision="f31250f9f5b03834d9964aaee7a3794c1d73d4a2" />
<project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk" revision="9ae5a78fc57c3cd539398373ae39601a8b923e62" />
<project path="external/bazelbuild-bazel-central-registry" name="platform/external/bazelbuild-bazel-central-registry" revision="3422f064566c274ea66633442521704d4a22486d" groups="ddk" />
<project path="external/bazelbuild-platforms" name="platform/external/bazelbuild-platforms" groups="ddk" revision="e352aabd0131f3ac3f340282a43ba85ffc3fe8fa" />
<project path="external/bazelbuild-apple_support" name="platform/external/bazelbuild-apple_support" groups="ddk" revision="f6003e1e3763f8aad9fb9acae79cfa5fff9ae988" />
<project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk" revision="f0df148dbeb9b9ed3816aad328ebe7c65efaaa24" />
<project path="external/bazelbuild-rules_java" name="platform/external/bazelbuild-rules_java" groups="ddk" revision="8e548c7053dffd1717d565f0409a88992f401da1" />
<project path="external/bazelbuild-rules_license" name="platform/external/bazelbuild-rules_license" groups="ddk" revision="f578df4fd057ffe2023728444759535685631548" />
<project path="external/bazelbuild-rules_pkg" name="platform/external/bazelbuild-rules_pkg" groups="ddk" revision="429887dfd8db834498ad95e99043f771a3882af0" />
<project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk" revision="f71847ac898655b67634bb14e77a7408c4fb5e00" />
<project path="external/bazelbuild-rules_rust" name="platform/external/bazelbuild-rules_rust" groups="ddk" revision="1520b49835be9122c2424231357d4db80069cc38" />
<project path="external/pigz" name="platform/external/pigz" groups="ddk" revision="9bc9fa17d499ddde88b77820f6d063e16c0cdd42" />
<project path="external/zlib" name="platform/external/zlib" groups="ddk" revision="eff168fd731068a3faddd9aae056875e10014a51" />
<project path="external/zopfli" name="platform/external/zopfli" groups="ddk" revision="36c79f00e5229800d2aaa13fc42c301ec8ef1153" />
</manifest>

View File

@@ -1,137 +1,137 @@
name: GKI Kernel Build name: GKI Kernel Build
on: on:
workflow_call: workflow_call:
inputs: inputs:
version_name: version_name:
required: true required: true
type: string type: string
description: > description: >
With SUBLEVEL of kernel, With SUBLEVEL of kernel,
for example: android12-5.10.66 for example: android12-5.10.66
arch: arch:
required: true required: true
type: string type: string
description: > description: >
Build arch: aarch64/x86_64 Build arch: aarch64/x86_64
debug: debug:
required: false required: false
type: boolean type: boolean
default: true default: true
manifest_name: manifest_name:
required: false required: false
type: string type: string
description: > description: >
Local repo manifest xml path, Local repo manifest xml path,
typically for AVD kernel build. typically for AVD kernel build.
secrets: secrets:
BOOT_SIGN_KEY: BOOT_SIGN_KEY:
required: false required: false
CHAT_ID: CHAT_ID:
required: false required: false
BOT_TOKEN: BOT_TOKEN:
required: false required: false
MESSAGE_THREAD_ID: MESSAGE_THREAD_ID:
required: false required: false
jobs: jobs:
build: build:
name: Build ${{ inputs.version_name }} name: Build ${{ inputs.version_name }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Maximize build space - name: Maximize build space
uses: easimon/maximize-build-space@master uses: easimon/maximize-build-space@master
with: with:
root-reserve-mb: 8192 root-reserve-mb: 8192
temp-reserve-mb: 2048 temp-reserve-mb: 2048
remove-dotnet: 'true' remove-dotnet: 'true'
remove-android: 'true' remove-android: 'true'
remove-haskell: 'true' remove-haskell: 'true'
remove-codeql: 'true' remove-codeql: 'true'
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
path: KernelSU path: KernelSU
fetch-depth: 0 fetch-depth: 0
- name: Setup need_upload - name: Setup need_upload
id: need_upload id: need_upload
run: | run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=true" >> $GITHUB_OUTPUT echo "UPLOAD=true" >> $GITHUB_OUTPUT
else else
echo "UPLOAD=false" >> $GITHUB_OUTPUT echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi fi
- name: Setup kernel source - name: Setup kernel source
run: | run: |
echo "Free space:" echo "Free space:"
df -h df -h
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
sudo apt-get install repo -y sudo apt-get install repo -y
mkdir android-kernel && cd android-kernel mkdir android-kernel && cd android-kernel
repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -m "$GITHUB_WORKSPACE/KernelSU/.github/manifests/${{ inputs.manifest_name }}" --repo-rev=v2.16 repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -m "$GITHUB_WORKSPACE/KernelSU/.github/manifests/${{ inputs.manifest_name }}" --repo-rev=v2.16
repo --version repo --version
repo --trace sync -c -j$(nproc --all) --no-tags repo --trace sync -c -j$(nproc --all) --no-tags
df -h df -h
- name: Setup KernelSU - name: Setup KernelSU
env: env:
PATCH_PATH: ${{ inputs.patch_path }} PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }} IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: | run: |
cd $GITHUB_WORKSPACE/android-kernel cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup" echo "[+] KernelSU setup"
GKI_ROOT=$(pwd) GKI_ROOT=$(pwd)
echo "[+] GKI_ROOT: $GKI_ROOT" echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers" echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile" echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
echo "[+] Apply KernelSU patches" echo "[+] Apply KernelSU patches"
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found" cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
if [ "$IS_DEBUG_KERNEL" = "true" ]; then if [ "$IS_DEBUG_KERNEL" = "true" ]; then
echo "[+] Enable debug features for kernel" echo "[+] Enable debug features for kernel"
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi fi
repo status repo status
echo "[+] KernelSU setup done." echo "[+] KernelSU setup done."
cd $GITHUB_WORKSPACE/KernelSU cd $GITHUB_WORKSPACE/KernelSU
VERSION=$(($(git rev-list --count HEAD) + 10200)) VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION" echo "VERSION: $VERSION"
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
- name: Make working directory clean to avoid dirty - name: Make working directory clean to avoid dirty
working-directory: android-kernel working-directory: android-kernel
run: | run: |
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!" rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
git config --global user.email "bot@kernelsu.org" git config --global user.email "bot@kernelsu.org"
git config --global user.name "KernelSUBot" git config --global user.name "KernelSUBot"
cd common/ && git add -A && git commit -a -m "Add KernelSU" cd common/ && git add -A && git commit -a -m "Add KernelSU"
repo status repo status
- name: Build kernel - name: Build kernel
working-directory: android-kernel working-directory: android-kernel
run: | run: |
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }} export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }} export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi fi
tools/bazel run --config=fast --config=stamp --lto=thin //common-modules/virtual-device:virtual_device_${{ inputs.arch }}_dist -- --dist_dir=dist tools/bazel run --config=fast --config=stamp --lto=thin //common-modules/virtual-device:virtual_device_${{ inputs.arch }}_dist -- --dist_dir=dist
NAME=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }} NAME=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
TARGET_IMAGE=dist/bzImage TARGET_IMAGE=dist/bzImage
if [ ! -e $TARGET_IMAGE ]; then if [ ! -e $TARGET_IMAGE ]; then
TARGET_IMAGE=dist/Image TARGET_IMAGE=dist/Image
fi fi
mv $TARGET_IMAGE $NAME mv $TARGET_IMAGE $NAME
echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV
- name: Upload Kernel - name: Upload Kernel
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }} name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
path: "${{ env.file_path }}" path: "${{ env.file_path }}"

View File

@@ -1,62 +0,0 @@
name: Build debug kernel
on:
workflow_dispatch:
jobs:
build-debug-kernel-a12:
uses: ./.github/workflows/gki-kernel.yml
with:
version: android12-5.10
version_name: android12-5.10.226
tag: android12-5.10-2024-11
os_patch_level: 2024-11
patch_path: "5.10"
debug: true
build-debug-kernel-a13:
strategy:
matrix:
include:
- version: "5.10"
sub_level: 223
os_patch_level: 2024-11
- version: "5.15"
sub_level: 167
os_patch_level: 2024-11
uses: ./.github/workflows/gki-kernel.yml
with:
version: android13-${{ matrix.version }}
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
debug: true
build-debug-kernel-a14:
strategy:
matrix:
include:
- version: "5.15"
sub_level: 167
os_patch_level: 2024-11
- version: "6.1"
sub_level: 115
os_patch_level: 2024-12
uses: ./.github/workflows/gki-kernel.yml
with:
version: android14-${{ matrix.version }}
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
debug: true
build-debug-kernel-a15:
strategy:
matrix:
include:
- version: "6.6"
sub_level: 57
os_patch_level: 2024-12
uses: ./.github/workflows/gki-kernel.yml
with:
version: android15-${{ matrix.version }}
version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
debug: true

View File

@@ -1,118 +0,0 @@
name: Build Kernel - Android 12
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-a12.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build_a12.sh"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-a12.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build-a12.sh"
- "kernel/**"
workflow_call:
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
strategy:
matrix:
include:
- sub_level: 209
os_patch_level: 2024-05
- sub_level: 218
os_patch_level: 2024-08
- sub_level: 226
os_patch_level: 2024-11
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
version: android12-5.10
version_name: android12-5.10.${{ matrix.sub_level }}
tag: android12-5.10-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: "5.10"
upload-artifacts:
needs: build-kernel
runs-on: ubuntu-latest
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
env:
CHAT_ID: ${{ secrets.CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: List artifacts
run: |
tree
- name: Download prebuilt toolchain
run: |
AOSP_MIRROR=https://android.googlesource.com
BRANCH=main-kernel-build-2024
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
pip3 install telethon
- name: Set boot sign key
env:
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
run: |
if [ ! -z "$BOOT_SIGN_KEY" ]; then
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
fi
- name: Bot session cache
id: bot_session_cache
uses: actions/cache@v4
if: false
with:
path: scripts/ksubot.session
key: ${{ runner.os }}-bot-session
- name: Build boot images
run: |
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
cd $GITHUB_WORKSPACE/KernelSU
export VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
cd -
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a12.sh
- name: Display structure of boot files
run: ls -R
- name: Upload images artifact
uses: actions/upload-artifact@v4
with:
name: boot-images-android12
path: Image-android12*/*.img.gz
check-build-kernel:
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
uses: ./.github/workflows/gki-kernel.yml
with:
version: android12-5.10
version_name: android12-5.10.223
tag: android12-5.10-2024-11
os_patch_level: 2024-11
patch_path: "5.10"

View File

@@ -1,151 +0,0 @@
name: Build Kernel - Android 13
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-a13.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build_a13.sh"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-a13.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build-a13.sh"
- "kernel/**"
workflow_call:
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "5.10"
sub_level: 209
os_patch_level: 2024-05
- version: "5.10"
sub_level: 210
os_patch_level: 2024-06
- version: "5.10"
sub_level: 214
os_patch_level: 2024-07
- version: "5.10"
sub_level: 218
os_patch_level: 2024-08
- version: "5.10"
sub_level: 223
os_patch_level: 2024-11
- version: "5.15"
sub_level: 148
os_patch_level: 2024-05
- version: "5.15"
sub_level: 149
os_patch_level: 2024-07
- version: "5.15"
sub_level: 151
os_patch_level: 2024-08
- version: "5.15"
sub_level: 153
os_patch_level: 2024-09
- version: "5.15"
sub_level: 167
os_patch_level: 2024-11
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
version: android13-${{ matrix.version }}
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
upload-artifacts:
needs: build-kernel
runs-on: ubuntu-latest
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
env:
CHAT_ID: ${{ secrets.CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: List artifacts
run: |
tree
- name: Download prebuilt toolchain
run: |
AOSP_MIRROR=https://android.googlesource.com
BRANCH=main-kernel-build-2024
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
pip3 install telethon
- name: Set boot sign key
env:
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
run: |
if [ ! -z "$BOOT_SIGN_KEY" ]; then
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
fi
- name: Bot session cache
id: bot_session_cache
uses: actions/cache@v4
if: false
with:
path: scripts/ksubot.session
key: ${{ runner.os }}-bot-session
- name: Build boot images
run: |
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
cd $GITHUB_WORKSPACE/KernelSU
export VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
cd -
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh
- name: Display structure of boot files
run: ls -R
- name: Upload images artifact
uses: actions/upload-artifact@v4
with:
name: boot-images-android13
path: Image-android13*/*.img.gz
check-build-kernel:
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "5.10"
sub_level: 223
os_patch_level: 2024-11
- version: "5.15"
sub_level: 167
os_patch_level: 2024-11
uses: ./.github/workflows/gki-kernel.yml
with:
version: android13-${{ matrix.version }}
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}

View File

@@ -1,163 +0,0 @@
name: Build Kernel - Android 14
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-a14.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build_a13.sh"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-a14.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build-a13.sh"
- "kernel/**"
workflow_call:
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "5.15"
sub_level: 148
os_patch_level: 2024-05
- version: "5.15"
sub_level: 149
os_patch_level: 2024-06
- version: "5.15"
sub_level: 153
os_patch_level: 2024-07
- version: "5.15"
sub_level: 158
os_patch_level: 2024-08
- version: "5.15"
sub_level: 164
os_patch_level: 2024-09
- version: "5.15"
sub_level: 167
os_patch_level: 2024-11
- version: "6.1"
sub_level: 75
os_patch_level: 2024-05
- version: "6.1"
sub_level: 78
os_patch_level: 2024-06
- version: "6.1"
sub_level: 84
os_patch_level: 2024-07
- version: "6.1"
sub_level: 90
os_patch_level: 2024-08
- version: "6.1"
sub_level: 93
os_patch_level: 2024-09
- version: "6.1"
sub_level: 99
os_patch_level: 2024-10
- version: "6.1"
sub_level: 112
os_patch_level: 2024-11
- version: "6.1"
sub_level: 115
os_patch_level: 2024-12
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
version: android14-${{ matrix.version }}
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
upload-artifacts:
needs: build-kernel
runs-on: ubuntu-latest
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
env:
CHAT_ID: ${{ secrets.CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: List artifacts
run: |
tree
- name: Download prebuilt toolchain
run: |
AOSP_MIRROR=https://android.googlesource.com
BRANCH=main-kernel-build-2024
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
pip3 install telethon
- name: Set boot sign key
env:
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
run: |
if [ ! -z "$BOOT_SIGN_KEY" ]; then
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
fi
- name: Bot session cache
id: bot_session_cache
uses: actions/cache@v4
if: false
with:
path: scripts/ksubot.session
key: ${{ runner.os }}-bot-session
- name: Build boot images
run: |
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
cd $GITHUB_WORKSPACE/KernelSU
export VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
cd -
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh
- name: Display structure of boot files
run: ls -R
- name: Upload images artifact
uses: actions/upload-artifact@v4
with:
name: boot-images-android14
path: Image-android14*/*.img.gz
check-build-kernel:
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "5.15"
sub_level: 167
os_patch_level: 2024-11
- version: "6.1"
sub_level: 115
os_patch_level: 2024-12
uses: ./.github/workflows/gki-kernel.yml
with:
version: android14-${{ matrix.version }}
version_name: android14-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android14-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}

View File

@@ -1,133 +0,0 @@
name: Build Kernel - Android 15
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-a15.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build_a13.sh"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-a15.yml"
- ".github/workflows/gki-kernel.yml"
- ".github/scripts/build-a13.sh"
- "kernel/**"
workflow_call:
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "6.6"
sub_level: 30
os_patch_level: 2024-08
- version: "6.6"
sub_level: 46
os_patch_level: 2024-09
- version: "6.6"
sub_level: 50
os_patch_level: 2024-10
- version: "6.6"
sub_level: 56
os_patch_level: 2024-11
- version: "6.6"
sub_level: 57
os_patch_level: 2024-12
uses: ./.github/workflows/gki-kernel.yml
secrets: inherit
with:
version: android15-${{ matrix.version }}
version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
upload-artifacts:
needs: build-kernel
runs-on: ubuntu-latest
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' || github.ref == 'refs/heads/ci' }}
env:
CHAT_ID: ${{ secrets.CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: List artifacts
run: |
tree
- name: Download prebuilt toolchain
run: |
AOSP_MIRROR=https://android.googlesource.com
BRANCH=main-kernel-build-2024
git clone $AOSP_MIRROR/platform/prebuilts/build-tools -b $BRANCH --depth 1 build-tools
git clone $AOSP_MIRROR/kernel/prebuilts/build-tools -b $BRANCH --depth 1 kernel-build-tools
git clone $AOSP_MIRROR/platform/system/tools/mkbootimg -b $BRANCH --depth 1
pip3 install telethon
- name: Set boot sign key
env:
BOOT_SIGN_KEY: ${{ secrets.BOOT_SIGN_KEY }}
run: |
if [ ! -z "$BOOT_SIGN_KEY" ]; then
echo "$BOOT_SIGN_KEY" > ./kernel-build-tools/linux-x86/share/avb/testkey_rsa2048.pem
fi
- name: Bot session cache
id: bot_session_cache
uses: actions/cache@v4
if: false
with:
path: scripts/ksubot.session
key: ${{ runner.os }}-bot-session
- name: Build boot images
run: |
export AVBTOOL=$GITHUB_WORKSPACE/kernel-build-tools/linux-x86/bin/avbtool
export GZIP=$GITHUB_WORKSPACE/build-tools/path/linux-x86/gzip
export LZ4=$GITHUB_WORKSPACE/build-tools/path/linux-x86/lz4
export MKBOOTIMG=$GITHUB_WORKSPACE/mkbootimg/mkbootimg.py
export UNPACK_BOOTIMG=$GITHUB_WORKSPACE/mkbootimg/unpack_bootimg.py
cd $GITHUB_WORKSPACE/KernelSU
export VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
cd -
bash $GITHUB_WORKSPACE/KernelSU/.github/scripts/build_a13.sh
- name: Display structure of boot files
run: ls -R
- name: Upload images artifact
uses: actions/upload-artifact@v4
with:
name: boot-images-android15
path: Image-android15*/*.img.gz
check-build-kernel:
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
strategy:
matrix:
include:
- version: "6.6"
sub_level: 57
os_patch_level: 2024-12
uses: ./.github/workflows/gki-kernel.yml
with:
version: android15-${{ matrix.version }}
version_name: android15-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android15-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}

View File

@@ -1,137 +0,0 @@
name: Build Kernel - ChromeOS ARCVM
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-arcvm.yml"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-arcvm.yml"
- "kernel/**"
workflow_call:
workflow_dispatch:
env:
git_tag: chromeos-5.10-arcvm
jobs:
build:
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
strategy:
matrix:
include:
- arch: x86_64
kernel_image_name: bzImage
build_config: build.config.gki.x86_64
defconfig: x86_64_arcvm_defconfig
- arch: arm64
kernel_image_name: Image
build_config: build.config.gki.aarch64
defconfig: arm64_arcvm_defconfig
name: Build ChromeOS ARCVM kernel
runs-on: ubuntu-20.04
env:
LTO: thin
ROOT_DIR: /
KERNEL_DIR: ${{ github.workspace }}/kernel
steps:
- name: Install Build Tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends bc \
bison build-essential ca-certificates flex git gnupg \
libelf-dev libssl-dev lsb-release software-properties-common wget \
libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip \
rsync python3 device-tree-compiler
sudo ln -s --force python3 /usr/bin/python
export LLVM_VERSION=12
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh $LLVM_VERSION
rm ./llvm.sh
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
- name: Checkout KernelSU
uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: Setup kernel source
run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ env.git_tag }} --depth=1
- name: Extract version from Makefile
working-directory: kernel
run: |
VERSION=$(grep -E '^VERSION = ' Makefile | awk '{print $3}')
PATCHLEVEL=$(grep -E '^PATCHLEVEL = ' Makefile | awk '{print $3}')
SUBLEVEL=$(grep -E '^SUBLEVEL = ' Makefile | awk '{print $3}')
echo "ChromeOS ARCVM Linux kernel version: $VERSION.$PATCHLEVEL.$SUBLEVEL"
echo "version=$VERSION.$PATCHLEVEL.$SUBLEVEL" >> $GITHUB_ENV
- name: Setup KernelSU
working-directory: kernel
run: |
echo "[+] KernelSU setup"
KERNEL_ROOT=$GITHUB_WORKSPACE/kernel
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
echo "[+] Apply KernelSU patches"
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch || echo "[-] No patch found"
echo "[+] Patch script/setlocalversion"
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
echo "[+] KernelSU setup done."
cd $GITHUB_WORKSPACE/KernelSU
KSU_VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "KernelSU version: $KSU_VERSION"
echo "kernelsu_version=$KSU_VERSION" >> $GITHUB_ENV
- name: Build Kernel
working-directory: kernel
env:
KERNEL_IMAGE_NAME: ${{ matrix.kernel_image_name }}
ARCH: ${{ matrix.arch }}
run: |
set -a && . ${{ matrix.build_config }}; set +a
export DEFCONFIG=${{ matrix.defconfig }}
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null
scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) ${KERNEL_IMAGE_NAME} modules prepare-objtool
ls -l -h ${PWD}/arch/${ARCH}/boot
echo "file_path=${PWD}/arch/${ARCH}/boot/${KERNEL_IMAGE_NAME}" >> $GITHUB_ENV
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
uses: actions/upload-artifact@v4
with:
name: kernel-ARCVM-${{ matrix.arch }}-${{ env.version }}
path: "${{ env.file_path }}"

View File

@@ -1,70 +0,0 @@
name: Build Kernel - AVD
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-avd.yml"
- ".github/workflows/avd-kernel.yml"
- ".github/workflows/manifests/*xml"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-avd.yml"
- ".github/workflows/avd-kernel.yml"
- ".github/workflows/manifests/*.xml"
- "kernel/**"
workflow_call:
workflow_dispatch:
inputs:
upload:
required: false
type: boolean
default: false
description: "Whether to upload to branch"
jobs:
build-kernel:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
uses: ./.github/workflows/avd-kernel.yml
secrets: inherit
strategy:
fail-fast: false
matrix:
include:
- version: "android-14-avd_x86_64"
manifest: "android-14-avd_x86_64.xml"
arch: "x86_64"
- version: "android-15-avd_aarch64"
manifest: "android-15-avd_aarch64.xml"
arch: "aarch64"
- version: "android-15-avd_x86_64"
manifest: "android-15-avd_x86_64.xml"
arch: "x86_64"
with:
version_name: ${{ matrix.version }}
manifest_name: ${{ matrix.manifest }}
arch: ${{ matrix.arch }}
debug: true
push-to-branch:
needs: [build-kernel]
runs-on: ubuntu-latest
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || inputs.upload
steps:
- name: Download all workflow run artifacts
uses: actions/download-artifact@v4
with:
path: bin/
merge-multiple: true
- name: Push to branch LKM
run: |
cd bin
git config --global init.defaultBranch avd
git init
git remote add origin https://${{ secrets.TOKEN }}@github.com/${{ github.repository }}
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
find . -type f
git add .
git commit -m "Upload AVD Kernel from ${{ github.sha }}" -m "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
git push --force --set-upstream origin avd

View File

@@ -1,38 +0,0 @@
name: Build Kernel - WSA
on:
push:
branches: ["main", "ci", "checkci"]
paths:
- ".github/workflows/build-kernel-wsa.yml"
- ".github/workflows/wsa-kernel.yml"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-wsa.yml"
- ".github/workflows/wsa-kernel.yml"
- "kernel/**"
workflow_call:
workflow_dispatch:
jobs:
build:
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/checkci'
strategy:
matrix:
arch: [x86_64, arm64]
version: ["5.15.94.2", "5.15.104.1", "5.15.104.2", "5.15.104.3", "5.15.104.4"]
uses: ./.github/workflows/wsa-kernel.yml
with:
arch: ${{ matrix.arch }}
version: ${{ matrix.version }}
check_build:
if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || github.ref == 'refs/heads/checkci'
uses: ./.github/workflows/wsa-kernel.yml
strategy:
matrix:
arch: [x86_64, arm64]
with:
arch: ${{ matrix.arch }}
version: "5.15.104.4"

View File

@@ -24,23 +24,23 @@ jobs:
matrix: matrix:
include: include:
- version: "android12-5.10" - version: "android12-5.10"
sub_level: 226 sub_level: 233
os_patch_level: 2024-11 os_patch_level: 2025-02
- version: "android13-5.10" - version: "android13-5.10"
sub_level: 223 sub_level: 228
os_patch_level: 2024-11 os_patch_level: 2025-01
- version: "android13-5.15" - version: "android13-5.15"
sub_level: 167 sub_level: 170
os_patch_level: 2024-11 os_patch_level: 2025-01
- version: "android14-5.15" - version: "android14-5.15"
sub_level: 167 sub_level: 170
os_patch_level: 2024-11 os_patch_level: 2025-01
- version: "android14-6.1" - version: "android14-6.1"
sub_level: 115 sub_level: 124
os_patch_level: 2024-12 os_patch_level: 2025-02
- version: "android15-6.6" - version: "android15-6.6"
sub_level: 57 sub_level: 66
os_patch_level: 2024-12 os_patch_level: 2025-02
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging # uses: ./.github/workflows/gki-kernel-mock.yml when debugging
uses: ./.github/workflows/gki-kernel.yml uses: ./.github/workflows/gki-kernel.yml
with: with:

View File

@@ -6,7 +6,9 @@ on:
paths: paths:
- '.github/workflows/build-manager.yml' - '.github/workflows/build-manager.yml'
- 'manager/**' - 'manager/**'
- 'kernel/**'
- 'userspace/ksud/**' - 'userspace/ksud/**'
- 'userspace/zakomksd/**'
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
paths: paths:
@@ -28,7 +30,6 @@ on:
type: boolean type: boolean
default: false default: false
description: "Whether to upload lkm" description: "Whether to upload lkm"
jobs: jobs:
check-build-lkm: check-build-lkm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -47,7 +48,7 @@ jobs:
cd tmp cd tmp
git config --global init.defaultBranch bot git config --global init.defaultBranch bot
git config --global user.name 'Bot' git config --global user.name 'Bot'
git config --global user.email 'bot@github.5ec1cff.io' git config --global user.email 'bot@github.shirkneko.io'
git init . git init .
git remote add origin https://github.com/${{ github.repository }} git remote add origin https://github.com/${{ github.repository }}
CURRENT_COMMIT="${{ github.event.head_commit.id }}" CURRENT_COMMIT="${{ github.event.head_commit.id }}"
@@ -62,7 +63,7 @@ jobs:
cd .. cd ..
rm -rf tmp rm -rf tmp
fi fi
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/susfs' ]; then
need_upload=true need_upload=true
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
need_upload="${{ inputs.upload_lkm }}" need_upload="${{ inputs.upload_lkm }}"
@@ -81,7 +82,19 @@ jobs:
with: with:
upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }} upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }}
secrets: inherit secrets: inherit
build-zakomksd:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy:
matrix:
include:
- target: aarch64-linux-android
os: ubuntu-latest
uses: ./.github/workflows/zakomksd.yml
with:
target: ${{ matrix.target }}
os: ${{ matrix.os }}
build-ksud: build-ksud:
if: ${{ always() }} if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ] needs: [ check-build-lkm, build-lkm ]
@@ -123,7 +136,7 @@ jobs:
fi fi
- name: Write key - name: Write key
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref == 'refs/heads/susfs' || github.ref_type == 'tag' }}
run: | run: |
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
{ {
@@ -147,6 +160,12 @@ jobs:
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3
- name: Download arm64 zakomksd
uses: actions/download-artifact@v4
with:
name: zakomksd-aarch64-linux-android
path: .
- name: Download arm64 ksud - name: Download arm64 ksud
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@@ -163,8 +182,13 @@ jobs:
run: | run: |
mkdir -p app/src/main/jniLibs/arm64-v8a mkdir -p app/src/main/jniLibs/arm64-v8a
mkdir -p app/src/main/jniLibs/x86_64 mkdir -p app/src/main/jniLibs/x86_64
cp -f ../aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud.so cp -f ../aarch64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/arm64-v8a/libzakomk.so
cp -f ../x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud.so cp -f ../x86_64-linux-android/release/zakomk ../manager/app/src/main/jniLibs/x86_64/libzakomk.so
- name: Copy zakomksd to app jniLibs
run: |
mkdir -p app/src/main/jniLibs/arm64-v8a
cp -f ../arm64-v8a/zakomksd ../manager/app/src/main/jniLibs/arm64-v8a/libzakomksd.so
- name: Build with Gradle - name: Build with Gradle
run: | run: |
@@ -176,7 +200,6 @@ jobs:
} >> gradle.properties } >> gradle.properties
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
./gradlew clean assembleRelease ./gradlew clean assembleRelease
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
@@ -190,7 +213,7 @@ jobs:
with: with:
name: "mappings" name: "mappings"
path: "manager/app/build/outputs/mapping/release/" path: "manager/app/build/outputs/mapping/release/"
- name: Bot session cache - name: Bot session cache
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
id: bot_session_cache id: bot_session_cache

View File

@@ -34,4 +34,4 @@ jobs:
- name: Run clippy - name: Run clippy
run: | run: |
cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release cross clippy --manifest-path userspace/ksud/Cargo.toml --target aarch64-linux-android --release
cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release cross clippy --manifest-path userspace/ksud/Cargo.toml --target x86_64-linux-android --release

View File

@@ -64,4 +64,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v4

View File

@@ -103,7 +103,7 @@ jobs:
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
sudo apt-get install repo -y sudo apt-get install repo -y
mkdir android-kernel && cd android-kernel mkdir android-kernel && cd android-kernel
repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.16 repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.35
REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }}) REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }})
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
if grep -q deprecated <<< $REMOTE_BRANCH; then if grep -q deprecated <<< $REMOTE_BRANCH; then
@@ -255,4 +255,4 @@ jobs:
if: ${{ inputs.build_lkm == true }} if: ${{ inputs.build_lkm == true }}
with: with:
name: ${{ inputs.version }}-lkm name: ${{ inputs.version }}-lkm
path: ./output/*_kernelsu.ko path: ./output/*_kernelsu.ko

View File

@@ -71,4 +71,4 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ksud-${{ inputs.target }} name: ksud-${{ inputs.target }}
path: userspace/ksud/target/**/release/ksud* path: userspace/ksud/target/**/release/zakomk*

View File

@@ -1,96 +0,0 @@
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
build-manager:
uses: ./.github/workflows/build-manager.yml
secrets: inherit
build-a12-kernel:
uses: ./.github/workflows/build-kernel-a12.yml
secrets: inherit
build-a13-kernel:
uses: ./.github/workflows/build-kernel-a13.yml
secrets: inherit
build-a14-kernel:
uses: ./.github/workflows/build-kernel-a14.yml
secrets: inherit
build-a15-kernel:
uses: ./.github/workflows/build-kernel-a15.yml
secrets: inherit
build-wsa-kernel:
uses: ./.github/workflows/build-kernel-wsa.yml
secrets: inherit
build-arcvm-kernel:
uses: ./.github/workflows/build-kernel-arcvm.yml
secrets: inherit
release:
needs:
- build-manager
- build-a12-kernel
- build-a13-kernel
- build-a14-kernel
- build-wsa-kernel
- build-arcvm-kernel
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Rename ksud
run: |
mkdir -p ksud
for dir in ./ksud-*; do
if [ -d "$dir" ]; then
echo "----- Rename $dir -----"
ksud_platform_name=$(basename "$dir")
find "$dir" -type f -name "ksud" -path "*/release/*" | while read -r ksud_file; do
if [ -f "$ksud_file" ]; then
mv "$ksud_file" "ksud/$ksud_platform_name"
fi
done
fi
done
- name: Zip AnyKernel3
run: |
for dir in AnyKernel3-*; do
if [ -d "$dir" ]; then
echo "----- Zip $dir -----"
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
fi
done
- name: Zip WSA kernel
run: |
for dir in kernel-WSA-*; do
if [ -d "$dir" ]; then
echo "------ Zip $dir ----------"
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
fi
done
- name: Zip ChromeOS ARCVM kernel
run: |
for dir in kernel-ARCVM-*; do
if [ -d "$dir" ]; then
echo "------ Zip $dir ----------"
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
fi
done
- name: Display structure of downloaded files
run: ls -R
- name: release
uses: softprops/action-gh-release@v2
with:
files: |
manager/*.apk
android*-lkm/*_kernelsu.ko
AnyKernel3-*.zip
boot-images-*/Image-*/*.img.gz
kernel-WSA*.zip
kernel-ARCVM*.zip
ksud/ksud-*

View File

@@ -16,7 +16,7 @@ on:
jobs: jobs:
shellcheck: shellcheck:
runs-on: ubuntu-latest runs-on: self-hosted
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -1,106 +0,0 @@
name: Build Kernel - WSA
on:
workflow_call:
inputs:
arch:
required: true
type: string
description: >
Build arch: x86_64 / arm64
version:
required: true
type: string
description: >
Build version
jobs:
build:
name: Build WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
runs-on: ubuntu-22.04
env:
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
CCACHE_NOHASHDIR: "true"
CCACHE_HARDLINK: "true"
steps:
- name: Install Build Tools
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: bc bison build-essential flex libelf-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu gzip ccache
version: 1.0
- name: Cache LLVM
id: cache-llvm
uses: actions/cache@v4
with:
path: ./llvm
key: llvm-12.0.1
- name: Setup LLVM
uses: KyleMayes/install-llvm-action@v1
with:
version: "12.0.1"
force-version: true
ubuntu-version: "16.04"
cached: ${{ steps.cache-llvm.outputs.cache-hit }}
- name: Checkout KernelSU
uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: Setup kernel source
uses: actions/checkout@v4
with:
repository: microsoft/WSA-Linux-Kernel
ref: android-lts/latte-2/${{ inputs.version }}
path: WSA-Linux-Kernel
- name: Setup Ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: WSA-Kernel-${{ inputs.version }}-${{ inputs.arch }}
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
max-size: 2G
- name: Setup KernelSU
working-directory: WSA-Linux-Kernel
run: |
echo "[+] KernelSU setup"
KERNEL_ROOT=$GITHUB_WORKSPACE/WSA-Linux-Kernel
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
DRIVER_KCONFIG=$KERNEL_ROOT/drivers/Kconfig
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
echo "[+] Apply KernelSU patches"
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.15/*.patch || echo "[-] No patch found"
echo "[+] KernelSU setup done."
cd $GITHUB_WORKSPACE/KernelSU
VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
- name: Build Kernel
working-directory: WSA-Linux-Kernel
run: |
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
declare -A ARCH_MAP=(["x86_64"]="x64" ["arm64"]="arm64")
cp configs/wsa/config-wsa-${ARCH_MAP[${{ inputs.arch }}]} .config
make olddefconfig
declare -A FILE_NAME=(["x86_64"]="bzImage" ["arm64"]="Image")
make -j`nproc` LLVM=1 ARCH=${{ inputs.arch }} $(if [ "${{ inputs.arch }}" == "arm64" ]; then echo CROSS_COMPILE=aarch64-linux-gnu; fi) ${FILE_NAME[${{ inputs.arch }}]} CCACHE="/usr/bin/ccache"
declare -A ARCH_MAP_FILE=(["x86_64"]="x86" ["arm64"]="arm64")
echo "file_path=WSA-Linux-Kernel/arch/${ARCH_MAP_FILE[${{ inputs.arch }}]}/boot/${FILE_NAME[${{ inputs.arch }}]}" >> $GITHUB_ENV
- name: Upload kernel-${{ inputs.arch }}-${{ inputs.version }}
uses: actions/upload-artifact@v4
with:
name: kernel-WSA-${{ inputs.arch }}-${{ inputs.version }}
path: "${{ env.file_path }}"

40
.github/workflows/zakomksd.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Build zakomksd
on:
push:
branches: [ "mian" ]
paths:
- '.github/workflows/zakomksd.yml'
- 'userspace/zakomksd/**'
workflow_dispatch:
workflow_call:
inputs:
target:
required: true
type: string
os:
required: false
type: string
default: self-hosted
jobs:
build-susfs:
name: Build userspace zakomksd
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build zakomksd
working-directory: ./userspace/zakomksd
run: |
$ANDROID_NDK_HOME/ndk-build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
name: zakomksd-aarch64-linux-android
path: ./userspace/zakomksd/libs

101
docs/README-en.md Normal file
View File

@@ -0,0 +1,101 @@
# SukiSU
**Enlish** | [简体中文](README.md)
Android device root solution based on [KernelSU](https://github.com/KernelSU/KernelSU)
**Experimental! Use at your own risk! **This solution is based on [KernelSU]() and is experimental!
>
> This is an unofficial fork, all rights reserved [@tiann](https://github.com/tiann)
>
- Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only)
## How to add
Using the susfs-dev branch (integrated susfs with support for non-GKI devices)
```
curl -LSs “https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh” | bash -s susfs-dev
```
Use main branching (no longer with support for non-GKI devices)
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
```
## How to use integrated susfs
Use the susfs-dev branch directly without any patching
## More links
Projects compiled based on Sukisu and susfs
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
- [OnePlus](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## Hook method
- This method references the hook manual to (https://github.com/rsuntk/KernelSU)
1. **KPROBES hook:**
- This fork only supports GKI (5.10 - 6.x) kernels, all non-GKI kernels must use manual hooks.
- For Loadable Kernel Modules (LKM)
- Default hooking method for GKI kernels
- Requires `CONFIG_KPROBES=y`. 2.
2. **Hooks manual:**
- For GKI (5.10 - 6.x) kernels, add `CONFIG_KSU_MANUAL_HOOK=y` to the kernel defconfig and make sure to protect KernelSU hooks by using `#ifdef CONFIG_KSU_MANUAL_HOOK` instead of `#ifdef CONFIG_KSU`.
- Standard KernelSU hooks: https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
- backslashxx syscall hooks: https://github.com/backslashxx/KernelSU/issues/5
- Some non-GKI devices that manually integrate KPROBES do not require the manual VFS hook `new_hook.patch` patch
## Usage
[GKI]
1. such as millet redmi samsung and other devices (does not include the magic kernel manufacturers such as: meizu, a plus real me oppo)
2. find more links in the GKI build project to find the device kernel version directly download with TWRP or kernel flashing tool to brush into the zip with AnyKernel3 suffix can be
3. General without the suffix of the .zip compressed package is universal, gz suffix for the special TianGui models, lz4 suffix for Google models, general brush without the suffix can be!
[OnePlus]
1. Find the Yiga project in the More link and fill in your own, then build it with cloud compilation, and finally brush in the zip with AnyKernel3 suffix.
Note: You only need to fill in the first two kernel versions, such as 5.10, 5.15, 6.1, 6.6.
- Please search for the processor codename by yourself, usually it is all English without numbers.
- Branching and configuration files, please fill in the kernel open source address.
## Features
1. Kernel-based `su` and root access management.
2. Not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS) module system. 3.
3. [Application Profiles](https://kernelsu.org/guide/app-profile.html): Lock root privileges in a cage. 4.
4. Bringing back non-GKI/GKI 1.0 support
5. More customization
## License
- The file in the “kernel” directory is [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
- All other parts except the “kernel” directory are [GPL-3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html).
## Sponsorship list
- [Ktouls](https://github.com/Ktouls) Thanks so much for bringing me support
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
How the above list does not have your name, I will keep you updated, thanks again for your support!
## Contributions
- [KernelSU](https://github.com/tiann/KernelSU): original project
- [MKSU](https://github.com/5ec1cff/KernelSU): Used project
- [RKSU](https://github.com/rsuntk/KernelsU)Re-support of non-GKI devices using the kernel of this project
- [susfs](https://gitlab.com/simonpunk/susfs4ksu)Used susfs file system
- [KernelSU](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU conceptualization
- [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility
- [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills.

View File

@@ -1,24 +1,102 @@
# MKSU # SukiSU
A [KernelSU](https://github.com/tiann/KernelSU/commit/eeffecbd1bd7d49672a1c6bd52d95d28a42acb21)-based root solution for Android devices. **简体中文** | [English](README-en.md)
**Experimental. Use at your own risk.** 基于 [KernelSU](https://github.com/tiann/KernelSU) 的安卓设备 root 解决方案
## Features **实验性!使用风险自负!**
1. Kernel-based `su` and root access management.
2. Module system not based on [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS).
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage.
## License >
> 这是非官方分支,保留所有权利 [@tiann](https://github.com/tiann)
>
- Files under the `kernel` directory are [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
- All other parts except the `kernel` directory are [GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html).
## Credits ## 如何添加
在内核源码的根目录下执行以下命令:
- [KernelSU](https://github.com/tiann/KernelSU): The original project. 使用 susfs-dev 分支已集成susfs带非GKI设备的支持
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): The KernelSU idea. ```
- [Magisk](https://github.com/topjohnwu/Magisk): The powerful root tool. curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s susfs-dev
- [genuine](https://github.com/brevent/genuine/): APK v2 signature validation. ```
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills.
使用 main 分支不再带非GKI设备的支持
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
```
## 如何集成 susfs
1. 直接使用 susfs-dev 分支,不需要再集成 susfs
## 钩子方法
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
1. **KPROBES 钩子:**
- 此方法仅支持 GKI 2.05.10 - 6.x内核,所有非 GKI 2.0 内核都必须使用手动钩子
- 用于可加载内核模块 (LKM)
- GKI 2.0 内核的默认钩子方法
- 需要 `CONFIG_KPROBES=y`
2. **手动钩子:**
- 对于 GKI 2.05.10 - 6.x内核需要在对应设备的 defconfig 文件中添加 `CONFIG_KSU_MANUAL_HOOK=y` 并确保使用 `#ifdef CONFIG_KSU_MANUAL_HOOK` 而不是 `#ifdef CONFIG_KSU` 来保护 KernelSU 钩子
- 标准的 KernelSU 钩子https://kernelsu.org/guide/how-to-integrate-for-non-gki.html#manually-modify-the-kernel-source
- backslashxx 的 syscall 手动钩子https://github.com/backslashxx/KernelSU/issues/5
- 部分手动集成KPROBES的非 GKI 2.0 设备不需要手动 VFS 钩子 `new_hook.patch` 补丁
## 更多链接
基于 SukiSU 和 susfs 编译的项目
- [GKI](https://github.com/ShirkNeko/GKI_KernelSU_SUSFS)
- [一加](https://github.com/ShirkNeko/Action_OnePlus_MKSU_SUSFS)
## 使用方法
### GKI
1. 适用于如小米红米三星等的 GKI 2.0 的设备(不包含魔改内核的厂商如魅族、一加、真我和 oppo
2. 找到更多链接里的 GKI 构建的项目找到设备内核版本直接下载用TWRP或者内核刷写工具刷入带 AnyKernel3 后缀的压缩包即可
3. 一般不带后缀的 .zip 压缩包是通用gz 后缀的为天玑机型专用lz4 后缀的为谷歌系机型专用,一般刷不带后缀的即可
### 一加
1.找到更多链接里的一加项目进行自行填写,然后云编译构建,最后刷入带 AnyKernel3 后缀的压缩包即可
注意事项:
- 内核版本只需要填写前两位即可,如 5.105.156.16.6
- 处理器代号请自行搜索,一般为全英文不带数字的代号
- 分支和配置文件请自行到一加内核开源地址进行填写
## 特点
1. 基于内核的 `su` 和 root 访问管理
2. 基于 5ec1cff 的 [Magic Mount](https://github.com/5ec1cff/KernelSU) 的模块系统
3. [App Profile](https://kernelsu.org/guide/app-profile.html):将 root 权限锁在笼子里
4. 恢复对非 GKI 2.0 内核的支持仅限susfs-dev和未进行susfs补丁的dev分支
5. 更多自定义功能
## 许可证
- `kernel` 目录下的文件是 [GPL-2.0-only](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)。
-`kernel` 目录外,所有其他部分均为 [GPL-3.0 或更高版本](https://www.gnu.org/licenses/gpl-3.0.html)。
## 赞助名单
- [Ktouls](https://github.com/Ktouls) 非常感谢你给我带来的支持
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
如何以上名单没有你的名称,我会及时更新,再次感谢大家的支持
## 贡献
- [KernelSU](https://github.com/tiann/KernelSU):原始项目
- [MKSU](https://github.com/5ec1cff/KernelSU):使用的项目
- [RKSU](https://github.com/rsuntk/KernelsU):使用该项目的 kernel 对非GKI设备重新进行支持
- [susfs4ksu](https://gitlab.com/simonpunk/susfs4ksu):使用的 susfs 文件系统
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/)KernelSU 的构想
- [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具
- [genuine](https://github.com/brevent/genuine/)APK v2 签名验证
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技能

View File

@@ -16,4 +16,11 @@ config KSU_DEBUG
help help
Enable KernelSU debug mode. Enable KernelSU debug mode.
config KSU_HOOK
bool "Enable KernelSU Hook"
default n
help
This option enables the KernelSU Hook feature. If enabled, it will
override the kernel version check and enable the hook functionality.
endmenu endmenu

View File

@@ -20,8 +20,8 @@ obj-$(CONFIG_KSU) += kernelsu.o
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0) ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow) $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD) KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
# ksu_version: major * 10000 + git version + 200 for historical reasons # ksu_version: major * 10000 + git version + 600 for historical reasons
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200)) $(eval KSU_VERSION=$(shell expr 12000 + $(KSU_GIT_VERSION) + 500))
$(info -- KernelSU version: $(KSU_VERSION)) $(info -- KernelSU version: $(KSU_VERSION))
ccflags-y += -DKSU_VERSION=$(KSU_VERSION) ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
else # If there is no .git file, the default version will be passed. else # If there is no .git file, the default version will be passed.
@@ -30,11 +30,11 @@ ccflags-y += -DKSU_VERSION=16
endif endif
ifndef KSU_EXPECTED_SIZE ifndef KSU_EXPECTED_SIZE
KSU_EXPECTED_SIZE := 384 KSU_EXPECTED_SIZE := 0x35c
endif endif
ifndef KSU_EXPECTED_HASH ifndef KSU_EXPECTED_HASH
KSU_EXPECTED_HASH := 7e0c6d7278a3bb8e364e0fcba95afaf3666cf5ff3c245a3b63c8833bd0445cc4 KSU_EXPECTED_HASH := 947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef
endif endif
ifdef KSU_MANAGER_PACKAGE ifdef KSU_MANAGER_PACKAGE
@@ -44,6 +44,10 @@ endif
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE)) $(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
$(info -- Supported Unofficial Manager: ShirkNeko (GKI) (Non-GKI))
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE) ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\" ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"

View File

@@ -236,12 +236,10 @@ static void nuke_ext4_sysfs() {
const char* name = sb->s_type->name; const char* name = sb->s_type->name;
if (strcmp(name, "ext4") != 0) { if (strcmp(name, "ext4") != 0) {
pr_info("nuke but module aren't mounted\n"); pr_info("nuke but module aren't mounted\n");
path_put(&path);
return; return;
} }
ext4_unregister_sysfs(sb); ext4_unregister_sysfs(sb);
path_put(&path);
} }
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
@@ -886,7 +884,9 @@ void __init ksu_core_init(void)
void ksu_core_exit(void) void ksu_core_exit(void)
{ {
#ifdef CONFIG_KPROBE
pr_info("ksu_core_kprobe_exit\n"); pr_info("ksu_core_kprobe_exit\n");
// we dont use this now // we dont use this now
// ksu_kprobe_exit(); // ksu_kprobe_exit();
#endif
} }

View File

@@ -56,9 +56,12 @@ int __init kernelsu_init(void)
ksu_allowlist_init(); ksu_allowlist_init();
ksu_throne_tracker_init(); ksu_throne_tracker_init();
#ifdef CONFIG_KPROBES
ksu_sucompat_init(); ksu_sucompat_init();
ksu_ksud_init(); ksu_ksud_init();
#else
pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html");
#endif
#ifdef MODULE #ifdef MODULE
#ifndef CONFIG_KSU_DEBUG #ifndef CONFIG_KSU_DEBUG
@@ -76,8 +79,10 @@ void kernelsu_exit(void)
destroy_workqueue(ksu_workqueue); destroy_workqueue(ksu_workqueue);
#ifdef CONFIG_KPROBES
ksu_ksud_exit(); ksu_ksud_exit();
ksu_sucompat_exit(); ksu_sucompat_exit();
#endif
ksu_core_exit(); ksu_core_exit();
} }

View File

@@ -20,6 +20,7 @@
#include "kernel_compat.h" #include "kernel_compat.h"
#include "selinux/selinux.h" #include "selinux/selinux.h"
static const char KERNEL_SU_RC[] = static const char KERNEL_SU_RC[] =
"\n" "\n"
@@ -47,12 +48,21 @@ static void stop_vfs_read_hook();
static void stop_execve_hook(); static void stop_execve_hook();
static void stop_input_hook(); static void stop_input_hook();
#ifdef CONFIG_KPROBES
static struct work_struct stop_vfs_read_work; static struct work_struct stop_vfs_read_work;
static struct work_struct stop_execve_hook_work; static struct work_struct stop_execve_hook_work;
static struct work_struct stop_input_hook_work; static struct work_struct stop_input_hook_work;
#else
bool ksu_vfs_read_hook __read_mostly = true;
bool ksu_execveat_hook __read_mostly = true;
bool ksu_input_hook __read_mostly = true;
#endif
u32 ksu_devpts_sid; u32 ksu_devpts_sid;
// Detect whether it is on or not
static bool is_boot_phase = true;
void on_post_fs_data(void) void on_post_fs_data(void)
{ {
static bool done = false; static bool done = false;
@@ -68,6 +78,9 @@ void on_post_fs_data(void)
ksu_devpts_sid = ksu_get_devpts_sid(); ksu_devpts_sid = ksu_get_devpts_sid();
pr_info("devpts sid: %d\n", ksu_devpts_sid); pr_info("devpts sid: %d\n", ksu_devpts_sid);
// End of boot state
is_boot_phase = false;
} }
#define MAX_ARG_STRINGS 0x7FFFFFFF #define MAX_ARG_STRINGS 0x7FFFFFFF
@@ -144,6 +157,11 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
struct user_arg_ptr *argv, struct user_arg_ptr *argv,
struct user_arg_ptr *envp, int *flags) struct user_arg_ptr *envp, int *flags)
{ {
#ifndef CONFIG_KPROBES
if (!ksu_execveat_hook) {
return 0;
}
#endif
struct filename *filename; struct filename *filename;
static const char app_process[] = "/system/bin/app_process"; static const char app_process[] = "/system/bin/app_process";
@@ -295,6 +313,11 @@ static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to)
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
size_t *count_ptr, loff_t **pos) size_t *count_ptr, loff_t **pos)
{ {
#ifndef CONFIG_KPROBES
if (!ksu_vfs_read_hook) {
return 0;
}
#endif
struct file *file; struct file *file;
char __user *buf; char __user *buf;
size_t count; size_t count;
@@ -403,10 +426,15 @@ static bool is_volumedown_enough(unsigned int count)
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
int *value) int *value)
{ {
#ifndef CONFIG_KPROBES
if (!ksu_input_hook) {
return 0;
}
#endif
if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) { if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) {
int val = *value; int val = *value;
pr_info("KEY_VOLUMEDOWN val: %d\n", val); pr_info("KEY_VOLUMEDOWN val: %d\n", val);
if (val) { if (val && is_boot_phase) {
// key pressed, count it // key pressed, count it
volumedown_pressed_count += 1; volumedown_pressed_count += 1;
if (is_volumedown_enough(volumedown_pressed_count)) { if (is_volumedown_enough(volumedown_pressed_count)) {
@@ -440,6 +468,7 @@ bool ksu_is_safe_mode()
return false; return false;
} }
#ifdef CONFIG_KPROBES
static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs) static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
{ {
struct pt_regs *real_regs = PT_REAL_REGS(regs); struct pt_regs *real_regs = PT_REAL_REGS(regs);
@@ -511,17 +540,28 @@ static void do_stop_input_hook(struct work_struct *work)
{ {
unregister_kprobe(&input_event_kp); unregister_kprobe(&input_event_kp);
} }
#endif
static void stop_vfs_read_hook() static void stop_vfs_read_hook()
{ {
#ifdef CONFIG_KPROBES
bool ret = schedule_work(&stop_vfs_read_work); bool ret = schedule_work(&stop_vfs_read_work);
pr_info("unregister vfs_read kprobe: %d!\n", ret); pr_info("unregister vfs_read kprobe: %d!\n", ret);
#else
ksu_vfs_read_hook = false;
pr_info("stop vfs_read_hook\n");
#endif
} }
static void stop_execve_hook() static void stop_execve_hook()
{ {
#ifdef CONFIG_KPROBES
bool ret = schedule_work(&stop_execve_hook_work); bool ret = schedule_work(&stop_execve_hook_work);
pr_info("unregister execve kprobe: %d!\n", ret); pr_info("unregister execve kprobe: %d!\n", ret);
#else
ksu_execveat_hook = false;
pr_info("stop execve_hook\n");
#endif
} }
static void stop_input_hook() static void stop_input_hook()
@@ -531,13 +571,19 @@ static void stop_input_hook()
return; return;
} }
input_hook_stopped = true; input_hook_stopped = true;
#ifdef CONFIG_KPROBES
bool ret = schedule_work(&stop_input_hook_work); bool ret = schedule_work(&stop_input_hook_work);
pr_info("unregister input kprobe: %d!\n", ret); pr_info("unregister input kprobe: %d!\n", ret);
#else
ksu_input_hook = false;
pr_info("stop input_hook\n");
#endif
} }
// ksud: module support // ksud: module support
void ksu_ksud_init() void ksu_ksud_init()
{ {
#ifdef CONFIG_KPROBES
int ret; int ret;
ret = register_kprobe(&execve_kp); ret = register_kprobe(&execve_kp);
@@ -552,12 +598,17 @@ void ksu_ksud_init()
INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook); INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook);
INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook); INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook);
INIT_WORK(&stop_input_hook_work, do_stop_input_hook); INIT_WORK(&stop_input_hook_work, do_stop_input_hook);
#endif
} }
void ksu_ksud_exit() void ksu_ksud_exit()
{ {
#ifdef CONFIG_KPROBES
unregister_kprobe(&execve_kp); unregister_kprobe(&execve_kp);
// this should be done before unregister vfs_read_kp // this should be done before unregister vfs_read_kp
// unregister_kprobe(&vfs_read_kp); // unregister_kprobe(&vfs_read_kp);
unregister_kprobe(&input_event_kp); unregister_kprobe(&input_event_kp);
is_boot_phase = false;
#endif
} }

View File

@@ -1,8 +1,6 @@
#ifndef __KSU_H_KSUD #ifndef __KSU_H_KSUD
#define __KSU_H_KSUD #define __KSU_H_KSUD
#include <linux/types.h>
#define KSUD_PATH "/data/adb/ksud" #define KSUD_PATH "/data/adb/ksud"
void on_post_fs_data(void); void on_post_fs_data(void);
@@ -10,5 +8,4 @@ void on_post_fs_data(void);
bool ksu_is_safe_mode(void); bool ksu_is_safe_mode(void);
extern u32 ksu_devpts_sid; extern u32 ksu_devpts_sid;
#endif #endif

View File

@@ -39,7 +39,7 @@ perform_cleanup() {
# Sets up or update KernelSU environment # Sets up or update KernelSU environment
setup_kernelsu() { setup_kernelsu() {
echo "[+] Setting up KernelSU..." echo "[+] Setting up KernelSU..."
test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/5ec1cff/KernelSU && echo "[+] Repository cloned." test -d "$GKI_ROOT/KernelSU" || git clone https://github.com/ShirkNeko/KernelSU && echo "[+] Repository cloned."
cd "$GKI_ROOT/KernelSU" cd "$GKI_ROOT/KernelSU"
git stash && echo "[-] Stashed current changes." git stash && echo "[-] Stashed current changes."
if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then if [ "$(git status | grep -Po 'v\d+(\.\d+)*' | head -n1)" ]; then

View File

@@ -164,7 +164,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
return 0; return 0;
} }
#ifdef CONFIG_KPROBES
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
{ {
struct pt_regs *real_regs = PT_REAL_REGS(regs); struct pt_regs *real_regs = PT_REAL_REGS(regs);
@@ -228,18 +228,23 @@ static void destroy_kprobe(struct kprobe **kp_ptr)
} }
static struct kprobe *su_kps[3]; static struct kprobe *su_kps[3];
#endif
// sucompat: permited process can execute 'su' to gain root access. // sucompat: permited process can execute 'su' to gain root access.
void ksu_sucompat_init() void ksu_sucompat_init()
{ {
#ifdef CONFIG_KPROBES
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre); su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre); su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
#endif
} }
void ksu_sucompat_exit() void ksu_sucompat_exit()
{ {
#ifdef CONFIG_KPROBES
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) { for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
destroy_kprobe(&su_kps[i]); destroy_kprobe(&su_kps[i]);
} }
#endif
} }

View File

@@ -148,6 +148,13 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen)) if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".." return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".."
if (d_type == DT_DIR && namelen >= 8 && !strncmp(name, "vmdl", 4) &&
!strncmp(name + namelen - 4, ".tmp", 4)) {
pr_info("Skipping directory: %.*s\n", namelen, name);
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
}
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir, if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
namelen, name) >= DATA_PATH_LEN) { namelen, name) >= DATA_PATH_LEN) {
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen, pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,

View File

@@ -10,6 +10,8 @@ plugins {
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
alias(libs.plugins.lsplugin.apksign) alias(libs.plugins.lsplugin.apksign)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
val managerVersionCode: Int by rootProject.extra val managerVersionCode: Int by rootProject.extra
@@ -23,7 +25,7 @@ apksign {
} }
android { android {
namespace = "me.weishu.kernelsu" namespace = "shirkneko.zako.sukisu"
buildTypes { buildTypes {
release { release {
@@ -68,7 +70,7 @@ android {
applicationVariants.all { applicationVariants.all {
outputs.forEach { outputs.forEach {
val output = it as BaseVariantOutputImpl val output = it as BaseVariantOutputImpl
output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk" output.outputFileName = "SukiSU_${managerVersionName}_${managerVersionCode}-$name.apk"
} }
kotlin.sourceSets { kotlin.sourceSets {
getByName(name) { getByName(name) {
@@ -102,6 +104,8 @@ dependencies {
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.foundation)
implementation(libs.androidx.documentfile)
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
@@ -133,4 +137,7 @@ dependencies {
implementation(libs.androidx.webkit) implementation(libs.androidx.webkit)
implementation(libs.lsposed.cxx) implementation(libs.lsposed.cxx)
implementation(libs.com.github.topjohnwu.libsu.core)
} }

View File

@@ -3,6 +3,9 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:name=".KernelSUApplication" android:name=".KernelSUApplication"
@@ -15,6 +18,7 @@
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.KernelSU" android:theme="@style/Theme.KernelSU"
android:requestLegacyExternalStorage="true"
tools:targetApi="34"> tools:targetApi="34">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"

View File

@@ -1,5 +1,5 @@
// IKsuInterface.aidl // IKsuInterface.aidl
package me.weishu.kernelsu; package shirkneko.zako.sukisu;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import rikka.parcelablelist.ParcelableListSlice; import rikka.parcelablelist.ParcelableListSlice;

Binary file not shown.

View File

@@ -10,7 +10,7 @@ project("kernelsu")
find_package(cxx REQUIRED CONFIG) find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx) link_libraries(cxx::cxx)
add_library(kernelsu add_library(zako
SHARED SHARED
jni.cc jni.cc
ksu.cc ksu.cc
@@ -18,4 +18,4 @@ add_library(kernelsu
find_library(log-lib log) find_library(log-lib log)
target_link_libraries(kernelsu ${log-lib}) target_link_libraries(zako ${log-lib})

View File

@@ -12,7 +12,7 @@
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) { Java_shirkneko_zako_sukisu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
auto cpkg = env->GetStringUTFChars(pkg, nullptr); auto cpkg = env->GetStringUTFChars(pkg, nullptr);
auto result = become_manager(cpkg); auto result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg); env->ReleaseStringUTFChars(pkg, cpkg);
@@ -21,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg)
extern "C" extern "C"
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) { Java_shirkneko_zako_sukisu_Natives_getVersion(JNIEnv *env, jobject) {
return get_version(); return get_version();
} }
extern "C" extern "C"
JNIEXPORT jintArray JNICALL JNIEXPORT jintArray JNICALL
Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) { Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) {
int uids[1024]; int uids[1024];
int size = 0; int size = 0;
bool result = get_allow_list(uids, &size); bool result = get_allow_list(uids, &size);
@@ -42,13 +42,13 @@ Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) { Java_shirkneko_zako_sukisu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
return is_safe_mode(); return is_safe_mode();
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isLkmMode(JNIEnv *env, jclass clazz) { Java_shirkneko_zako_sukisu_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
return is_lkm_mode(); return is_lkm_mode();
} }
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
extern "C" extern "C"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) { Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) { if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
return nullptr; return nullptr;
} }
@@ -129,7 +129,7 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg,
bool useDefaultProfile = !get_app_profile(key, &profile); bool useDefaultProfile = !get_app_profile(key, &profile);
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
auto constructor = env->GetMethodID(cls, "<init>", "()V"); auto constructor = env->GetMethodID(cls, "<init>", "()V");
auto obj = env->NewObject(cls, constructor); auto obj = env->NewObject(cls, constructor);
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
@@ -207,8 +207,8 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg,
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) { Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I"); auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
@@ -293,16 +293,16 @@ Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobjec
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) { Java_shirkneko_zako_sukisu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
return uid_should_umount(uid); return uid_should_umount(uid);
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) { Java_shirkneko_zako_sukisu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
return is_su_enabled(); return is_su_enabled();
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { Java_shirkneko_zako_sukisu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
return set_su_enabled(enabled); return set_su_enabled(enabled);
} }

View File

@@ -1,362 +0,0 @@
package me.weishu.kernelsu.ui.screen
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FileUpload
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.getCurrentKmi
import me.weishu.kernelsu.ui.util.getSupportedKmis
import me.weishu.kernelsu.ui.util.isAbDevice
import me.weishu.kernelsu.ui.util.isInitBoot
import me.weishu.kernelsu.ui.util.rootAvailable
/**
* @author weishu
* @date 2024/3/12.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember {
mutableStateOf<InstallMethod?>(null)
}
var lkmSelection by remember {
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
}
val onInstall = {
installMethod?.let { method ->
val flashIt = FlashIt.FlashBoot(
boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot
)
navigator.navigate(FlashScreenDestination(flashIt))
}
}
val currentKmi by produceState(initialValue = "") { value = getCurrentKmi() }
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
kmi?.let {
lkmSelection = LkmSelection.KmiString(it)
onInstall()
}
}
val onClickNext = {
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
// no lkm file selected and cannot get current kmi
selectKmiDialog.show()
} else {
onInstall()
}
}
val selectLkmLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
lkmSelection = LkmSelection.LkmUri(uri)
}
}
}
val onLkmUpload = {
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopBar(
onBack = dropUnlessResumed { navigator.popBackStack() },
onLkmUpload = onLkmUpload,
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
SelectInstallMethod { method ->
installMethod = method
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text(
stringResource(
id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)"
)
)
}
Button(modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
onClick = {
onClickNext()
}) {
Text(
stringResource(id = R.string.install_next),
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
}
}
}
}
}
sealed class InstallMethod {
data class SelectFile(
val uri: Uri? = null,
@StringRes override val label: Int = R.string.select_file,
override val summary: String?
) : InstallMethod()
data object DirectInstall : InstallMethod() {
override val label: Int
get() = R.string.direct_install
}
data object DirectInstallToInactiveSlot : InstallMethod() {
override val label: Int
get() = R.string.install_inactive_slot
}
abstract val label: Int
open val summary: String? = null
}
@Composable
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val selectFileTip = stringResource(
id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot"
)
val radioOptions =
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
if (rootAvailable) {
radioOptions.add(InstallMethod.DirectInstall)
if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
}
}
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
val selectImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
val option = InstallMethod.SelectFile(uri, summary = selectFileTip)
selectedOption = option
onSelected(option)
}
}
}
val confirmDialog = rememberConfirmDialog(onConfirm = {
selectedOption = InstallMethod.DirectInstallToInactiveSlot
onSelected(InstallMethod.DirectInstallToInactiveSlot)
}, onDismiss = null)
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
val onClick = { option: InstallMethod ->
when (option) {
is InstallMethod.SelectFile -> {
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
is InstallMethod.DirectInstall -> {
selectedOption = option
onSelected(option)
}
is InstallMethod.DirectInstallToInactiveSlot -> {
confirmDialog.showConfirm(dialogTitle, dialogContent)
}
}
}
Column {
radioOptions.forEach { option ->
val interactionSource = remember { MutableInteractionSource() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.toggleable(
value = option.javaClass == selectedOption?.javaClass,
onValueChange = {
onClick(option)
},
role = Role.RadioButton,
indication = LocalIndication.current,
interactionSource = interactionSource
)
) {
RadioButton(
selected = option.javaClass == selectedOption?.javaClass,
onClick = {
onClick(option)
},
interactionSource = interactionSource
)
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
Text(
text = stringResource(id = option.label),
fontSize = MaterialTheme.typography.titleMedium.fontSize,
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
fontStyle = MaterialTheme.typography.titleMedium.fontStyle
)
option.summary?.let {
Text(
text = it,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
fontStyle = MaterialTheme.typography.bodySmall.fontStyle
)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
return rememberCustomDialog { dismiss ->
val supportedKmi by produceState(initialValue = emptyList<String>()) {
value = getSupportedKmis()
}
val options = supportedKmi.map { value ->
ListOption(
titleText = value
)
}
var selection by remember { mutableStateOf<String?>(null) }
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
onSelected(selection)
}, onCloseRequest = {
dismiss()
}), header = Header.Default(
title = stringResource(R.string.select_kmi),
), selection = ListSelection.Single(
showRadioButtons = true,
options = options,
) { _, option ->
selection = option.titleText
})
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
onBack: () -> Unit = {},
onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar(
title = { Text(stringResource(R.string.install)) }, navigationIcon = {
IconButton(
onClick = onBack
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
}, actions = {
IconButton(onClick = onLkmUpload) {
Icon(Icons.Filled.FileUpload, contentDescription = null)
}
},
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
)
}
@Composable
@Preview
fun SelectInstallPreview() {
InstallScreen(EmptyDestinationsNavigator)
}

View File

@@ -1,192 +0,0 @@
package me.weishu.kernelsu.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun SuperUserScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<SuperUserViewModel>()
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val listState = rememberLazyListState()
LaunchedEffect(key1 = navigator) {
viewModel.search = ""
if (viewModel.appList.isEmpty()) {
viewModel.fetchAppList()
}
}
LaunchedEffect(viewModel.search) {
if (viewModel.search.isEmpty()) {
listState.scrollToItem(0)
}
}
Scaffold(
topBar = {
SearchAppBar(
title = { Text(stringResource(R.string.superuser)) },
searchText = viewModel.search,
onSearchTextChange = { viewModel.search = it },
onClearClick = { viewModel.search = "" },
dropdownContent = {
var showDropdown by remember { mutableStateOf(false) }
IconButton(
onClick = { showDropdown = true },
) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(id = R.string.settings)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
DropdownMenuItem(text = {
Text(stringResource(R.string.refresh))
}, onClick = {
scope.launch {
viewModel.fetchAppList()
}
showDropdown = false
})
DropdownMenuItem(text = {
Text(
if (viewModel.showSystemApps) {
stringResource(R.string.hide_system_apps)
} else {
stringResource(R.string.show_system_apps)
}
)
}, onClick = {
viewModel.showSystemApps = !viewModel.showSystemApps
showDropdown = false
})
}
}
},
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding ->
PullToRefreshBox(
modifier = Modifier.padding(innerPadding),
onRefresh = {
scope.launch { viewModel.fetchAppList() }
},
isRefreshing = viewModel.isRefreshing
) {
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
AppItem(app) {
navigator.navigate(AppProfileScreenDestination(app))
}
}
}
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AppItem(
app: SuperUserViewModel.AppInfo,
onClickListener: () -> Unit,
) {
ListItem(
modifier = Modifier.clickable(onClick = onClickListener),
headlineContent = { Text(app.label) },
supportingContent = {
Column {
Text(app.packageName)
FlowRow {
if (app.allowSu) {
LabelText(label = "ROOT")
} else {
if (Natives.uidShouldUmount(app.uid)) {
LabelText(label = "UMOUNT")
}
}
if (app.hasCustomProfile) {
LabelText(label = "CUSTOM")
}
}
}
},
leadingContent = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo)
.crossfade(true)
.build(),
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.width(48.dp)
.height(48.dp)
)
},
)
}
@Composable
fun LabelText(label: String) {
Box(
modifier = Modifier
.padding(top = 4.dp, end = 4.dp)
.background(
Color.Black,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = label,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
style = TextStyle(
fontSize = 8.sp,
color = Color.White,
)
)
}
}

View File

@@ -1,10 +0,0 @@
package me.weishu.kernelsu.ui.theme
import androidx.compose.ui.graphics.Color
val YELLOW = Color(0xFFeed502)
val YELLOW_LIGHT = Color(0xFFffff52)
val SECONDARY_LIGHT = Color(0xffa9817f)
val YELLOW_DARK = Color(0xFFb7a400)
val SECONDARY_DARK = Color(0xFF4c2b2b)

View File

@@ -1,46 +0,0 @@
package me.weishu.kernelsu.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = YELLOW,
secondary = YELLOW_DARK,
tertiary = SECONDARY_DARK
)
private val LightColorScheme = lightColorScheme(
primary = YELLOW,
secondary = YELLOW_LIGHT,
tertiary = SECONDARY_LIGHT
)
@Composable
fun KernelSUTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -1,7 +0,0 @@
package me.weishu.kernelsu.ui.util.module
data class LatestVersionInfo(
val versionCode : Int = 0,
val downloadUrl : String = "",
val changelog : String = ""
)

View File

@@ -1,22 +1,16 @@
package me.weishu.kernelsu package shirkneko.zako.sukisu
import android.app.Application import android.app.Application
import android.system.Os
import coil.Coil import coil.Coil
import coil.ImageLoader import coil.ImageLoader
import me.zhanghai.android.appiconloader.coil.AppIconFetcher import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import okhttp3.Cache
import okhttp3.OkHttpClient
import java.io.File import java.io.File
import java.util.Locale
lateinit var ksuApp: KernelSUApplication lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() { class KernelSUApplication : Application() {
lateinit var okhttpClient: OkHttpClient
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ksuApp = this ksuApp = this
@@ -36,20 +30,7 @@ class KernelSUApplication : Application() {
if (!webroot.exists()) { if (!webroot.exists()) {
webroot.mkdir() webroot.mkdir()
} }
// Provide working env for rust's temp_dir()
Os.setenv("TMPDIR", cacheDir.absolutePath, true)
okhttpClient =
OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
.addInterceptor { block ->
block.proceed(
block.request().newBuilder()
.header("User-Agent", "KernelSU/${BuildConfig.VERSION_CODE}")
.header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
)
}.build()
} }
} }

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu package shirkneko.zako.sukisu
import android.system.Os import android.system.Os

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu package shirkneko.zako.sukisu
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.Keep import androidx.annotation.Keep
@@ -30,7 +30,7 @@ object Natives {
const val ROOT_GID = 0 const val ROOT_GID = 0
init { init {
System.loadLibrary("kernelsu") System.loadLibrary("zako")
} }
// become root manager, return true if success. // become root manager, return true if success.

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.profile package shirkneko.zako.sukisu.profile
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.profile package shirkneko.zako.sukisu.profile
/** /**
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h * https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui; package shirkneko.zako.sukisu.ui;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import me.weishu.kernelsu.IKsuInterface; import shirkneko.zako.sukisu.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice; import rikka.parcelablelist.ParcelableListSlice;
/** /**

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui package shirkneko.zako.sukisu.ui
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.union
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -29,8 +30,9 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
@@ -39,13 +41,16 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty
import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.ksuApp
import me.weishu.kernelsu.ui.screen.BottomBarDestination import shirkneko.zako.sukisu.ui.screen.BottomBarDestination
import me.weishu.kernelsu.ui.theme.KernelSUTheme import shirkneko.zako.sukisu.ui.theme.CardConfig
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import shirkneko.zako.sukisu.ui.theme.KernelSUTheme
import me.weishu.kernelsu.ui.util.rootAvailable import shirkneko.zako.sukisu.ui.theme.loadCustomBackground
import me.weishu.kernelsu.ui.util.install import shirkneko.zako.sukisu.ui.theme.loadThemeMode
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.rootAvailable
import shirkneko.zako.sukisu.ui.util.install
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -59,8 +64,14 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val isManager = Natives.becomeManager(ksuApp.packageName) // 加载保存的背景设置
if (isManager) install() loadCustomBackground()
loadThemeMode()
CardConfig.load(applicationContext)
val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) install()
setContent { setContent {
KernelSUTheme { KernelSUTheme {
@@ -96,8 +107,16 @@ private fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator() val navigator = navController.rememberDestinationsNavigator()
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.becomeManager(ksuApp.packageName)
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable() val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
NavigationBar( NavigationBar(
tonalElevation = 8.dp, tonalElevation = cardElevation, // 动态设置阴影
containerColor = cardColor.copy(alpha = cardAlpha), // 动态设置颜色和透明度
contentColor = if (cardColor.luminance() > 0.5) Color.Black else Color.White, // 根据背景亮度设置文字颜色
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only( windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
) )
@@ -127,8 +146,12 @@ private fun BottomBar(navController: NavHostController) {
} }
}, },
label = { Text(stringResource(destination.label)) }, label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false alwaysShowLabel = false,
colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
selectedTextColor = Color.Black,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
) )
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component package shirkneko.zako.sukisu.ui.component
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import me.weishu.kernelsu.BuildConfig import shirkneko.zako.sukisu.BuildConfig
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
@Preview @Preview
@Composable @Composable
@@ -72,7 +72,7 @@ private fun AboutCardContent() {
shape = CircleShape shape = CircleShape
) { ) {
Image( Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground), painter = painterResource(id = R.drawable.ic_launcher_monochrome),
contentDescription = "icon", contentDescription = "icon",
modifier = Modifier.scale(1.4f) modifier = Modifier.scale(1.4f)
) )
@@ -98,8 +98,8 @@ private fun AboutCardContent() {
val annotatedString = AnnotatedString.Companion.fromHtml( val annotatedString = AnnotatedString.Companion.fromHtml(
htmlString = stringResource( htmlString = stringResource(
id = R.string.about_source_code, id = R.string.about_source_code,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>", "<b><a href=\"https://github.com/ShirkNeko/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>" "<b><a href=\"https://t.me/SukiKSU\">Telegram</a></b>"
), ),
linkStyles = TextLinkStyles( linkStyles = TextLinkStyles(
style = SpanStyle( style = SpanStyle(

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component package shirkneko.zako.sukisu.ui.component
import android.graphics.text.LineBreaker import android.graphics.text.LineBreaker
import android.os.Build import android.os.Build
@@ -88,6 +88,7 @@ interface ConfirmDialogHandle : DialogHandle {
) )
suspend fun awaitConfirm( suspend fun awaitConfirm(
title: String, title: String,
content: String, content: String,
markdown: Boolean = false, markdown: Boolean = false,

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component package shirkneko.zako.sukisu.ui.component
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component package shirkneko.zako.sukisu.ui.component
import android.util.Log import android.util.Log
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@@ -20,9 +20,11 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@@ -40,6 +42,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import shirkneko.zako.sukisu.ui.theme.CardConfig
private const val TAG = "SearchBar" private const val TAG = "SearchBar"
@@ -59,6 +62,11 @@ fun SearchAppBar(
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
var onSearch by remember { mutableStateOf(false) } var onSearch by remember { mutableStateOf(false) }
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
if (onSearch) { if (onSearch) {
LaunchedEffect(Unit) { focusRequester.requestFocus() } LaunchedEffect(Unit) { focusRequester.requestFocus() }
} }
@@ -140,7 +148,11 @@ fun SearchAppBar(
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
)
) )
} }
@@ -155,4 +167,4 @@ private fun SearchAppBarPreview() {
onSearchTextChange = { searchText = it }, onSearchTextChange = { searchText = it },
onClearClick = { searchText = "" } onClearClick = { searchText = "" }
) )
} }

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component package shirkneko.zako.sukisu.ui.component
import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component.profile package shirkneko.zako.sukisu.ui.component.profile
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.SwitchItem import shirkneko.zako.sukisu.ui.component.SwitchItem
@Composable @Composable
fun AppProfileConfig( fun AppProfileConfig(

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component.profile package shirkneko.zako.sukisu.ui.component.profile
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -9,17 +9,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.AssistChip import androidx.compose.material3.AssistChip
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.OutlinedTextFieldDefaults
@@ -49,12 +42,12 @@ import com.maxkeppeler.sheets.input.models.ValidationResult
import com.maxkeppeler.sheets.list.ListDialog import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection import com.maxkeppeler.sheets.list.models.ListSelection
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.profile.Capabilities import shirkneko.zako.sukisu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups import shirkneko.zako.sukisu.profile.Groups
import me.weishu.kernelsu.ui.component.rememberCustomDialog import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.util.isSepolicyValid import shirkneko.zako.sukisu.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component.profile package shirkneko.zako.sukisu.ui.component.profile
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.util.listAppProfileTemplates import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import me.weishu.kernelsu.ui.util.setSepolicy import shirkneko.zako.sukisu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
@@ -64,20 +64,20 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.SwitchItem import shirkneko.zako.sukisu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.component.profile.TemplateConfig import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.forceStopApp import shirkneko.zako.sukisu.ui.util.forceStopApp
import me.weishu.kernelsu.ui.util.getSepolicy import shirkneko.zako.sukisu.ui.util.getSepolicy
import me.weishu.kernelsu.ui.util.launchApp import shirkneko.zako.sukisu.ui.util.launchApp
import me.weishu.kernelsu.ui.util.restartApp import shirkneko.zako.sukisu.ui.util.restartApp
import me.weishu.kernelsu.ui.util.setSepolicy import shirkneko.zako.sukisu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -8,8 +8,9 @@ import androidx.compose.ui.graphics.vector.ImageVector
import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination
import com.ramcosta.composedestinations.generated.destinations.ModuleScreenDestination import com.ramcosta.composedestinations.generated.destinations.ModuleScreenDestination
import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
enum class BottomBarDestination( enum class BottomBarDestination(
val direction: DirectionDestinationSpec, val direction: DirectionDestinationSpec,
@@ -20,5 +21,6 @@ enum class BottomBarDestination(
) { ) {
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false), Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true), SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true) Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true),
Settings(SettingScreenDestination, R.string.settings, Icons.Filled.Settings, Icons.Outlined.Settings, false),
} }

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.os.Environment import android.os.Environment
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.runModuleAction import shirkneko.zako.sukisu.ui.util.runModuleAction
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date

View File

@@ -1,40 +1,18 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.*
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.runtime.*
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
@@ -52,25 +30,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.FlashResult import shirkneko.zako.sukisu.ui.util.*
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.flashModule
import me.weishu.kernelsu.ui.util.installBoot
import me.weishu.kernelsu.ui.util.reboot
import me.weishu.kernelsu.ui.util.restoreBoot
import me.weishu.kernelsu.ui.util.uninstallPermanently
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.*
import java.util.Locale
/**
* @author weishu
* @date 2023/1/1.
*/
enum class FlashingStatus { enum class FlashingStatus {
FLASHING, FLASHING,
@@ -78,27 +43,20 @@ enum class FlashingStatus {
FAILED FAILED
} }
// Lets you flash modules sequentially when mutiple zipUris are selected private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
fun flashModulesSequentially(
uris: List<Uri>, fun getFlashingStatus(): FlashingStatus {
onStdout: (String) -> Unit, return currentFlashingStatus.value
onStderr: (String) -> Unit }
): FlashResult {
for (uri in uris) { fun setFlashingStatus(status: FlashingStatus) {
flashModule(uri, onStdout, onStderr).apply { currentFlashingStatus.value = status
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() }
@@ -108,18 +66,27 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
var flashing by rememberSaveable {
mutableStateOf(FlashingStatus.FLASHING)
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
return@LaunchedEffect return@LaunchedEffect
} }
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
flashIt(flashIt, onStdout = { setFlashingStatus(FlashingStatus.FLASHING)
flashIt(flashIt, onFinish = { showReboot, code ->
if (code != 0) {
text += "Error: exit code = $code.\nPlease save and check the log.\n"
setFlashingStatus(FlashingStatus.FAILED)
} else {
setFlashingStatus(FlashingStatus.SUCCESS)
}
if (showReboot) {
text += "\n\n\n"
showFloatAction = true
}
}, onStdout = {
tempText = "$it\n" tempText = "$it\n"
if (tempText.startsWith("")) { // clear command if (tempText.startsWith("[H[J")) { // clear command
text = tempText.substring(6) text = tempText.substring(6)
} else { } else {
text += tempText text += tempText
@@ -127,24 +94,15 @@ 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
}
} }
} }
Scaffold( Scaffold(
topBar = { topBar = {
TopBar( TopBar(
flashing, currentFlashingStatus.value,
onBack = dropUnlessResumed { onBack = dropUnlessResumed {
navigator.popBackStack() navigator.popBackStack()
}, },
onSave = { onSave = {
@@ -207,37 +165,30 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
@Parcelize @Parcelize
sealed class FlashIt : Parcelable { sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt()
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, flashIt: FlashIt,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit, onStdout: (String) -> Unit,
onStderr: (String) -> Unit onStderr: (String) -> Unit
): FlashResult { ) {
return when (flashIt) { 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.FlashModules -> { FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr)
flashModulesSequentially(flashIt.uris, onStdout, onStderr) FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr)
}
FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr)
FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr)
} }
} }
@@ -281,6 +232,6 @@ private fun TopBar(
@Preview @Preview
@Composable @Composable
fun InstallPreview() { fun FlashScreenPreview() {
InstallScreen(EmptyDestinationsNavigator) FlashScreen(EmptyDestinationsNavigator, FlashIt.FlashUninstall)
} }

View File

@@ -1,9 +1,10 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import android.system.Os import android.system.Os
import android.util.Log
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -11,20 +12,15 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Archive import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.outlined.*
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.*
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -36,39 +32,59 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.weishu.kernelsu.* import shirkneko.zako.sukisu.*
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.rememberConfirmDialog import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.util.* import shirkneko.zako.sukisu.ui.util.*
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.saveable.rememberSaveable
import shirkneko.zako.sukisu.ui.theme.CardConfig
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>(start = true) @Destination<RootGraph>(start = true)
@Composable @Composable
fun HomeScreen(navigator: DestinationsNavigator) { fun HomeScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current
var isSimpleMode by rememberSaveable { mutableStateOf(false) }
// 从 SharedPreferences 加载简洁模式状态
LaunchedEffect(Unit) {
isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false)
}
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
topBar = { topBar = {
TopBar( TopBar(
kernelVersion, kernelVersion,
onSettingsClick = { onInstallClick = { navigator.navigate(InstallScreenDestination) },
navigator.navigate(SettingScreenDestination) onSettingsClick = { navigator.navigate(SettingScreenDestination) },
},
onInstallClick = {
navigator.navigate(InstallScreenDestination)
},
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
}, },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
)
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(top = 12.dp)
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
@@ -99,9 +115,42 @@ fun HomeScreen(navigator: DestinationsNavigator) {
if (checkUpdate) { if (checkUpdate) {
UpdateCard() UpdateCard()
} }
val prefs = remember { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) }
var clickCount by rememberSaveable { mutableStateOf(prefs.getInt("click_count", 0)) }
if (!isSimpleMode && clickCount < 3) {
AnimatedVisibility(
visible = clickCount < 3,
exit = shrinkVertically() + fadeOut()
) {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
clickCount++
prefs.edit().putInt("click_count", clickCount).apply()
}
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.using_mksu_manager),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
InfoCard() InfoCard()
DonateCard() if (!isSimpleMode) {
LearnMoreCard() DonateCard()
LearnMoreCard()
}
Spacer(Modifier) Spacer(Modifier)
} }
} }
@@ -122,6 +171,11 @@ fun UpdateCard() {
val newVersionUrl = newVersion.downloadUrl val newVersionUrl = newVersion.downloadUrl
val changelog = newVersion.changelog val changelog = newVersion.changelog
Log.d("UpdateCard", "Current version code: $currentVersionCode")
Log.d("UpdateCard", "New version code: $newVersionCode")
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val title = stringResource(id = R.string.module_changelog) val title = stringResource(id = R.string.module_changelog)
val updateText = stringResource(id = R.string.module_update) val updateText = stringResource(id = R.string.module_update)
@@ -167,30 +221,27 @@ private fun TopBar(
onSettingsClick: () -> Unit, onSettingsClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.app_name)) }, title = { Text(stringResource(R.string.app_name)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
actions = { actions = {
if (kernelVersion.isGKI()) { if (kernelVersion.isGKI()) {
IconButton(onClick = onInstallClick) { IconButton(onClick = onInstallClick) {
Icon( Icon(Icons.Filled.Archive, stringResource(R.string.install))
imageVector = Icons.Filled.Archive,
contentDescription = stringResource(id = R.string.install)
)
} }
} }
var showDropdown by remember { mutableStateOf(false) } var showDropdown by remember { mutableStateOf(false) }
IconButton(onClick = { IconButton(onClick = { showDropdown = true }) {
showDropdown = true Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
}) { DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
Icon( ) {
imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
RebootDropdownItem(id = R.string.reboot) RebootDropdownItem(id = R.string.reboot)
@@ -205,13 +256,6 @@ private fun TopBar(
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl") RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
} }
} }
IconButton(onClick = onSettingsClick) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = stringResource(id = R.string.settings)
)
}
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
@@ -226,10 +270,8 @@ private fun StatusCard(
onClickInstall: () -> Unit = {} onClickInstall: () -> Unit = {}
) { ) {
ElevatedCard( ElevatedCard(
colors = CardDefaults.elevatedCardColors(containerColor = run { colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
else MaterialTheme.colorScheme.errorContainer
})
) { ) {
Row(modifier = Modifier Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -247,7 +289,7 @@ private fun StatusCard(
} }
val workingMode = when (lkmMode) { val workingMode = when (lkmMode) {
null -> "" null -> " <Non-GKI>"
true -> " <LKM>" true -> " <LKM>"
else -> " <GKI>" else -> " <GKI>"
} }
@@ -277,6 +319,19 @@ private fun StatusCard(
text = stringResource(R.string.home_module_count, getModuleCount()), text = stringResource(R.string.home_module_count, getModuleCount()),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
Spacer(modifier = Modifier.height(4.dp))
val suSFS = getSuSFS()
val translatedStatus = when (suSFS) {
"Supported" -> stringResource(R.string.status_supported)
"Not Supported" -> stringResource(R.string.status_not_supported)
else -> stringResource(R.string.status_unknown)
}
Text(
text = stringResource(R.string.home_susfs, translatedStatus),
style = MaterialTheme.typography.bodyMedium
)
} }
} }
@@ -319,9 +374,8 @@ fun WarningCard(
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null
) { ) {
ElevatedCard( ElevatedCard(
colors = CardDefaults.elevatedCardColors( colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
containerColor = color elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -341,7 +395,10 @@ fun LearnMoreCard() {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val url = stringResource(R.string.home_learn_kernelsu_url) val url = stringResource(R.string.home_learn_kernelsu_url)
ElevatedCard { ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -368,7 +425,10 @@ fun LearnMoreCard() {
fun DonateCard() { fun DonateCard() {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
ElevatedCard { ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -394,8 +454,13 @@ fun DonateCard() {
@Composable @Composable
private fun InfoCard() { private fun InfoCard() {
val context = LocalContext.current val context = LocalContext.current
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false)
ElevatedCard { ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -405,26 +470,77 @@ private fun InfoCard() {
val uname = Os.uname() val uname = Os.uname()
@Composable @Composable
fun InfoCardItem(label: String, content: String) { fun InfoCardItem(
label: String,
content: String,
) {
contents.appendLine(label).appendLine(content).appendLine() contents.appendLine(label).appendLine(content).appendLine()
Text(text = label, style = MaterialTheme.typography.bodyLarge) Text(text = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium) Text(text = content, style = MaterialTheme.typography.bodyMedium)
} }
InfoCardItem(stringResource(R.string.home_kernel), uname.release) InfoCardItem(stringResource(R.string.home_kernel), uname.release)
Spacer(Modifier.height(16.dp)) if (!isSimpleMode) {
val managerVersion = getManagerVersion(context) Spacer(Modifier.height(16.dp))
InfoCardItem( val androidVersion = Build.VERSION.RELEASE
stringResource(R.string.home_manager_version), InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
"${managerVersion.first} (${managerVersion.second})" }
)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus()) val deviceModel = Build.MODEL
InfoCardItem(stringResource(R.string.home_device_model), deviceModel)
Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context)
InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
if (!isSimpleMode) {
Spacer(modifier = Modifier.height(16.dp))
val suSFS = getSuSFS()
if (suSFS == "Supported") {
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
} else {
val susSUMode = try {
susfsSUS_SU_Mode()
} catch (e: Exception) {
0
}
if (susSUMode == 2 || susSUMode == 0) {
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val susSUModeLabel = stringResource(R.string.sus_su_mode)
val susSUModeValue = susSUMode.toString()
val susSUModeText = if (isSUS_SU) " $susSUModeLabel $susSUModeValue" else ""
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${getSuSFSVariant()})$susSUModeText"
)
} else {
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
}
}
}
} }
} }
} }

View File

@@ -0,0 +1,571 @@
package shirkneko.zako.sukisu.ui.screen
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FileUpload
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.utils.AssetsUtil
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
/**
* @author weishu
* @date 2024/3/12.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
val context = LocalContext.current
var showRebootDialog by remember { mutableStateOf(false) }
val onFlashComplete = {
showRebootDialog = true
}
if (showRebootDialog) {
RebootDialog(
show = true,
onDismiss = { showRebootDialog = false },
onConfirm = {
showRebootDialog = false
try {
val process = Runtime.getRuntime().exec("su")
process.outputStream.bufferedWriter().use { writer ->
writer.write("svc power reboot\n")
writer.write("exit\n")
}
} catch (e: Exception) {
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
}
}
)
}
val onInstall = {
installMethod?.let { method ->
when (method) {
is InstallMethod.HorizonKernel -> {
method.uri?.let { uri ->
val worker = HorizonKernelWorker(context)
worker.uri = uri
worker.setOnFlashCompleteListener(onFlashComplete)
worker.start()
}
}
else -> {
val flashIt = FlashIt.FlashBoot(
boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot
)
navigator.navigate(FlashScreenDestination(flashIt))
}
}
}
Unit
}
val currentKmi by produceState(initialValue = "") {
value = getCurrentKmi()
}
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
kmi?.let {
lkmSelection = LkmSelection.KmiString(it)
onInstall()
}
}
val onClickNext = {
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
selectKmiDialog.show()
} else {
onInstall()
}
Unit
}
val selectLkmLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
lkmSelection = LkmSelection.LkmUri(uri)
}
}
}
val onLkmUpload = {
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopBar(
onBack = { navigator.popBackStack() },
onLkmUpload = onLkmUpload,
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
SelectInstallMethod { method ->
installMethod = method
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text(
stringResource(
id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)"
)
)
}
Button(
modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
onClick = onClickNext
) {
Text(
stringResource(id = R.string.install_next),
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
}
}
}
}
}
private fun launchHorizonKernelFlash(context: Context, uri: Uri) {
val worker = HorizonKernelWorker(context)
worker.uri = uri
worker.setOnFlashCompleteListener {
}
worker.start()
}
@Composable
private fun RebootDialog(
show: Boolean,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
if (show) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(id = R.string.reboot_complete_title)) },
text = { Text(stringResource(id = R.string.reboot_complete_msg)) },
confirmButton = {
TextButton(onClick = onConfirm) {
Text(stringResource(id = R.string.yes))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(id = R.string.no))
}
}
)
}
}
private class HorizonKernelWorker(private val context: Context) : Thread() {
var uri: Uri? = null
private lateinit var filePath: String
private lateinit var binaryPath: String
private var onFlashComplete: (() -> Unit)? = null
fun setOnFlashCompleteListener(listener: () -> Unit) {
onFlashComplete = listener
}
override fun run() {
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
try {
cleanup()
if (!rootAvailable()) {
showError(context.getString(R.string.root_required))
return
}
copy()
if (!File(filePath).exists()) {
showError(context.getString(R.string.copy_failed))
return
}
getBinary()
patch()
flash()
(context as? Activity)?.runOnUiThread {
onFlashComplete?.invoke()
}
} catch (e: Exception) {
showError(e.message ?: context.getString(R.string.unknown_error))
}
}
private fun cleanup() {
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
}
private fun copy() {
uri?.let { safeUri ->
context.contentResolver.openInputStream(safeUri)?.use { input ->
FileOutputStream(File(filePath)).use { output ->
input.copyTo(output)
}
}
}
}
private fun getBinary() {
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
if (!File(binaryPath).exists()) {
throw IOException("Failed to extract update-binary")
}
}
private fun patch() {
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
}
private fun flash() {
val process = ProcessBuilder("su")
.redirectErrorStream(true)
.start()
try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
if (line.startsWith("ui_print")) {
showLog(line.removePrefix("ui_print"))
}
}
}
} finally {
process.destroy()
}
if (!File("${context.filesDir.absolutePath}/done").exists()) {
throw IOException("Flash failed")
}
}
private fun runCommand(su: Boolean, cmd: String): Int {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.waitFor()
} finally {
process.destroy()
}
}
private fun showError(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
private fun showLog(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
}
sealed class InstallMethod {
data class SelectFile(
val uri: Uri? = null,
@StringRes override val label: Int = R.string.select_file,
override val summary: String?
) : InstallMethod()
data object DirectInstall : InstallMethod() {
override val label: Int
get() = R.string.direct_install
}
data object DirectInstallToInactiveSlot : InstallMethod() {
override val label: Int
get() = R.string.install_inactive_slot
}
data class HorizonKernel(
val uri: Uri? = null,
@StringRes override val label: Int = R.string.horizon_kernel,
override val summary: String? = null
) : InstallMethod()
abstract val label: Int
open val summary: String? = null
}
@Composable
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val selectFileTip = stringResource(
id = R.string.select_file_tip,
if (isInitBoot()) "init_boot" else "boot"
)
val radioOptions = mutableListOf<InstallMethod>(
InstallMethod.SelectFile(summary = selectFileTip)
)
if (rootAvailable) {
radioOptions.add(InstallMethod.DirectInstall)
if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
}
radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
}
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
var currentSelectingMethod by remember { mutableStateOf<InstallMethod?>(null) }
val selectImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
val option = when (currentSelectingMethod) {
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel")
else -> null
}
option?.let {
selectedOption = it
onSelected(it)
}
}
}
}
val confirmDialog = rememberConfirmDialog(
onConfirm = {
selectedOption = InstallMethod.DirectInstallToInactiveSlot
onSelected(InstallMethod.DirectInstallToInactiveSlot)
},
onDismiss = null
)
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
val onClick = { option: InstallMethod ->
currentSelectingMethod = option
when (option) {
is InstallMethod.SelectFile, is InstallMethod.HorizonKernel -> {
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/octet-stream", "application/zip"))
})
}
is InstallMethod.DirectInstall -> {
selectedOption = option
onSelected(option)
}
is InstallMethod.DirectInstallToInactiveSlot -> {
confirmDialog.showConfirm(dialogTitle, dialogContent)
}
}
}
Column {
radioOptions.forEach { option ->
val interactionSource = remember { MutableInteractionSource() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.toggleable(
value = option.javaClass == selectedOption?.javaClass,
onValueChange = { onClick(option) },
role = Role.RadioButton,
indication = LocalIndication.current,
interactionSource = interactionSource
)
) {
RadioButton(
selected = option.javaClass == selectedOption?.javaClass,
onClick = { onClick(option) },
interactionSource = interactionSource
)
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
Text(
text = stringResource(id = option.label),
fontSize = MaterialTheme.typography.titleMedium.fontSize,
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
fontStyle = MaterialTheme.typography.titleMedium.fontStyle
)
option.summary?.let {
Text(
text = it,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
fontStyle = MaterialTheme.typography.bodySmall.fontStyle
)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
return rememberCustomDialog { dismiss ->
val supportedKmi by produceState(initialValue = emptyList<String>()) {
value = getSupportedKmis()
}
val options = supportedKmi.map { value ->
ListOption(titleText = value)
}
var selection by remember { mutableStateOf<String?>(null) }
ListDialog(
state = rememberUseCaseState(
visible = true,
onFinishedRequest = {
onSelected(selection)
},
onCloseRequest = {
dismiss()
}
),
header = Header.Default(
title = stringResource(R.string.select_kmi),
),
selection = ListSelection.Single(
showRadioButtons = true,
options = options,
) { _, option ->
selection = option.titleText
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
onBack: () -> Unit = {},
onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar(
title = { Text(stringResource(R.string.install)) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
actions = {
IconButton(onClick = onLkmUpload) {
Icon(Icons.Filled.FileUpload, contentDescription = null)
}
},
windowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
),
scrollBehavior = scrollBehavior
)
}
@Preview
@Composable
fun SelectInstallPreview() {
InstallScreen(EmptyDestinationsNavigator)
}

View File

@@ -1,14 +1,13 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.app.Activity.RESULT_OK import android.app.Activity.*
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -24,22 +23,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.selection.toggleable
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Wysiwyg import androidx.compose.material.icons.automirrored.outlined.*
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.*
import androidx.compose.material.icons.outlined.PlayArrow
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
@@ -58,7 +53,6 @@ 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
@@ -74,7 +68,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -94,24 +88,30 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.ConfirmResult import shirkneko.zako.sukisu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.component.SearchAppBar import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberConfirmDialog import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog import shirkneko.zako.sukisu.ui.util.DownloadListener
import me.weishu.kernelsu.ui.util.DownloadListener import shirkneko.zako.sukisu.ui.util.*
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import shirkneko.zako.sukisu.ui.util.download
import me.weishu.kernelsu.ui.util.download import shirkneko.zako.sukisu.ui.util.hasMagisk
import me.weishu.kernelsu.ui.util.hasMagisk import shirkneko.zako.sukisu.ui.util.reboot
import me.weishu.kernelsu.ui.util.reboot import shirkneko.zako.sukisu.ui.util.restoreModule
import me.weishu.kernelsu.ui.util.restoreModule import shirkneko.zako.sukisu.ui.util.toggleModule
import me.weishu.kernelsu.ui.util.toggleModule import shirkneko.zako.sukisu.ui.util.uninstallModule
import me.weishu.kernelsu.ui.util.uninstallModule import shirkneko.zako.sukisu.ui.webui.WebUIActivity
import me.weishu.kernelsu.ui.util.getFileName import okhttp3.OkHttpClient
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel import shirkneko.zako.sukisu.ui.util.ModuleModify
import me.weishu.kernelsu.ui.webui.WebUIActivity import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import shirkneko.zako.sukisu.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.zip.ZipInputStream
import androidx.compose.ui.graphics.Color
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@@ -121,6 +121,122 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current val context = LocalContext.current
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val confirmDialog = rememberConfirmDialog()
val buttonTextColor = androidx.compose.ui.graphics.Color.Black
val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
}
val data = it.data ?: return@rememberLauncherForActivityResult
scope.launch {
val clipData = data.clipData
if (clipData != null) {
// 处理多选结果
val selectedModules = mutableSetOf<Uri>()
val selectedModuleNames = mutableMapOf<Uri, String>()
suspend fun processUri(uri: Uri) {
val moduleName = withContext(Dispatchers.IO) {
try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
var entry = zipInputStream.nextEntry
var name = context.getString(R.string.unknown_module)
while (entry != null) {
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line?.substringAfter("=") ?: name
break
}
}
break
}
entry = zipInputStream.nextEntry
}
name
} catch (e: Exception) {
context.getString(R.string.unknown_module)
}
}
selectedModules.add(uri)
selectedModuleNames[uri] = moduleName
}
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
processUri(uri)
}
// 显示确认对话框
val modulesList = selectedModuleNames.values.joinToString("\n", "")
val confirmResult = confirmDialog.awaitConfirm(
title = context.getString(R.string.module_install),
content = context.getString(R.string.module_install_multiple_confirm_with_names, selectedModules.size, modulesList),
confirm = context.getString(R.string.install),
dismiss = context.getString(R.string.cancel)
)
if (confirmResult == ConfirmResult.Confirmed) {
// 批量安装模块
selectedModules.forEach { uri ->
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
}
viewModel.markNeedRefresh()
}
} else {
// 单个文件安装逻辑
val uri = data.data ?: return@launch
val moduleName = withContext(Dispatchers.IO) {
try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
var entry = zipInputStream.nextEntry
var name = context.getString(R.string.unknown_module)
while (entry != null) {
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line?.substringAfter("=") ?: name
break
}
}
break
}
entry = zipInputStream.nextEntry
}
name
} catch (e: Exception) {
context.getString(R.string.unknown_module)
}
}
val confirmResult = confirmDialog.awaitConfirm(
title = context.getString(R.string.module_install),
content = context.getString(R.string.module_install_confirm, moduleName),
confirm = context.getString(R.string.install),
dismiss = context.getString(R.string.cancel)
)
if (confirmResult == ConfirmResult.Confirmed) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
viewModel.markNeedRefresh()
}
}
}
}
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -160,43 +276,62 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contentDescription = stringResource(id = R.string.settings) contentDescription = stringResource(id = R.string.settings)
) )
DropdownMenu(expanded = showDropdown, onDismissRequest = { DropdownMenu(
showDropdown = false expanded = showDropdown,
}) { onDismissRequest = { showDropdown = false }
DropdownMenuItem(text = { ) {
Text(stringResource(R.string.module_sort_action_first)) DropdownMenuItem(
}, trailingIcon = { text = { Text(stringResource(R.string.module_sort_action_first)) },
Checkbox(viewModel.sortActionFirst, null) trailingIcon = { Checkbox(viewModel.sortActionFirst, null) },
}, onClick = { onClick = {
viewModel.sortActionFirst = viewModel.sortActionFirst = !viewModel.sortActionFirst
!viewModel.sortActionFirst prefs.edit()
prefs.edit() .putBoolean("module_sort_action_first", viewModel.sortActionFirst)
.putBoolean( .apply()
"module_sort_action_first", scope.launch {
viewModel.sortActionFirst viewModel.fetchModuleList()
) }
.apply()
scope.launch {
viewModel.fetchModuleList()
} }
}) )
DropdownMenuItem(text = { DropdownMenuItem(
Text(stringResource(R.string.module_sort_enabled_first)) text = { Text(stringResource(R.string.module_sort_enabled_first)) },
}, trailingIcon = { trailingIcon = { Checkbox(viewModel.sortEnabledFirst, null) },
Checkbox(viewModel.sortEnabledFirst, null) onClick = {
}, onClick = { viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
viewModel.sortEnabledFirst = prefs.edit()
!viewModel.sortEnabledFirst .putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
prefs.edit() .apply()
.putBoolean( scope.launch {
"module_sort_enabled_first", viewModel.fetchModuleList()
viewModel.sortEnabledFirst }
)
.apply()
scope.launch {
viewModel.fetchModuleList()
} }
}) )
DropdownMenuItem(
text = { Text(stringResource(R.string.backup_modules)) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.Download,
contentDescription = "备份"
)
},
onClick = {
showDropdown = false
backupLauncher.launch(ModuleModify.createBackupIntent())
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.restore_modules)) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.Refresh,
contentDescription = "还原"
)
},
onClick = {
showDropdown = false
restoreLauncher.launch(ModuleModify.createRestoreIntent())
}
)
} }
} }
}, },
@@ -206,63 +341,36 @@ 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(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
}
val data = it.data ?: return@rememberLauncherForActivityResult
val clipData = data.clipData
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) }
}
if (uris.size == 1) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(uris.first()))))
} 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 files to install selectZipLauncher.launch(
val intent = Intent(Intent.ACTION_GET_CONTENT).apply { Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip" type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
} }
selectZipLauncher.launch(intent) )
}, },
icon = { Icon(Icons.Filled.Add, moduleInstall) }, icon = {
text = { Text(text = moduleInstall) }, Icon(
imageVector = Icons.Filled.Add,
contentDescription = moduleInstall,
tint = buttonTextColor
)
},
text = {
Text(
text = moduleInstall,
color = buttonTextColor
)
}
) )
} }
}, },
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(
@@ -277,15 +385,14 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) )
} }
} }
else -> { else -> {
ModuleList( ModuleList(
navigator, navigator = navigator,
viewModel = viewModel, viewModel = viewModel,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
boxModifier = Modifier.padding(innerPadding), boxModifier = Modifier.padding(innerPadding),
onInstallModule = { onInstallModule = {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(it)))) navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
}, },
onClickModule = { id, name, hasWebUi -> onClickModule = { id, name, hasWebUi ->
if (hasWebUi) { if (hasWebUi) {
@@ -345,7 +452,7 @@ private fun ModuleList(
val changelogResult = loadingDialog.withLoading { val changelogResult = loadingDialog.withLoading {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { runCatching {
ksuApp.okhttpClient.newCall( OkHttpClient().newCall(
okhttp3.Request.Builder().url(changelogUrl).build() okhttp3.Request.Builder().url(changelogUrl).build()
).execute().body!!.string() ).execute().body!!.string()
} }
@@ -560,7 +667,8 @@ fun ModuleItem(
onClick: (ModuleViewModel.ModuleInfo) -> Unit onClick: (ModuleViewModel.ModuleInfo) -> Unit
) { ) {
ElevatedCard( ElevatedCard(
modifier = Modifier.fillMaxWidth() colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) { ) {
val textDecoration = if (!module.remove) null else TextDecoration.LineThrough val textDecoration = if (!module.remove) null else TextDecoration.LineThrough
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
@@ -617,7 +725,7 @@ fun ModuleItem(
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight, lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
textDecoration = textDecoration textDecoration = textDecoration
) )
} }
@@ -668,7 +776,11 @@ fun ModuleItem(
navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId)) navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId))
viewModel.markNeedRefresh() viewModel.markNeedRefresh()
}, },
contentPadding = ButtonDefaults.TextButtonContentPadding contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) { ) {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@@ -694,7 +806,11 @@ fun ModuleItem(
enabled = !module.remove && module.enabled, enabled = !module.remove && module.enabled,
onClick = { onClick(module) }, onClick = { onClick(module) },
interactionSource = interactionSource, interactionSource = interactionSource,
contentPadding = ButtonDefaults.TextButtonContentPadding contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) { ) {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@@ -720,7 +836,11 @@ fun ModuleItem(
enabled = !module.remove, enabled = !module.remove,
onClick = { onUpdate(module) }, onClick = { onUpdate(module) },
shape = ButtonDefaults.textShape, shape = ButtonDefaults.textShape,
contentPadding = ButtonDefaults.TextButtonContentPadding contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) { ) {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
@@ -743,7 +863,11 @@ fun ModuleItem(
FilledTonalButton( FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp), modifier = Modifier.defaultMinSize(52.dp, 32.dp),
onClick = { onUninstallClicked(module) }, onClick = { onUninstallClicked(module) },
contentPadding = ButtonDefaults.TextButtonContentPadding contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) { ) {
if (!module.remove) { if (!module.remove) {
Icon( Icon(
@@ -756,6 +880,7 @@ fun ModuleItem(
modifier = Modifier.size(20.dp).rotate(180f), modifier = Modifier.size(20.dp).rotate(180f),
imageVector = Icons.Outlined.Refresh, imageVector = Icons.Outlined.Refresh,
contentDescription = null, contentDescription = null,
) )
} }
if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) { if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
@@ -792,3 +917,4 @@ fun ModuleItemPreview() {
) )
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {}) ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
} }

View File

@@ -0,0 +1,470 @@
package shirkneko.zako.sukisu.ui.screen
import android.content.Context
import android.net.Uri
import android.os.Build
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.theme.CardConfig
import shirkneko.zako.sukisu.ui.theme.ThemeColors
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
import shirkneko.zako.sukisu.ui.theme.saveCustomBackground
import shirkneko.zako.sukisu.ui.theme.saveThemeColors
import shirkneko.zako.sukisu.ui.theme.saveThemeMode
import shirkneko.zako.sukisu.ui.theme.saveDynamicColorState
import shirkneko.zako.sukisu.ui.util.getSuSFS
import shirkneko.zako.sukisu.ui.util.getSuSFSFeatures
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_0
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_2
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_Mode
fun saveCardConfig(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
with(prefs.edit()) {
putFloat("card_alpha", CardConfig.cardAlpha)
putBoolean("custom_background_enabled", CardConfig.cardElevation == 0.dp)
apply()
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun MoreSettingsScreen(navigator: DestinationsNavigator) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val context = LocalContext.current
val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
// 主题模式选择
var themeMode by remember {
mutableStateOf(
when(ThemeConfig.forceDarkMode) {
true -> 2 // 深色
false -> 1 // 浅色
null -> 0 // 跟随系统
}
)
}
// 动态颜色开关状态
var useDynamicColor by remember {
mutableStateOf(ThemeConfig.useDynamicColor)
}
var showThemeModeDialog by remember { mutableStateOf(false) }
// 主题模式选项
val themeOptions = listOf(
stringResource(R.string.theme_follow_system),
stringResource(R.string.theme_light),
stringResource(R.string.theme_dark)
)
// 简洁模块开关状态
var isSimpleMode by remember {
mutableStateOf(prefs.getBoolean("is_simple_mode", false))
}
// 更新简洁模块开关状态
val onSimpleModeChange = { newValue: Boolean ->
prefs.edit().putBoolean("is_simple_mode", newValue).apply()
isSimpleMode = newValue
}
// SELinux 状态
var selinuxEnabled by remember {
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
}
// 卡片配置状态
var cardAlpha by rememberSaveable { mutableStateOf(CardConfig.cardAlpha) }
var showCardSettings by remember { mutableStateOf(false) }
var isCustomBackgroundEnabled by rememberSaveable {
mutableStateOf(ThemeConfig.customBackgroundUri != null)
}
// 初始化卡片配置
LaunchedEffect(Unit) {
CardConfig.apply {
cardAlpha = prefs.getFloat("card_alpha", 0.85f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else CardConfig.defaultElevation
}
}
// 主题色选项
val themeColorOptions = listOf(
stringResource(R.string.color_default) to ThemeColors.Default,
stringResource(R.string.color_blue) to ThemeColors.Blue,
stringResource(R.string.color_green) to ThemeColors.Green,
stringResource(R.string.color_purple) to ThemeColors.Purple,
stringResource(R.string.color_orange) to ThemeColors.Orange,
stringResource(R.string.color_pink) to ThemeColors.Pink,
stringResource(R.string.color_gray) to ThemeColors.Gray,
stringResource(R.string.color_ivory) to ThemeColors.Ivory
)
var showThemeColorDialog by remember { mutableStateOf(false) }
// 图片选择器
val pickImageLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
uri?.let {
context.saveCustomBackground(it)
isCustomBackgroundEnabled = true
CardConfig.cardElevation = 0.dp
saveCardConfig(context)
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.more_settings)) },
navigationIcon = {
IconButton(onClick = { navigator.popBackStack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, null)
}
},
scrollBehavior = scrollBehavior
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(top = 12.dp)
) {
// SELinux 开关
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.selinux),
summary = if (selinuxEnabled)
stringResource(R.string.selinux_enabled) else
stringResource(R.string.selinux_disabled),
checked = selinuxEnabled
) { enabled ->
val command = if (enabled) "setenforce 1" else "setenforce 0"
Shell.getShell().newJob().add(command).exec().let { result ->
if (result.isSuccess) selinuxEnabled = enabled
}
}
// 添加简洁模块开关
SwitchItem(
icon = Icons.Filled.FormatPaint,
title = stringResource(R.string.simple_mode),
summary = stringResource(R.string.simple_mode_summary),
checked = isSimpleMode
) {
onSimpleModeChange(it)
}
// region SUSFS 配置(仅在支持时显示)
val suSFS = getSuSFS()
val isSUS_SU = getSuSFSFeatures()
if (suSFS == "Supported") {
if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") {
// 初始化时,默认启用
var isEnabled by rememberSaveable {
mutableStateOf(true) // 默认启用
}
// 在启动时检查状态
LaunchedEffect(Unit) {
// 如果当前模式不是2就强制启用
val currentMode = susfsSUS_SU_Mode()
val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true)
if (currentMode != "2" && wasManuallyDisabled) {
susfsSUS_SU_2() // 强制切换到模式2
prefs.edit().putBoolean("enable_sus_su", true).apply()
}
isEnabled = currentMode == "2"
}
SwitchItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(id = R.string.settings_susfs_toggle),
summary = stringResource(id = R.string.settings_susfs_toggle_summary),
checked = isEnabled
) {
if (it) {
// 手动启用
susfsSUS_SU_2()
prefs.edit().putBoolean("enable_sus_su", true).apply()
} else {
// 手动关闭
susfsSUS_SU_0()
prefs.edit().putBoolean("enable_sus_su", false).apply()
}
isEnabled = it
}
}
}
// endregion
// 动态颜色开关
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
SwitchItem(
icon = Icons.Filled.ColorLens,
title = stringResource(R.string.dynamic_color_title),
summary = stringResource(R.string.dynamic_color_summary),
checked = useDynamicColor
) { enabled ->
useDynamicColor = enabled
context.saveDynamicColorState(enabled)
}
}
// 只在未启用动态颜色时显示主题色选择
if (!useDynamicColor) {
ListItem(
leadingContent = { Icon(Icons.Default.Palette, null) },
headlineContent = { Text("主题颜色") },
supportingContent = {
val currentThemeName = when (ThemeConfig.currentTheme) {
is ThemeColors.Default -> stringResource(R.string.color_default)
is ThemeColors.Blue -> stringResource(R.string.color_blue)
is ThemeColors.Green -> stringResource(R.string.color_green)
is ThemeColors.Purple -> stringResource(R.string.color_purple)
is ThemeColors.Orange -> stringResource(R.string.color_orange)
is ThemeColors.Pink -> stringResource(R.string.color_pink)
is ThemeColors.Gray -> stringResource(R.string.color_gray)
is ThemeColors.Ivory -> stringResource(R.string.color_ivory)
else -> stringResource(R.string.color_default)
}
Text(currentThemeName)
},
modifier = Modifier.clickable { showThemeColorDialog = true }
)
if (showThemeColorDialog) {
AlertDialog(
onDismissRequest = { showThemeColorDialog = false },
title = { Text(stringResource(R.string.choose_theme_color)) },
text = {
Column {
themeColorOptions.forEach { (name, theme) ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
context.saveThemeColors(when (theme) {
ThemeColors.Default -> "default"
ThemeColors.Blue -> "blue"
ThemeColors.Green -> "green"
ThemeColors.Purple -> "purple"
ThemeColors.Orange -> "orange"
ThemeColors.Pink -> "pink"
ThemeColors.Gray -> "gray"
ThemeColors.Ivory -> "ivory"
else -> "default"
})
showThemeColorDialog = false
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = ThemeConfig.currentTheme::class == theme::class,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.size(24.dp)
.background(theme.Primary, shape = CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Text(name)
}
}
}
},
confirmButton = {}
)
}
}
// 自定义背景开关
SwitchItem(
icon = Icons.Filled.Wallpaper,
title = stringResource(id = R.string.settings_custom_background),
summary = stringResource(id = R.string.settings_custom_background_summary),
checked = isCustomBackgroundEnabled
) { isChecked ->
if (isChecked) {
pickImageLauncher.launch("image/*")
} else {
context.saveCustomBackground(null)
isCustomBackgroundEnabled = false
CardConfig.cardElevation = CardConfig.defaultElevation
CardConfig.cardAlpha = 1f
saveCardConfig(context)
}
}
// 卡片管理展开控制
if (ThemeConfig.customBackgroundUri != null) {
ListItem(
leadingContent = { Icon(Icons.Default.ExpandMore, null) },
headlineContent = { Text(stringResource(R.string.settings_card_manage)) },
modifier = Modifier.clickable { showCardSettings = !showCardSettings }
)
if (showCardSettings) {
// 透明度 Slider
ListItem(
leadingContent = { Icon(Icons.Filled.Opacity, null) },
headlineContent = { Text(stringResource(R.string.settings_card_alpha)) },
supportingContent = {
Slider(
value = cardAlpha,
onValueChange = { newValue ->
cardAlpha = newValue
CardConfig.cardAlpha = newValue
prefs.edit().putFloat("card_alpha", newValue).apply()
},
onValueChangeFinished = {
CoroutineScope(Dispatchers.IO).launch {
saveCardConfig(context)
}
},
valueRange = 0f..1f,
// 确保使用自定义颜色
colors = getSliderColors(cardAlpha, useCustomColors = true),
thumb = {
SliderDefaults.Thumb(
interactionSource = remember { MutableInteractionSource() },
thumbSize = DpSize(0.dp, 0.dp)
)
}
)
}
)
ListItem(
leadingContent = { Icon(Icons.Filled.DarkMode, null) },
headlineContent = { Text(stringResource(R.string.theme_mode)) },
supportingContent = { Text(themeOptions[themeMode]) },
modifier = Modifier.clickable {
showThemeModeDialog = true
}
)
// 主题模式选择对话框
if (showThemeModeDialog) {
AlertDialog(
onDismissRequest = { showThemeModeDialog = false },
title = { Text(stringResource(R.string.theme_mode)) },
text = {
Column {
themeOptions.forEachIndexed { index, option ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
themeMode = index
val newThemeMode = when(index) {
0 -> null // 跟随系统
1 -> false // 浅色
2 -> true // 深色
else -> null
}
context.saveThemeMode(newThemeMode)
showThemeModeDialog = false
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = themeMode == index,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(option)
}
}
}
},
confirmButton = {}
)
}
}
}
}
}
}
@Composable
private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors {
val theme = ThemeConfig.currentTheme
val isDarkTheme = ThemeConfig.forceDarkMode ?: isSystemInDarkTheme()
val useDynamicColor = ThemeConfig.useDynamicColor
return when {
// 使用动态颜色时
useDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
SliderDefaults.colors(
activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f),
inactiveTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
thumbColor = MaterialTheme.colorScheme.primary
)
}
// 使用自定义主题色时
useCustomColors -> {
SliderDefaults.colors(
activeTrackColor = theme.getCustomSliderActiveColor(),
inactiveTrackColor = theme.getCustomSliderInactiveColor(),
thumbColor = theme.Primary
)
}
else -> {
val activeColor = if (isDarkTheme) {
theme.Primary.copy(alpha = cardAlpha)
} else {
theme.Primary.copy(alpha = cardAlpha)
}
val inactiveColor = if (isDarkTheme) {
Color.DarkGray.copy(alpha = 0.3f)
} else {
Color.LightGray.copy(alpha = 0.3f)
}
SliderDefaults.colors(
activeTrackColor = activeColor,
inactiveTrackColor = inactiveColor,
thumbColor = activeColor
)
}
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -18,23 +18,10 @@ import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.Compress
import androidx.compose.material.icons.filled.ContactPage
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.DeveloperMode
import androidx.compose.material.icons.filled.Fence
import androidx.compose.material.icons.filled.FolderDelete
import androidx.compose.material.icons.filled.RemoveModerator
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Update
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -62,7 +49,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.compose.dropUnlessResumed
import com.maxkeppeker.sheets.core.models.base.Header import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.IconSource import com.maxkeppeker.sheets.core.models.base.IconSource
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
@@ -73,25 +59,30 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.weishu.kernelsu.BuildConfig import shirkneko.zako.sukisu.BuildConfig
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.AboutDialog import shirkneko.zako.sukisu.ui.component.AboutDialog
import me.weishu.kernelsu.ui.component.ConfirmResult import shirkneko.zako.sukisu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.DialogHandle import shirkneko.zako.sukisu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.SwitchItem import shirkneko.zako.sukisu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.rememberConfirmDialog import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.getBugreportFile import shirkneko.zako.sukisu.ui.util.getBugreportFile
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.MaterialTheme
import shirkneko.zako.sukisu.ui.theme.CardConfig
/** /**
* @author weishu * @author weishu
@@ -101,15 +92,14 @@ import java.time.format.DateTimeFormatter
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun SettingScreen(navigator: DestinationsNavigator) { fun SettingScreen(navigator: DestinationsNavigator) {
// region 界面基础设置
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
// endregion
Scaffold( Scaffold(
topBar = { topBar = {
TopBar( TopBar(
onBack = dropUnlessResumed {
navigator.popBackStack()
},
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
}, },
@@ -121,6 +111,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
val loadingDialog = rememberLoadingDialog() val loadingDialog = rememberLoadingDialog()
val shrinkDialog = rememberConfirmDialog() val shrinkDialog = rememberConfirmDialog()
// endregion
Column( Column(
modifier = Modifier modifier = Modifier
@@ -128,10 +119,12 @@ fun SettingScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
// region 上下文与协程
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// endregion
// region 日志导出功能
val exportBugreportLauncher = rememberLauncherForActivityResult( val exportBugreportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/gzip") ActivityResultContracts.CreateDocument("application/gzip")
) { uri: Uri? -> ) { uri: Uri? ->
@@ -146,8 +139,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
loadingDialog.hide() loadingDialog.hide()
snackBarHost.showSnackbar(context.getString(R.string.log_saved)) snackBarHost.showSnackbar(context.getString(R.string.log_saved))
} }
// endregion
} }
// region 配置项列表
// 配置文件模板入口
val profileTemplate = stringResource(id = R.string.settings_profile_template) val profileTemplate = stringResource(id = R.string.settings_profile_template)
ListItem( ListItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
@@ -157,7 +152,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
navigator.navigate(AppProfileTemplateScreenDestination) navigator.navigate(AppProfileTemplateScreenDestination)
} }
) )
// 卸载模块开关
var umountChecked by rememberSaveable { var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules()) mutableStateOf(Natives.isDefaultUmountModules())
} }
@@ -171,7 +166,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
umountChecked = it umountChecked = it
} }
} }
// SU 禁用开关(仅在兼容版本显示)
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable { var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled()) mutableStateOf(!Natives.isSuEnabled())
@@ -190,6 +185,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
// 更新检查开关
var checkUpdate by rememberSaveable { var checkUpdate by rememberSaveable {
mutableStateOf( mutableStateOf(
prefs.getBoolean("check_update", true) prefs.getBoolean("check_update", true)
@@ -205,6 +202,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
checkUpdate = it checkUpdate = it
} }
// Web调试开关
var enableWebDebugging by rememberSaveable { var enableWebDebugging by rememberSaveable {
mutableStateOf( mutableStateOf(
prefs.getBoolean("enable_web_debugging", false) prefs.getBoolean("enable_web_debugging", false)
@@ -219,6 +217,21 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.edit().putBoolean("enable_web_debugging", it).apply() prefs.edit().putBoolean("enable_web_debugging", it).apply()
enableWebDebugging = it enableWebDebugging = it
} }
// endregion
val newButtonTitle = stringResource(id = R.string.more_settings)
ListItem(
leadingContent = {
Icon(
Icons.Filled.ExpandMore,
contentDescription = newButtonTitle
)
},
headlineContent = { Text(newButtonTitle) },
supportingContent = { Text(stringResource(id = R.string.more_settings)) },
modifier = Modifier.clickable {
navigator.navigate(MoreSettingsScreenDestination)
}
)
var showBottomsheet by remember { mutableStateOf(false) } var showBottomsheet by remember { mutableStateOf(false) }
@@ -458,18 +471,17 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar( private fun TopBar(
onBack: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.settings)) }, title = { Text(stringResource(R.string.settings)) },
navigationIcon = { colors = TopAppBarDefaults.topAppBarColors(
IconButton( containerColor = cardColor.copy(alpha = cardAlpha),
onClick = onBack scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
) { ),
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
@@ -480,3 +492,5 @@ private fun TopBar(
private fun SettingsPreview() { private fun SettingsPreview() {
SettingScreen(EmptyDestinationsNavigator) SettingScreen(EmptyDestinationsNavigator)
} }

View File

@@ -0,0 +1,403 @@
package shirkneko.zako.sukisu.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun SuperUserScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<SuperUserViewModel>()
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val listState = rememberLazyListState()
val context = LocalContext.current
val snackBarHostState = remember { SnackbarHostState() }
// 添加备份和还原启动器
val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState)
val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState)
LaunchedEffect(key1 = navigator) {
viewModel.search = ""
if (viewModel.appList.isEmpty()) {
viewModel.fetchAppList()
}
}
LaunchedEffect(viewModel.search) {
if (viewModel.search.isEmpty()) {
listState.scrollToItem(0)
}
}
Scaffold(
topBar = {
SearchAppBar(
title = { Text(stringResource(R.string.superuser)) },
searchText = viewModel.search,
onSearchTextChange = { viewModel.search = it },
onClearClick = { viewModel.search = "" },
dropdownContent = {
var showDropdown by remember { mutableStateOf(false) }
IconButton(
onClick = { showDropdown = true },
) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(id = R.string.settings)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
DropdownMenuItem(text = {
Text(stringResource(R.string.refresh))
}, onClick = {
scope.launch {
viewModel.fetchAppList()
}
showDropdown = false
})
DropdownMenuItem(text = {
Text(
if (viewModel.showSystemApps) {
stringResource(R.string.hide_system_apps)
} else {
stringResource(R.string.show_system_apps)
}
)
}, onClick = {
viewModel.showSystemApps = !viewModel.showSystemApps
showDropdown = false
})
// 批量操作菜单项已移除
DropdownMenuItem(text = {
Text(stringResource(R.string.backup_allowlist))
}, onClick = {
backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
showDropdown = false
})
DropdownMenuItem(text = {
Text(stringResource(R.string.restore_allowlist))
}, onClick = {
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
showDropdown = false
})
}
}
},
scrollBehavior = scrollBehavior
)
},
snackbarHost = { SnackbarHost(snackBarHostState) },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
bottomBar = {
// 批量操作按钮,直接放在底部栏
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(true)
}
}
) {
Text("批量授权")
}
Button(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(false)
}
}
) {
Text("批量取消授权")
}
}
}
}
) { innerPadding ->
PullToRefreshBox(
modifier = Modifier.padding(innerPadding),
onRefresh = {
scope.launch { viewModel.fetchAppList() }
},
isRefreshing = viewModel.isRefreshing
) {
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
// 获取分组后的应用列表 - 修改分组逻辑,避免应用重复出现在多个分组中
val rootApps = viewModel.appList.filter { it.allowSu }
val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile }
val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile }
// 显示ROOT权限应用组
if (rootApps.isNotEmpty()) {
item {
GroupHeader(title = "ROOT 权限应用")
}
items(rootApps, key = { "root_" + it.packageName + it.uid }) { app ->
AppItem(
app = app,
isSelected = viewModel.selectedApps.contains(app.packageName),
onToggleSelection = { viewModel.toggleAppSelection(app.packageName) },
onSwitchChange = { allowSu ->
scope.launch {
val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList()
}
}
},
onClick = {
if (viewModel.showBatchActions) {
viewModel.toggleAppSelection(app.packageName)
} else {
navigator.navigate(AppProfileScreenDestination(app))
}
},
onLongClick = {
// 长按进入多选模式
if (!viewModel.showBatchActions) {
viewModel.toggleBatchMode()
viewModel.toggleAppSelection(app.packageName)
}
},
viewModel = viewModel
)
}
}
// 显示自定义配置应用组
if (customApps.isNotEmpty()) {
item {
GroupHeader(title = "自定义配置应用")
}
items(customApps, key = { "custom_" + it.packageName + it.uid }) { app ->
AppItem(
app = app,
isSelected = viewModel.selectedApps.contains(app.packageName),
onToggleSelection = { viewModel.toggleAppSelection(app.packageName) },
onSwitchChange = { allowSu ->
scope.launch {
val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList()
}
}
},
onClick = {
if (viewModel.showBatchActions) {
viewModel.toggleAppSelection(app.packageName)
} else {
navigator.navigate(AppProfileScreenDestination(app))
}
},
onLongClick = {
// 长按进入多选模式
if (!viewModel.showBatchActions) {
viewModel.toggleBatchMode()
viewModel.toggleAppSelection(app.packageName)
}
},
viewModel = viewModel
)
}
}
// 显示其他应用组
if (otherApps.isNotEmpty()) {
item {
GroupHeader(title = "其他应用")
}
items(otherApps, key = { "other_" + it.packageName + it.uid }) { app ->
AppItem(
app = app,
isSelected = viewModel.selectedApps.contains(app.packageName),
onToggleSelection = { viewModel.toggleAppSelection(app.packageName) },
onSwitchChange = { allowSu ->
scope.launch {
val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList()
}
}
},
onClick = {
if (viewModel.showBatchActions) {
viewModel.toggleAppSelection(app.packageName)
} else {
navigator.navigate(AppProfileScreenDestination(app))
}
},
onLongClick = {
// 长按进入多选模式
if (!viewModel.showBatchActions) {
viewModel.toggleBatchMode()
viewModel.toggleAppSelection(app.packageName)
}
},
viewModel = viewModel
)
}
}
}
}
}
}
@Composable
fun GroupHeader(title: String) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(
text = title,
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AppItem(
app: SuperUserViewModel.AppInfo,
isSelected: Boolean,
onToggleSelection: () -> Unit,
onSwitchChange: (Boolean) -> Unit,
onClick: () -> Unit,
onLongClick: () -> Unit,
viewModel: SuperUserViewModel
) {
ListItem(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures(
onLongPress = { onLongClick() },
onTap = { onClick() }
)
},
headlineContent = { Text(app.label) },
supportingContent = {
Column {
Text(app.packageName)
FlowRow {
if (app.allowSu) {
LabelText(label = "ROOT")
} else {
if (Natives.uidShouldUmount(app.uid)) {
LabelText(label = "UMOUNT")
}
}
if (app.hasCustomProfile) {
LabelText(label = "CUSTOM")
}
}
}
},
leadingContent = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo)
.crossfade(true)
.build(),
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.width(48.dp)
.height(48.dp)
)
},
trailingContent = {
if (!viewModel.showBatchActions) {
Switch(
checked = app.allowSu,
onCheckedChange = onSwitchChange
)
} else {
Checkbox(
checked = isSelected,
onCheckedChange = { onToggleSelection() }
)
}
}
)
}
@Composable
fun LabelText(label: String) {
Box(
modifier = Modifier
.padding(top = 4.dp, end = 4.dp)
.background(
Color.Black,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = label,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
style = TextStyle(
fontSize = 8.sp,
color = Color.White,
)
)
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -49,7 +49,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
@@ -59,8 +58,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.ramcosta.composedestinations.result.getOr import com.ramcosta.composedestinations.result.getOr
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import androidx.lifecycle.compose.dropUnlessResumed
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen package shirkneko.zako.sukisu.ui.screen
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
@@ -44,18 +44,18 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.lifecycle.compose.dropUnlessResumed
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.util.deleteAppProfileTemplate import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate
import me.weishu.kernelsu.ui.util.getAppProfileTemplate import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import me.weishu.kernelsu.ui.util.setAppProfileTemplate import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import me.weishu.kernelsu.ui.viewmodel.toJSON import shirkneko.zako.sukisu.ui.viewmodel.toJSON
import androidx.lifecycle.compose.dropUnlessResumed
/** /**
* @author weishu * @author weishu

View File

@@ -0,0 +1,42 @@
package shirkneko.zako.sukisu.ui.theme
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.material3.CardDefaults
object CardConfig {
val defaultElevation: Dp = 2.dp
var cardAlpha by mutableStateOf(1f)
var cardElevation by mutableStateOf(defaultElevation)
fun save(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
prefs.edit().apply {
putFloat("card_alpha", cardAlpha)
putBoolean("custom_background_enabled", cardElevation == 0.dp)
apply()
}
}
fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat("card_alpha", 1f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
}
}
@Composable
fun getCardColors(originalColor: Color) = CardDefaults.elevatedCardColors(
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
contentColor = if (originalColor.luminance() > 0.5) Color.Black else Color.White
)
fun getCardElevation() = CardConfig.cardElevation

View File

@@ -0,0 +1,162 @@
package shirkneko.zako.sukisu.ui.theme
import androidx.compose.ui.graphics.Color
sealed class ThemeColors {
abstract val Primary: Color
abstract val Secondary: Color
abstract val Tertiary: Color
abstract val OnPrimary: Color
abstract val OnSecondary: Color
abstract val OnTertiary: Color
abstract val PrimaryContainer: Color
abstract val SecondaryContainer: Color
abstract val TertiaryContainer: Color
abstract val OnPrimaryContainer: Color
abstract val OnSecondaryContainer: Color
abstract val OnTertiaryContainer: Color
open fun getCustomSliderActiveColor(): Color = Primary
open fun getCustomSliderInactiveColor(): Color = PrimaryContainer
// Default Theme (Yellow)
object Default : ThemeColors() {
override val Primary = Color(0xFFFFD700)
override val Secondary = Color(0xFFFFBC52)
override val Tertiary = Color(0xFF795548)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFFBE9)
override val SecondaryContainer = Color(0xFFFFE6B3)
override val TertiaryContainer = Color(0xFFD7CCC8)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Blue Theme
object Blue : ThemeColors() {
override val Primary = Color(0xFF2196F3)
override val Secondary = Color(0xFF1E88E5)
override val Tertiary = Color(0xFF0D47A1)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFE3F2FD)
override val SecondaryContainer = Color(0xFFBBDEFB)
override val TertiaryContainer = Color(0xFF90CAF9)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Green Theme
object Green : ThemeColors() {
override val Primary = Color(0xFF4CAF50)
override val Secondary = Color(0xFF43A047)
override val Tertiary = Color(0xFF1B5E20)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFE8F5E9)
override val SecondaryContainer = Color(0xFFC8E6C9)
override val TertiaryContainer = Color(0xFFA5D6A7)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Purple Theme
object Purple : ThemeColors() {
override val Primary = Color(0xFF9C27B0)
override val Secondary = Color(0xFF8E24AA)
override val Tertiary = Color(0xFF4A148C)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFF3E5F5)
override val SecondaryContainer = Color(0xFFE1BEE7)
override val TertiaryContainer = Color(0xFFCE93D8)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Orange Theme
object Orange : ThemeColors() {
override val Primary = Color(0xFFFF9800)
override val Secondary = Color(0xFFFB8C00)
override val Tertiary = Color(0xFFE65100)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFF3E0)
override val SecondaryContainer = Color(0xFFFFE0B2)
override val TertiaryContainer = Color(0xFFFFCC80)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Pink Theme
object Pink : ThemeColors() {
override val Primary = Color(0xFFE91E63)
override val Secondary = Color(0xFFD81B60)
override val Tertiary = Color(0xFF880E4F)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFCE4EC)
override val SecondaryContainer = Color(0xFFF8BBD0)
override val TertiaryContainer = Color(0xFFF48FB1)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Gray Theme
object Gray : ThemeColors() {
override val Primary = Color(0xFF9E9E9E)
override val Secondary = Color(0xFF757575)
override val Tertiary = Color(0xFF616161)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFEEEEEE)
override val SecondaryContainer = Color(0xFFE0E0E0)
override val TertiaryContainer = Color(0xFFBDBDBD)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Ivory Theme
object Ivory : ThemeColors() {
override val Primary = Color(0xFFFAF0E6)
override val Secondary = Color(0xFFFFF0E6)
override val Tertiary = Color(0xFFD7CCC8)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFFAE3)
override val SecondaryContainer = Color(0xFFFFF0E6)
override val TertiaryContainer = Color(0xFFFFF0E6)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
companion object {
fun fromName(name: String): ThemeColors = when (name.lowercase()) {
"blue" -> Blue
"green" -> Green
"purple" -> Purple
"orange" -> Orange
"pink" -> Pink
"gray" -> Gray
"ivory" -> Ivory
else -> Default
}
}
}

View File

@@ -0,0 +1,289 @@
package shirkneko.zako.sukisu.ui.theme
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.zIndex
import coil.compose.rememberAsyncImagePainter
import androidx.compose.foundation.background
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
object ThemeConfig {
var customBackgroundUri by mutableStateOf<Uri?>(null)
var forceDarkMode by mutableStateOf<Boolean?>(null)
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
var useDynamicColor by mutableStateOf(false)
}
@Composable
private fun getDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.Primary,
onPrimary = ThemeConfig.currentTheme.OnPrimary,
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
onPrimaryContainer = Color.White,
secondary = ThemeConfig.currentTheme.Secondary,
onSecondary = ThemeConfig.currentTheme.OnSecondary,
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer,
onSecondaryContainer = Color.White,
tertiary = ThemeConfig.currentTheme.Tertiary,
onTertiary = ThemeConfig.currentTheme.OnTertiary,
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer,
onTertiaryContainer = Color.White,
background = Color.Transparent,
surface = Color.Transparent,
onBackground = Color.White,
onSurface = Color.White
)
@Composable
private fun getLightColorScheme() = lightColorScheme(
primary = ThemeConfig.currentTheme.Primary,
onPrimary = ThemeConfig.currentTheme.OnPrimary,
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
onPrimaryContainer = ThemeConfig.currentTheme.OnPrimaryContainer,
secondary = ThemeConfig.currentTheme.Secondary,
onSecondary = ThemeConfig.currentTheme.OnSecondary,
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer,
onSecondaryContainer = ThemeConfig.currentTheme.OnSecondaryContainer,
tertiary = ThemeConfig.currentTheme.Tertiary,
onTertiary = ThemeConfig.currentTheme.OnTertiary,
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer,
onTertiaryContainer = ThemeConfig.currentTheme.OnTertiaryContainer,
background = Color.Transparent,
surface = Color.Transparent
)
// 复制图片到应用内部存储
fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
val fileName = "custom_background.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
outputStream.close()
inputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
return null
}
}
@Composable
fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> isSystemInDarkTheme()
},
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
content: @Composable () -> Unit
) {
val context = LocalContext.current
context.loadCustomBackground()
context.loadThemeColors()
context.loadDynamicColorState()
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(context).copy(
background = Color.Transparent,
surface = Color.Transparent,
onBackground = Color.White,
onSurface = Color.White
) else dynamicLightColorScheme(context).copy(
background = Color.Transparent,
surface = Color.Transparent
)
}
darkTheme -> getDarkColorScheme()
else -> getLightColorScheme()
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography
) {
Box(modifier = Modifier.fillMaxSize()) {
// 背景图层
ThemeConfig.customBackgroundUri?.let { uri ->
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(-1f)
) {
// 背景图片
Box(
modifier = Modifier
.fillMaxSize()
.paint(
painter = rememberAsyncImagePainter(
model = uri,
onError = {
ThemeConfig.customBackgroundUri = null
context.saveCustomBackground(null)
}
),
contentScale = ContentScale.Crop
)
)
// 亮度调节层
Box(
modifier = Modifier
.fillMaxSize()
.background(
if (darkTheme) {
Color.Black.copy(alpha = 0.4f)
} else {
Color.White.copy(alpha = 0.1f)
}
)
)
// 边缘渐变遮罩层
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.radialGradient(
colors = listOf(
Color.Transparent,
if (darkTheme) {
Color.Black.copy(alpha = 0.5f)
} else {
Color.Black.copy(alpha = 0.2f)
}
),
radius = 1200f
)
)
)
}
}
// 内容图层
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(1f)
) {
content()
}
}
}
}
fun Context.saveCustomBackground(uri: Uri?) {
val newUri = uri?.let { copyImageToInternalStorage(it) }
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putString("custom_background", newUri?.toString())
.apply()
ThemeConfig.customBackgroundUri = newUri
}
fun Context.loadCustomBackground() {
val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("custom_background", null)
ThemeConfig.customBackgroundUri = uriString?.let { Uri.parse(it) }
}
fun Context.saveThemeMode(forceDark: Boolean?) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putString("theme_mode", when(forceDark) {
true -> "dark"
false -> "light"
null -> "system"
})
.apply()
ThemeConfig.forceDarkMode = forceDark
}
fun Context.loadThemeMode() {
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_mode", "system")
ThemeConfig.forceDarkMode = when(mode) {
"dark" -> true
"light" -> false
else -> null
}
}
fun Context.saveThemeColors(themeName: String) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putString("theme_colors", themeName)
.apply()
ThemeConfig.currentTheme = when(themeName) {
"blue" -> ThemeColors.Blue
"green" -> ThemeColors.Green
"purple" -> ThemeColors.Purple
"orange" -> ThemeColors.Orange
"pink" -> ThemeColors.Pink
"gray" -> ThemeColors.Gray
"ivory" -> ThemeColors.Ivory
else -> ThemeColors.Default
}
}
fun Context.loadThemeColors() {
val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_colors", "default")
ThemeConfig.currentTheme = when(themeName) {
"blue" -> ThemeColors.Blue
"green" -> ThemeColors.Green
"purple" -> ThemeColors.Purple
"orange" -> ThemeColors.Orange
"pink" -> ThemeColors.Pink
"gray" -> ThemeColors.Gray
"ivory" -> ThemeColors.Ivory
else -> ThemeColors.Default
}
}
fun Context.saveDynamicColorState(enabled: Boolean) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putBoolean("use_dynamic_color", enabled)
.apply()
ThemeConfig.useDynamicColor = enabled
}
fun Context.loadDynamicColorState() {
val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getBoolean("use_dynamic_color", true)
ThemeConfig.useDynamicColor = enabled
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.theme package shirkneko.zako.sukisu.ui.theme
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util package shirkneko.zako.sukisu.ui.util
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util package shirkneko.zako.sukisu.ui.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.DownloadManager import android.app.DownloadManager
@@ -8,11 +8,11 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo
/** /**
* @author weishu * @author weishu
@@ -63,45 +63,62 @@ fun download(
} }
fun checkNewVersion(): LatestVersionInfo { fun checkNewVersion(): LatestVersionInfo {
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest" // 改为新的 release 接口
// default null value if failed val url = "https://api.github.com/repos/ShirkNeko/KernelSU/releases/latest"
val defaultValue = LatestVersionInfo() val defaultValue = LatestVersionInfo()
runCatching { return runCatching {
ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute() okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
.use { response -> .use { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
Log.d("CheckUpdate", "Network request failed: ${response.message}")
return defaultValue return defaultValue
} }
val body = response.body?.string() ?: return defaultValue val body = response.body?.string()
if (body == null) {
Log.d("CheckUpdate", "Response body is null")
return defaultValue
}
Log.d("CheckUpdate", "Response body: $body")
val json = org.json.JSONObject(body) val json = org.json.JSONObject(body)
// 直接从 tag_name 提取版本号(如 v1.1
val tagName = json.optString("tag_name", "")
val versionName = tagName.removePrefix("v") // 移除前缀 "v"
// 从 body 字段获取更新日志(保留换行符)
val changelog = json.optString("body") val changelog = json.optString("body")
.replace("\\r\\n", "\n") // 转换换行符
val assets = json.getJSONArray("assets") val assets = json.getJSONArray("assets")
for (i in 0 until assets.length()) { for (i in 0 until assets.length()) {
val asset = assets.getJSONObject(i) val asset = assets.getJSONObject(i)
val name = asset.getString("name") val name = asset.getString("name")
if (!name.endsWith(".apk")) { if (!name.endsWith(".apk")) continue
// 修改正则表达式,只匹配 SukiSU 和版本号
val regex = Regex("SukiSU.*_(\\d+)-release")
val matchResult = regex.find(name)
if (matchResult == null) {
Log.d("CheckUpdate", "No match found in $name, skipping")
continue continue
} }
val versionCode = matchResult.groupValues[1].toInt()
val regex = Regex("v(.+?)_(\\d+)-")
val matchResult = regex.find(name) ?: continue
val versionName = matchResult.groupValues[1]
val versionCode = matchResult.groupValues[2].toInt()
val downloadUrl = asset.getString("browser_download_url") val downloadUrl = asset.getString("browser_download_url")
return LatestVersionInfo( return LatestVersionInfo(
versionCode, versionCode,
downloadUrl, downloadUrl,
changelog changelog,
versionName
) )
} }
Log.d("CheckUpdate", "No valid apk asset found, returning default value")
defaultValue
} }
} }.getOrDefault(defaultValue)
return defaultValue
} }
@Composable @Composable
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) { fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
DisposableEffect(context) { DisposableEffect(context) {
@@ -141,3 +158,4 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util; package shirkneko.zako.sukisu.ui.util;
/* /*
* Copyright (C) 2009 The Android Open Source Project * Copyright (C) 2009 The Android Open Source Project
* *

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util package shirkneko.zako.sukisu.ui.util
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util package shirkneko.zako.sukisu.ui.util
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.BuildConfig import shirkneko.zako.sukisu.BuildConfig
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.ksuApp
import org.json.JSONArray import org.json.JSONArray
import java.io.File import java.io.File
@@ -30,12 +30,7 @@ import java.io.File
private const val TAG = "KsuCli" private const val TAG = "KsuCli"
private fun getKsuDaemonPath(): String { private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.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 {
@@ -104,7 +99,7 @@ fun execKsud(args: String, newShell: Boolean = false): Boolean {
fun install() { fun install() {
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so").absolutePath val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so").absolutePath
val result = execKsud("install --magiskboot $magiskboot", true) val result = execKsud("install --magiskboot $magiskboot", true)
Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms") Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms")
} }
@@ -179,9 +174,10 @@ 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
): FlashResult { ): Boolean {
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")
@@ -194,7 +190,8 @@ fun flashModule(
file.delete() file.delete()
return FlashResult(result) onFinish(result.isSuccess, result.code)
return result.isSuccess
} }
} }
@@ -223,19 +220,21 @@ fun runModuleAction(
} }
fun restoreBoot( fun restoreBoot(
onStdout: (String) -> Unit, onStderr: (String) -> Unit onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): FlashResult { ): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr) val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
return FlashResult(result) onFinish(result.isSuccess, result.code)
return result.isSuccess
} }
fun uninstallPermanently( fun uninstallPermanently(
onStdout: (String) -> Unit, onStderr: (String) -> Unit onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): FlashResult { ): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr) val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
return FlashResult(result) onFinish(result.isSuccess, result.code)
return result.isSuccess
} }
@Parcelize @Parcelize
@@ -249,9 +248,10 @@ 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,
): FlashResult { ): Boolean {
val resolver = ksuApp.contentResolver val resolver = ksuApp.contentResolver
val bootFile = bootUri?.let { uri -> val bootFile = bootUri?.let { uri ->
@@ -265,7 +265,7 @@ fun installBoot(
} }
} }
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so") val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}" var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}"
cmd += if (bootFile == null) { cmd += if (bootFile == null) {
@@ -314,7 +314,8 @@ 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
return FlashResult(result, bootUri == null && result.isSuccess) onFinish(bootUri == null && result.isSuccess, result.code)
return result.isSuccess
} }
fun reboot(reason: String = "") { fun reboot(reason: String = "") {
@@ -434,3 +435,48 @@ fun restartApp(packageName: String) {
forceStopApp(packageName) forceStopApp(packageName)
launchApp(packageName) launchApp(packageName)
} }
private fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomksd.so"
}
fun getSuSFS(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} support")
return result
}
fun getSuSFSVersion(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} version")
return result
}
fun getSuSFSVariant(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} variant")
return result
}
fun getSuSFSFeatures(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} features")
return result
}
fun susfsSUS_SU_0(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 0")
return result
}
fun susfsSUS_SU_2(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 2")
return result
}
fun susfsSUS_SU_Mode(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su mode")
return result
}

View File

@@ -1,11 +1,11 @@
package me.weishu.kernelsu.ui.util package shirkneko.zako.sukisu.ui.util
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.system.Os import android.system.Os
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.ui.screen.getManagerVersion import shirkneko.zako.sukisu.ui.screen.getManagerVersion
import java.io.File import java.io.File
import java.io.FileWriter import java.io.FileWriter
import java.io.PrintWriter import java.io.PrintWriter

View File

@@ -0,0 +1,330 @@
package shirkneko.zako.sukisu.ui.util
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object ModuleModify {
suspend fun showRestoreConfirmation(context: Context): Boolean {
val result = CompletableDeferred<Boolean>()
withContext(Dispatchers.Main) {
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.restore_confirm_title))
.setMessage(context.getString(R.string.restore_confirm_message))
.setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) }
.setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) }
.setOnCancelListener { result.complete(false) }
.show()
}
return result.await()
}
suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
withContext(Dispatchers.IO) {
try {
val busyboxPath = "/data/adb/ksu/bin/busybox"
val moduleDir = "/data/adb/modules"
// 直接将tar输出重定向到用户选择的文件
val command = """
cd "$moduleDir" &&
$busyboxPath tar -cz ./* > /proc/self/fd/1
""".trimIndent()
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))
// 直接将tar输出写入到用户选择的文件
context.contentResolver.openOutputStream(uri)?.use { output ->
process.inputStream.copyTo(output)
}
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.backup_success),
duration = SnackbarDuration.Long
)
}
} catch (e: Exception) {
Log.e("Backup", context.getString(R.string.backup_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.backup_failed, e.message),
duration = SnackbarDuration.Long
)
}
}
}
}
suspend fun restoreModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
val userConfirmed = showRestoreConfirmation(context)
if (!userConfirmed) return
withContext(Dispatchers.IO) {
try {
val busyboxPath = "/data/adb/ksu/bin/busybox"
val moduleDir = "/data/adb/modules"
// 直接从用户选择的文件读取并解压
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir"))
context.contentResolver.openInputStream(uri)?.use { input ->
input.copyTo(process.outputStream)
}
process.outputStream.close()
process.waitFor()
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
val snackbarResult = snackBarHost.showSnackbar(
message = context.getString(R.string.restore_success),
actionLabel = context.getString(R.string.restart_now),
duration = SnackbarDuration.Long
)
if (snackbarResult == SnackbarResult.ActionPerformed) {
reboot()
}
}
} catch (e: Exception) {
Log.e("Restore", context.getString(R.string.restore_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
message = context.getString(
R.string.restore_failed,
e.message ?: context.getString(R.string.unknown_error)
),
duration = SnackbarDuration.Long
)
}
}
}
}
suspend fun showAllowlistRestoreConfirmation(context: Context): Boolean {
val result = CompletableDeferred<Boolean>()
withContext(Dispatchers.Main) {
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.allowlist_restore_confirm_title))
.setMessage(context.getString(R.string.allowlist_restore_confirm_message))
.setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) }
.setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) }
.setOnCancelListener { result.complete(false) }
.show()
}
return result.await()
}
suspend fun backupAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
withContext(Dispatchers.IO) {
try {
val allowlistPath = "/data/adb/ksu/.allowlist"
// 直接复制文件到用户选择的位置
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat $allowlistPath"))
context.contentResolver.openOutputStream(uri)?.use { output ->
process.inputStream.copyTo(output)
}
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_backup_success),
duration = SnackbarDuration.Long
)
}
} catch (e: Exception) {
Log.e("AllowlistBackup", context.getString(R.string.allowlist_backup_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_backup_failed, e.message),
duration = SnackbarDuration.Long
)
}
}
}
}
suspend fun restoreAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
val userConfirmed = showAllowlistRestoreConfirmation(context)
if (!userConfirmed) return
withContext(Dispatchers.IO) {
try {
val allowlistPath = "/data/adb/ksu/.allowlist"
// 直接从用户选择的文件读取并写入到目标位置
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat > $allowlistPath"))
context.contentResolver.openInputStream(uri)?.use { input ->
input.copyTo(process.outputStream)
}
process.outputStream.close()
process.waitFor()
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_restore_success),
duration = SnackbarDuration.Long
)
}
} catch (e: Exception) {
Log.e("AllowlistRestore", context.getString(R.string.allowlist_restore_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_restore_failed, e.message),
duration = SnackbarDuration.Long
)
}
}
}
}
@Composable
fun rememberModuleBackupLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
backupModules(context, snackBarHost, uri)
}
}
}
}
@Composable
fun rememberModuleRestoreLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
restoreModules(context, snackBarHost, uri)
}
}
}
}
@Composable
fun rememberAllowlistBackupLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
backupAllowlist(context, snackBarHost, uri)
}
}
}
}
@Composable
fun rememberAllowlistRestoreLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
restoreAllowlist(context, snackBarHost, uri)
}
}
}
}
fun createBackupIntent(): Intent {
return Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/zip"
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
putExtra(Intent.EXTRA_TITLE, "modules_backup_$timestamp.zip")
}
}
fun createRestoreIntent(): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/zip"
}
}
fun createAllowlistBackupIntent(): Intent {
return Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
putExtra(Intent.EXTRA_TITLE, "ksu_allowlist_backup_$timestamp.dat")
}
}
fun createAllowlistRestoreIntent(): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
}
}
private fun reboot() {
Runtime.getRuntime().exec(arrayOf("su", "-c", "reboot"))
}
}

View File

@@ -1,34 +1,33 @@
package me.weishu.kernelsu.ui.util package shirkneko.zako.sukisu.ui.util
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import me.weishu.kernelsu.R import shirkneko.zako.sukisu.R
@Composable @Composable
fun getSELinuxStatus(): String { fun getSELinuxStatus(): String {
val shell = Shell.Builder.create() val shell = Shell.Builder.create().build("sh")
.setFlags(Shell.FLAG_REDIRECT_STDERR)
.build("sh")
val list = ArrayList<String>() val list = ArrayList<String>()
val result = shell.use { val result = shell.use {
it.newJob().add("getenforce").to(list, list).exec() it.newJob().add("getenforce").to(list, list).exec()
} }
val output = result.out.joinToString("\n").trim()
if (result.isSuccess) { val output = list.joinToString("\n").trim()
return when (output) {
return if (result.isSuccess) {
when (output) {
"Enforcing" -> stringResource(R.string.selinux_status_enforcing) "Enforcing" -> stringResource(R.string.selinux_status_enforcing)
"Permissive" -> stringResource(R.string.selinux_status_permissive) "Permissive" -> stringResource(R.string.selinux_status_permissive)
"Disabled" -> stringResource(R.string.selinux_status_disabled) "Disabled" -> stringResource(R.string.selinux_status_disabled)
else -> stringResource(R.string.selinux_status_unknown) else -> stringResource(R.string.selinux_status_unknown)
} }
}
return if (output.endsWith("Permission denied")) {
stringResource(R.string.selinux_status_enforcing)
} else { } else {
stringResource(R.string.selinux_status_unknown) if (output.contains("Permission denied")) {
stringResource(R.string.selinux_status_enforcing)
} else {
stringResource(R.string.selinux_status_unknown)
}
} }
} }

View File

@@ -0,0 +1,8 @@
package shirkneko.zako.sukisu.ui.util.module
data class LatestVersionInfo(
val versionCode : Int = 0,
val downloadUrl : String = "",
val changelog : String = "",
val versionName: String = ""
)

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.viewmodel package shirkneko.zako.sukisu.ui.viewmodel
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
@@ -10,9 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import me.weishu.kernelsu.ui.util.HanziToPinyin import shirkneko.zako.sukisu.ui.util.listModules
import me.weishu.kernelsu.ui.util.listModules
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.Collator import java.text.Collator
@@ -134,8 +133,11 @@ class ModuleViewModel : ViewModel() {
val result = kotlin.runCatching { val result = kotlin.runCatching {
val url = m.updateJson val url = m.updateJson
Log.i(TAG, "checkUpdate url: $url") Log.i(TAG, "checkUpdate url: $url")
val response = ksuApp.okhttpClient.newCall( val response = okhttp3.OkHttpClient()
okhttp3.Request.Builder().url(url).build() .newCall(
okhttp3.Request.Builder()
.url(url)
.build()
).execute() ).execute()
Log.d(TAG, "checkUpdate code: ${response.code}") Log.d(TAG, "checkUpdate code: ${response.code}")
if (response.isSuccessful) { if (response.isSuccessful) {

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.viewmodel package shirkneko.zako.sukisu.ui.viewmodel
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
@@ -18,19 +18,18 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.IKsuInterface import shirkneko.zako.sukisu.IKsuInterface
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.ksuApp
import me.weishu.kernelsu.ui.KsuService import shirkneko.zako.sukisu.ui.KsuService
import me.weishu.kernelsu.ui.util.HanziToPinyin import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import me.weishu.kernelsu.ui.util.KsuCli import shirkneko.zako.sukisu.ui.util.KsuCli
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class SuperUserViewModel : ViewModel() { class SuperUserViewModel : ViewModel() {
companion object { companion object {
private const val TAG = "SuperUserViewModel" private const val TAG = "SuperUserViewModel"
private var apps by mutableStateOf<List<AppInfo>>(emptyList()) private var apps by mutableStateOf<List<AppInfo>>(emptyList())
@@ -54,7 +53,6 @@ class SuperUserViewModel : ViewModel() {
if (profile == null) { if (profile == null) {
return false return false
} }
return if (profile.allowSu) { return if (profile.allowSu) {
!profile.rootUseDefault !profile.rootUseDefault
} else { } else {
@@ -68,6 +66,12 @@ class SuperUserViewModel : ViewModel() {
var isRefreshing by mutableStateOf(false) var isRefreshing by mutableStateOf(false)
private set private set
// 批量操作相关状态
var showBatchActions by mutableStateOf(false)
private set
var selectedApps by mutableStateOf<Set<String>>(emptySet())
private set
private val sortedList by derivedStateOf { private val sortedList by derivedStateOf {
val comparator = compareBy<AppInfo> { val comparator = compareBy<AppInfo> {
when { when {
@@ -89,21 +93,65 @@ class SuperUserViewModel : ViewModel() {
) || HanziToPinyin.getInstance() ) || HanziToPinyin.getInstance()
.toPinyinString(it.label).contains(search, true) .toPinyinString(it.label).contains(search, true)
}.filter { }.filter {
it.uid == 2000 // Always show shell it.uid == 2000 || showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
|| showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
} }
} }
private suspend inline fun connectKsuService( // 切换批量操作模式
crossinline onDisconnect: () -> Unit = {} fun toggleBatchMode() {
): Pair<IBinder, ServiceConnection> = suspendCoroutine { showBatchActions = !showBatchActions
if (!showBatchActions) {
clearSelection()
}
}
// 切换应用选择状态
fun toggleAppSelection(packageName: String) {
selectedApps = if (selectedApps.contains(packageName)) {
selectedApps - packageName
} else {
selectedApps + packageName
}
}
// 清除所有选择
fun clearSelection() {
selectedApps = emptySet()
}
// 批量更新权限
suspend fun updateBatchPermissions(allowSu: Boolean) {
selectedApps.forEach { packageName ->
val app = apps.find { it.packageName == packageName }
app?.let {
val profile = Natives.getAppProfile(packageName, it.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
apps = apps.map { app ->
if (app.packageName == packageName) {
app.copy(profile = updatedProfile)
} else {
app
}
}
}
}
}
clearSelection()
showBatchActions = false // 批量操作完成后退出批量模式
fetchAppList() // 刷新列表以显示最新状态
}
private suspend fun connectKsuService(
onDisconnect: () -> Unit = {}
): Pair<IBinder, ServiceConnection> = suspendCoroutine { continuation ->
val connection = object : ServiceConnection { val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
onDisconnect() onDisconnect()
} }
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
it.resume(binder as IBinder to this) continuation.resume(binder as IBinder to this)
} }
} }
@@ -124,7 +172,6 @@ class SuperUserViewModel : ViewModel() {
} }
suspend fun fetchAppList() { suspend fun fetchAppList() {
isRefreshing = true isRefreshing = true
val result = connectKsuService { val result = connectKsuService {
@@ -157,4 +204,4 @@ class SuperUserViewModel : ViewModel() {
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}") Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}")
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.viewmodel package shirkneko.zako.sukisu.ui.viewmodel
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
@@ -10,18 +10,19 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.Natives import shirkneko.zako.sukisu.Natives
import me.weishu.kernelsu.ksuApp import shirkneko.zako.sukisu.profile.Capabilities
import me.weishu.kernelsu.profile.Capabilities import shirkneko.zako.sukisu.profile.Groups
import me.weishu.kernelsu.profile.Groups import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import me.weishu.kernelsu.ui.util.getAppProfileTemplate import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import me.weishu.kernelsu.ui.util.listAppProfileTemplates import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import me.weishu.kernelsu.ui.util.setAppProfileTemplate import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.Collator import java.text.Collator
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit
/** /**
@@ -137,7 +138,13 @@ class TemplateViewModel : ViewModel() {
private fun fetchRemoteTemplates() { private fun fetchRemoteTemplates() {
runCatching { runCatching {
ksuApp.okhttpClient.newCall( val client: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build()
client.newCall(
Request.Builder().url(TEMPLATE_INDEX_URL).build() Request.Builder().url(TEMPLATE_INDEX_URL).build()
).execute().use { response -> ).execute().use { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
@@ -148,7 +155,7 @@ private fun fetchRemoteTemplates() {
0.until(remoteTemplateIds.length()).forEach { i -> 0.until(remoteTemplateIds.length()).forEach { i ->
val id = remoteTemplateIds.getString(i) val id = remoteTemplateIds.getString(i)
Log.i(TAG, "fetch template: $id") Log.i(TAG, "fetch template: $id")
val templateJson = ksuApp.okhttpClient.newCall( val templateJson = client.newCall(
Request.Builder().url(TEMPLATE_URL.format(id)).build() Request.Builder().url(TEMPLATE_URL.format(id)).build()
).runCatching { ).runCatching {
execute().use { response -> execute().use { response ->

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package me.weishu.kernelsu.ui.webui; package shirkneko.zako.sukisu.ui.webui;
import java.net.URLConnection; import java.net.URLConnection;

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.webui; package shirkneko.zako.sukisu.ui.webui;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.webui package shirkneko.zako.sukisu.ui.webui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityManager import android.app.ActivityManager
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import me.weishu.kernelsu.ui.util.createRootShell import shirkneko.zako.sukisu.ui.util.createRootShell
import java.io.File import java.io.File
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.webui package shirkneko.zako.sukisu.ui.webui
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
@@ -14,9 +14,9 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import me.weishu.kernelsu.ui.util.createRootShell import shirkneko.zako.sukisu.ui.util.createRootShell
import me.weishu.kernelsu.ui.util.listModules import shirkneko.zako.sukisu.ui.util.listModules
import me.weishu.kernelsu.ui.util.withNewRootShell import shirkneko.zako.sukisu.ui.util.withNewRootShell
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File

View File

@@ -0,0 +1,26 @@
package shirkneko.zako.sukisu.utils
import android.content.Context
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
object AssetsUtil {
@Throws(IOException::class)
fun exportFiles(context: Context, src: String, out: String) {
val fileNames = context.assets.list(src)
if (fileNames?.isNotEmpty() == true) {
val file = File(out)
file.mkdirs()
fileNames.forEach { fileName ->
exportFiles(context, "$src/$fileName", "$out/$fileName")
}
} else {
context.assets.open(src).use { inputStream ->
FileOutputStream(File(out)).use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}
}
}

View File

@@ -1 +1,2 @@
libksud.so libzakomk.so
libzakomksd.so

View File

@@ -18,7 +18,7 @@
<string name="selinux_status_permissive">متساهل</string> <string name="selinux_status_permissive">متساهل</string>
<string name="selinux_status_unknown">مجهول</string> <string name="selinux_status_unknown">مجهول</string>
<string name="superuser">مستخدم خارق</string> <string name="superuser">مستخدم خارق</string>
<string name="module_failed_to_enable">فشل في تمكين الوحدة %s</string> <string name="module_failed_to_enable">لا يمكن تشغيل %s الوحدة</string>
<string name="module_failed_to_disable">فشل تعطيل الإضافة : %s</string> <string name="module_failed_to_disable">فشل تعطيل الإضافة : %s</string>
<string name="module_empty">لا توجد إضافات مثبتة</string> <string name="module_empty">لا توجد إضافات مثبتة</string>
<string name="module">الإضافات</string> <string name="module">الإضافات</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">تعرف على كيفية تثبيت KernelSU واستخدام الإضافات</string> <string name="home_click_to_learn_kernelsu">تعرف على كيفية تثبيت KernelSU واستخدام الإضافات</string>
<string name="home_support_title">إدعمنا</string> <string name="home_support_title">إدعمنا</string>
<string name="home_support_content">KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع.</string> <string name="home_support_content">KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع.</string>
<string name="about_source_code"><![CDATA[أنظر إلى مصدر البرمجة في %1$s<br/>إنضم إلى قناتنا في %2$s ]]></string>
<string name="profile_capabilities">القدرات</string> <string name="profile_capabilities">القدرات</string>
<string name="module_update">تحديث</string> <string name="module_update">تحديث</string>
<string name="module_downloading">تحميل الإضافة: %s</string> <string name="module_downloading">تحميل الإضافة: %s</string>
@@ -73,11 +72,10 @@
<string name="settings_umount_modules_default_summary">القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف.</string> <string name="settings_umount_modules_default_summary">القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف.</string>
<string name="profile_umount_modules_summary">سيسمح تمكين هذا الخيار لـKernelSU باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق.</string> <string name="profile_umount_modules_summary">سيسمح تمكين هذا الخيار لـKernelSU باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق.</string>
<string name="profile_selinux_domain">المجال</string> <string name="profile_selinux_domain">المجال</string>
<string name="profile_selinux_rules">القواعد</string> <string name="profile_selinux_rules" formatted="false">القواعد</string>
<string name="restart_app">إعادة تشغيل التطبيق</string> <string name="restart_app">إعادة تشغيل التطبيق</string>
<string name="failed_to_update_sepolicy">فشل تحديث قواعد SELinux لـ %s</string> <string name="failed_to_update_sepolicy">فشل تحديث قواعد SELinux لـ %s</string>
<string name="profile_name">اسم الملف الشخصي</string> <string name="profile_name">اسم الملف الشخصي</string>
<string name="require_kernel_version">إصدار KernelSU الحالي %d منخفض جدًا بحيث لا يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %d أو أعلى!</string>
<string name="module_changelog">سجل التغييرات</string> <string name="module_changelog">سجل التغييرات</string>
<string name="app_profile_template_import_success">تم الاستيراد بنجاح</string> <string name="app_profile_template_import_success">تم الاستيراد بنجاح</string>
<string name="app_profile_export_to_clipboard">تصدير إلى الحافظة</string> <string name="app_profile_export_to_clipboard">تصدير إلى الحافظة</string>
@@ -132,6 +130,4 @@
<string name="log_saved">السجلات محفوظة</string> <string name="log_saved">السجلات محفوظة</string>
<string name="module_sort_enabled_first">فرز (الممكن أولاً)</string> <string name="module_sort_enabled_first">فرز (الممكن أولاً)</string>
<string name="module_sort_action_first">فرز (الإجراء أولاً)</string> <string name="module_sort_action_first">فرز (الإجراء أولاً)</string>
<string name="settings_disable_su">تعطيل توافق su</string>
<string name="settings_disable_su_summary">قم بتعطيل أي تطبيقات مؤقتا من الحصول على امتيازات الجذر عبر الأمر su (لن تتأثر عمليات الجذر الحالية).</string>
</resources> </resources>

View File

@@ -53,7 +53,6 @@
<string name="profile_default">Defolt</string> <string name="profile_default">Defolt</string>
<string name="profile_custom">Özəl</string> <string name="profile_custom">Özəl</string>
<string name="home_support_content">KernelSU pulsuz və açıq mənbəlidir,həmişə belə olacaqdır. Bununla belə, ianə etməklə bizə qayğı göstərdiyinizi göstərə bilərsiniz.</string> <string name="home_support_content">KernelSU pulsuz və açıq mənbəlidir,həmişə belə olacaqdır. Bununla belə, ianə etməklə bizə qayğı göstərdiyinizi göstərə bilərsiniz.</string>
<string name="about_source_code">Mənbə kodlarımıza baxın %1$s<br/>Kanalımıza %2$s qoşulun</string>
<string name="profile_name">Profil adı</string> <string name="profile_name">Profil adı</string>
<string name="profile_capabilities">Bacarıqlar</string> <string name="profile_capabilities">Bacarıqlar</string>
<string name="profile_umount_modules">Modulları umount et</string> <string name="profile_umount_modules">Modulları umount et</string>

View File

@@ -41,7 +41,7 @@
<string name="refresh">রিফ্রেশ</string> <string name="refresh">রিফ্রেশ</string>
<string name="show_system_apps">শো সিস্টেম অ্যাপস</string> <string name="show_system_apps">শো সিস্টেম অ্যাপস</string>
<string name="hide_system_apps">হাইড সিস্টেম অ্যাপস</string> <string name="hide_system_apps">হাইড সিস্টেম অ্যাপস</string>
<string name="send_log">সেন্ড লগ</string> <string name="send_log" formatted="false">সেন্ড লগ</string>
<string name="safe_mode">সেইফ মোড</string> <string name="safe_mode">সেইফ মোড</string>
<string name="reboot_to_apply">রিবুট এপ্লাই</string> <string name="reboot_to_apply">রিবুট এপ্লাই</string>
<string name="module_magisk_conflict">মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে!</string> <string name="module_magisk_conflict">মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে!</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন</string> <string name="home_click_to_learn_kernelsu">কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন</string>
<string name="home_support_title">সাপোর্ট টাইটেল</string> <string name="home_support_title">সাপোর্ট টাইটেল</string>
<string name="home_support_content">কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন.</string> <string name="home_support_content">কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন.</string>
<string name="about_source_code"><![CDATA[Bekijk source code op %1$s<br/>আমাদের %2$s চ্যানেল মার্জ করুন]]></string>
<string name="profile_name">প্রফাইলের নাম</string> <string name="profile_name">প্রফাইলের নাম</string>
<string name="profile_namespace">নেমস্পেস মাউন্ট</string> <string name="profile_namespace">নেমস্পেস মাউন্ট</string>
<string name="profile_groups">গ্রুপস</string> <string name="profile_groups">গ্রুপস</string>
@@ -62,6 +61,5 @@
<string name="profile_namespace_global">গ্লোবাল</string> <string name="profile_namespace_global">গ্লোবাল</string>
<string name="profile_namespace_individual">আলাদাভাবে</string> <string name="profile_namespace_individual">আলাদাভাবে</string>
<string name="profile_umount_modules">আনমাউন্ট মোডিউল</string> <string name="profile_umount_modules">আনমাউন্ট মোডিউল</string>
<string name="require_kernel_version">ম্যানেজার সঠিকভাবে কাজ করার জন্য বর্তমান KernelSU সংস্করণ %d খুবই কম। অনুগ্রহ করে %d বা উচ্চতর সংস্করণে আপগ্রেড করুন!</string>
<string name="save_log">লগ সংরক্ষণ করুন</string> <string name="save_log">লগ সংরক্ষণ করুন</string>
</resources> </resources>

View File

@@ -9,7 +9,6 @@
<string name="profile_selinux_context">SELinux kontekst</string> <string name="profile_selinux_context">SELinux kontekst</string>
<string name="profile_umount_modules">Umount module</string> <string name="profile_umount_modules">Umount module</string>
<string name="failed_to_update_app_profile">Ažuriranje Profila Aplikacije za %s nije uspjelo</string> <string name="failed_to_update_app_profile">Ažuriranje Profila Aplikacije za %s nije uspjelo</string>
<string name="require_kernel_version">Trenutna KernelSU verzija %d je preniska da bi upravitelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju!</string>
<string name="settings_umount_modules_default">Umount module po zadanom</string> <string name="settings_umount_modules_default">Umount module po zadanom</string>
<string name="settings_umount_modules_default_summary">Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil.</string> <string name="settings_umount_modules_default_summary">Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil.</string>
<string name="profile_umount_modules_summary">Uključivanjem ove opcije omogućit će KernelSU-u da vrati sve izmjenute datoteke od strane modula za ovu aplikaciju.</string> <string name="profile_umount_modules_summary">Uključivanjem ove opcije omogućit će KernelSU-u da vrati sve izmjenute datoteke od strane modula za ovu aplikaciju.</string>
@@ -18,7 +17,7 @@
<string name="module_start_downloading">Započnite sa skidanjem: %s</string> <string name="module_start_downloading">Započnite sa skidanjem: %s</string>
<string name="new_version_available">Nova verzija: %s je dostupna, kliknite da skinete</string> <string name="new_version_available">Nova verzija: %s je dostupna, kliknite da skinete</string>
<string name="launch_app">Pokrenite</string> <string name="launch_app">Pokrenite</string>
<string name="force_stop_app">Prisilno Zaustavite</string> <string name="force_stop_app" formatted="false">Prisilno Zaustavite</string>
<string name="restart_app">Resetujte</string> <string name="restart_app">Resetujte</string>
<string name="selinux_status_enforcing">U Provođenju</string> <string name="selinux_status_enforcing">U Provođenju</string>
<string name="home">Početna</string> <string name="home">Početna</string>
@@ -48,7 +47,6 @@
<string name="home_support_title">Podržite Nas</string> <string name="home_support_title">Podržite Nas</string>
<string name="send_log">Pošaljite Izvještaj</string> <string name="send_log">Pošaljite Izvještaj</string>
<string name="home_learn_kernelsu">Naučite KernelSU</string> <string name="home_learn_kernelsu">Naučite KernelSU</string>
<string name="about_source_code">Pogledajte izvornu kodu na %1$s<br/>Pridružite nam se na %2$s kanalu</string>
<string name="profile_selinux_domain">Domena</string> <string name="profile_selinux_domain">Domena</string>
<string name="profile_selinux_rules">Pravila</string> <string name="profile_selinux_rules">Pravila</string>
<string name="failed_to_update_sepolicy">Neuspješno ažuriranje SELinux pravila za: %s</string> <string name="failed_to_update_sepolicy">Neuspješno ažuriranje SELinux pravila za: %s</string>

View File

@@ -32,7 +32,6 @@
<string name="home_learn_kernelsu">Lær KernelSU</string> <string name="home_learn_kernelsu">Lær KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string> <string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Lær hvordan man installerer KernelSU og moduler</string> <string name="home_click_to_learn_kernelsu">Lær hvordan man installerer KernelSU og moduler</string>
<string name="about_source_code">Se source koden ved %1$s<br/>Deltage i vores %2$s kanal</string>
<string name="profile_default">Standard</string> <string name="profile_default">Standard</string>
<string name="profile_template">Skabelon</string> <string name="profile_template">Skabelon</string>
<string name="profile_namespace">Monter navnerum</string> <string name="profile_namespace">Monter navnerum</string>
@@ -75,8 +74,7 @@
<string name="failed_to_update_app_profile">Opdatering af App Profil for %s fejlede</string> <string name="failed_to_update_app_profile">Opdatering af App Profil for %s fejlede</string>
<string name="settings_umount_modules_default_summary">Den globale standard værdi for \"Afmonter moduler\" i App Profiler. Hvis aktiveret vil den fjerne alle modulers modifikationer til system applikationerne der ikke har en sat Profil.</string> <string name="settings_umount_modules_default_summary">Den globale standard værdi for \"Afmonter moduler\" i App Profiler. Hvis aktiveret vil den fjerne alle modulers modifikationer til system applikationerne der ikke har en sat Profil.</string>
<string name="profile_selinux_domain">Domæne</string> <string name="profile_selinux_domain">Domæne</string>
<string name="profile_selinux_rules">Regler</string> <string name="profile_selinux_rules" formatted="false">Regler</string>
<string name="restart_app">Genstart</string> <string name="restart_app">Genstart</string>
<string name="require_kernel_version">Den nuværende KernelSU version %d er for lav til manageren for at fungere ordentligt. Opgrader til version %d eller højere!</string>
<string name="save_log">Gem Logfiler</string> <string name="save_log">Gem Logfiler</string>
</resources> </resources>

Some files were not shown because too many files have changed in this diff Show More