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:
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
on:
workflow_call:
inputs:
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
arch:
required: true
type: string
description: >
Build arch: aarch64/x86_64
debug:
required: false
type: boolean
default: true
manifest_name:
required: false
type: string
description: >
Local repo manifest xml path,
typically for AVD kernel build.
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
build:
name: Build ${{ inputs.version_name }}
runs-on: ubuntu-22.04
steps:
- name: Maximize build space
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 8192
temp-reserve-mb: 2048
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: Setup need_upload
id: need_upload
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=true" >> $GITHUB_OUTPUT
else
echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi
- name: Setup kernel source
run: |
echo "Free space:"
df -h
cd $GITHUB_WORKSPACE
sudo apt-get install repo -y
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 --version
repo --trace sync -c -j$(nproc --all) --no-tags
df -h
- name: Setup KernelSU
env:
PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: |
cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup"
GKI_ROOT=$(pwd)
echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
DRIVER_KCONFIG=$GKI_ROOT/common/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 $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
echo "[+] Enable debug features for kernel"
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
repo status
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: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
git config --global user.email "bot@kernelsu.org"
git config --global user.name "KernelSUBot"
cd common/ && git add -A && git commit -a -m "Add KernelSU"
repo status
- name: Build kernel
working-directory: android-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
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 }}
TARGET_IMAGE=dist/bzImage
if [ ! -e $TARGET_IMAGE ]; then
TARGET_IMAGE=dist/Image
fi
mv $TARGET_IMAGE $NAME
echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV
- name: Upload Kernel
uses: actions/upload-artifact@v4
with:
name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
path: "${{ env.file_path }}"
name: GKI Kernel Build
on:
workflow_call:
inputs:
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
arch:
required: true
type: string
description: >
Build arch: aarch64/x86_64
debug:
required: false
type: boolean
default: true
manifest_name:
required: false
type: string
description: >
Local repo manifest xml path,
typically for AVD kernel build.
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
build:
name: Build ${{ inputs.version_name }}
runs-on: ubuntu-22.04
steps:
- name: Maximize build space
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 8192
temp-reserve-mb: 2048
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: Setup need_upload
id: need_upload
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=true" >> $GITHUB_OUTPUT
else
echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi
- name: Setup kernel source
run: |
echo "Free space:"
df -h
cd $GITHUB_WORKSPACE
sudo apt-get install repo -y
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 --version
repo --trace sync -c -j$(nproc --all) --no-tags
df -h
- name: Setup KernelSU
env:
PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: |
cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup"
GKI_ROOT=$(pwd)
echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
DRIVER_KCONFIG=$GKI_ROOT/common/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 $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
echo "[+] Enable debug features for kernel"
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
repo status
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: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
git config --global user.email "bot@kernelsu.org"
git config --global user.name "KernelSUBot"
cd common/ && git add -A && git commit -a -m "Add KernelSU"
repo status
- name: Build kernel
working-directory: android-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
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 }}
TARGET_IMAGE=dist/bzImage
if [ ! -e $TARGET_IMAGE ]; then
TARGET_IMAGE=dist/Image
fi
mv $TARGET_IMAGE $NAME
echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV
- name: Upload Kernel
uses: actions/upload-artifact@v4
with:
name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
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:
include:
- version: "android12-5.10"
sub_level: 226
os_patch_level: 2024-11
sub_level: 233
os_patch_level: 2025-02
- version: "android13-5.10"
sub_level: 223
os_patch_level: 2024-11
sub_level: 228
os_patch_level: 2025-01
- version: "android13-5.15"
sub_level: 167
os_patch_level: 2024-11
sub_level: 170
os_patch_level: 2025-01
- version: "android14-5.15"
sub_level: 167
os_patch_level: 2024-11
sub_level: 170
os_patch_level: 2025-01
- version: "android14-6.1"
sub_level: 115
os_patch_level: 2024-12
sub_level: 124
os_patch_level: 2025-02
- version: "android15-6.6"
sub_level: 57
os_patch_level: 2024-12
sub_level: 66
os_patch_level: 2025-02
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
uses: ./.github/workflows/gki-kernel.yml
with:

View File

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

View File

@@ -34,4 +34,4 @@ jobs:
- name: Run clippy
run: |
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:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4

View File

@@ -103,7 +103,7 @@ jobs:
cd $GITHUB_WORKSPACE
sudo apt-get install repo -y
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 }})
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
if grep -q deprecated <<< $REMOTE_BRANCH; then
@@ -255,4 +255,4 @@ jobs:
if: ${{ inputs.build_lkm == true }}
with:
name: ${{ inputs.version }}-lkm
path: ./output/*_kernelsu.ko
path: ./output/*_kernelsu.ko

View File

@@ -71,4 +71,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
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:
shellcheck:
runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- 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.
- [Kernel-Assisted Superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): The KernelSU idea.
- [Magisk](https://github.com/topjohnwu/Magisk): The powerful root tool.
- [genuine](https://github.com/brevent/genuine/): APK v2 signature validation.
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit skills.
使用 susfs-dev 分支已集成susfs带非GKI设备的支持
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s susfs-dev
```
使用 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
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

View File

@@ -20,8 +20,8 @@ obj-$(CONFIG_KSU) += kernelsu.o
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)
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
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200))
# ksu_version: major * 10000 + git version + 600 for historical reasons
$(eval KSU_VERSION=$(shell expr 12000 + $(KSU_GIT_VERSION) + 500))
$(info -- KernelSU version: $(KSU_VERSION))
ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
else # If there is no .git file, the default version will be passed.
@@ -30,11 +30,11 @@ ccflags-y += -DKSU_VERSION=16
endif
ifndef KSU_EXPECTED_SIZE
KSU_EXPECTED_SIZE := 384
KSU_EXPECTED_SIZE := 0x35c
endif
ifndef KSU_EXPECTED_HASH
KSU_EXPECTED_HASH := 7e0c6d7278a3bb8e364e0fcba95afaf3666cf5ff3c245a3b63c8833bd0445cc4
KSU_EXPECTED_HASH := 947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef
endif
ifdef KSU_MANAGER_PACKAGE
@@ -44,6 +44,10 @@ endif
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(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_HASH=\"$(KSU_EXPECTED_HASH)\"

View File

@@ -236,12 +236,10 @@ static void nuke_ext4_sysfs() {
const char* name = sb->s_type->name;
if (strcmp(name, "ext4") != 0) {
pr_info("nuke but module aren't mounted\n");
path_put(&path);
return;
}
ext4_unregister_sysfs(sb);
path_put(&path);
}
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)
{
#ifdef CONFIG_KPROBE
pr_info("ksu_core_kprobe_exit\n");
// we dont use this now
// ksu_kprobe_exit();
#endif
}

View File

@@ -56,9 +56,12 @@ int __init kernelsu_init(void)
ksu_allowlist_init();
ksu_throne_tracker_init();
#ifdef CONFIG_KPROBES
ksu_sucompat_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
#ifndef CONFIG_KSU_DEBUG
@@ -76,8 +79,10 @@ void kernelsu_exit(void)
destroy_workqueue(ksu_workqueue);
#ifdef CONFIG_KPROBES
ksu_ksud_exit();
ksu_sucompat_exit();
#endif
ksu_core_exit();
}

View File

@@ -20,6 +20,7 @@
#include "kernel_compat.h"
#include "selinux/selinux.h"
static const char KERNEL_SU_RC[] =
"\n"
@@ -47,12 +48,21 @@ static void stop_vfs_read_hook();
static void stop_execve_hook();
static void stop_input_hook();
#ifdef CONFIG_KPROBES
static struct work_struct stop_vfs_read_work;
static struct work_struct stop_execve_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;
// Detect whether it is on or not
static bool is_boot_phase = true;
void on_post_fs_data(void)
{
static bool done = false;
@@ -68,6 +78,9 @@ void on_post_fs_data(void)
ksu_devpts_sid = ksu_get_devpts_sid();
pr_info("devpts sid: %d\n", ksu_devpts_sid);
// End of boot state
is_boot_phase = false;
}
#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 *envp, int *flags)
{
#ifndef CONFIG_KPROBES
if (!ksu_execveat_hook) {
return 0;
}
#endif
struct filename *filename;
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,
size_t *count_ptr, loff_t **pos)
{
#ifndef CONFIG_KPROBES
if (!ksu_vfs_read_hook) {
return 0;
}
#endif
struct file *file;
char __user *buf;
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 *value)
{
#ifndef CONFIG_KPROBES
if (!ksu_input_hook) {
return 0;
}
#endif
if (*type == EV_KEY && *code == KEY_VOLUMEDOWN) {
int val = *value;
pr_info("KEY_VOLUMEDOWN val: %d\n", val);
if (val) {
if (val && is_boot_phase) {
// key pressed, count it
volumedown_pressed_count += 1;
if (is_volumedown_enough(volumedown_pressed_count)) {
@@ -440,6 +468,7 @@ bool ksu_is_safe_mode()
return false;
}
#ifdef CONFIG_KPROBES
static int sys_execve_handler_pre(struct kprobe *p, struct pt_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);
}
#endif
static void stop_vfs_read_hook()
{
#ifdef CONFIG_KPROBES
bool ret = schedule_work(&stop_vfs_read_work);
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()
{
#ifdef CONFIG_KPROBES
bool ret = schedule_work(&stop_execve_hook_work);
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()
@@ -531,13 +571,19 @@ static void stop_input_hook()
return;
}
input_hook_stopped = true;
#ifdef CONFIG_KPROBES
bool ret = schedule_work(&stop_input_hook_work);
pr_info("unregister input kprobe: %d!\n", ret);
#else
ksu_input_hook = false;
pr_info("stop input_hook\n");
#endif
}
// ksud: module support
void ksu_ksud_init()
{
#ifdef CONFIG_KPROBES
int ret;
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_execve_hook_work, do_stop_execve_hook);
INIT_WORK(&stop_input_hook_work, do_stop_input_hook);
#endif
}
void ksu_ksud_exit()
{
#ifdef CONFIG_KPROBES
unregister_kprobe(&execve_kp);
// this should be done before unregister vfs_read_kp
// unregister_kprobe(&vfs_read_kp);
unregister_kprobe(&input_event_kp);
is_boot_phase = false;
#endif
}

View File

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

View File

@@ -39,7 +39,7 @@ perform_cleanup() {
# Sets up or update KernelSU environment
setup_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"
git stash && echo "[-] Stashed current changes."
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;
}
#ifdef CONFIG_KPROBES
static int faccessat_handler_pre(struct kprobe *p, struct pt_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];
#endif
// sucompat: permited process can execute 'su' to gain root access.
void ksu_sucompat_init()
{
#ifdef CONFIG_KPROBES
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
#endif
}
void ksu_sucompat_exit()
{
#ifdef CONFIG_KPROBES
for (int i = 0; i < ARRAY_SIZE(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))
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,
namelen, name) >= DATA_PATH_LEN) {
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.lsplugin.apksign)
id("kotlin-parcelize")
}
val managerVersionCode: Int by rootProject.extra
@@ -23,7 +25,7 @@ apksign {
}
android {
namespace = "me.weishu.kernelsu"
namespace = "shirkneko.zako.sukisu"
buildTypes {
release {
@@ -68,7 +70,7 @@ android {
applicationVariants.all {
outputs.forEach {
val output = it as BaseVariantOutputImpl
output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk"
output.outputFileName = "SukiSU_${managerVersionName}_${managerVersionCode}-$name.apk"
}
kotlin.sourceSets {
getByName(name) {
@@ -102,6 +104,8 @@ dependencies {
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
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.tooling)
@@ -133,4 +137,7 @@ dependencies {
implementation(libs.androidx.webkit)
implementation(libs.lsposed.cxx)
implementation(libs.com.github.topjohnwu.libsu.core)
}

View File

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

View File

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

Binary file not shown.

View File

@@ -10,7 +10,7 @@ project("kernelsu")
find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
add_library(kernelsu
add_library(zako
SHARED
jni.cc
ksu.cc
@@ -18,4 +18,4 @@ add_library(kernelsu
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"
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 result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg);
@@ -21,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg)
extern "C"
JNIEXPORT jint JNICALL
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) {
Java_shirkneko_zako_sukisu_Natives_getVersion(JNIEnv *env, jobject) {
return get_version();
}
extern "C"
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 size = 0;
bool result = get_allow_list(uids, &size);
@@ -42,13 +42,13 @@ Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
extern "C"
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();
}
extern "C"
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();
}
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
extern "C"
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) {
return nullptr;
}
@@ -129,7 +129,7 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg,
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 obj = env->NewObject(cls, constructor);
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"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
@@ -293,16 +293,16 @@ Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobjec
}
extern "C"
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);
}
extern "C"
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();
}
extern "C"
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);
}

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.system.Os
import coil.Coil
import coil.ImageLoader
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import okhttp3.Cache
import okhttp3.OkHttpClient
import java.io.File
import java.util.Locale
lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() {
lateinit var okhttpClient: OkHttpClient
override fun onCreate() {
super.onCreate()
ksuApp = this
@@ -36,20 +30,7 @@ class KernelSUApplication : Application() {
if (!webroot.exists()) {
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

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu
package shirkneko.zako.sukisu
import android.os.Parcelable
import androidx.annotation.Keep
@@ -30,7 +30,7 @@ object Natives {
const val ROOT_GID = 0
init {
System.loadLibrary("kernelsu")
System.loadLibrary("zako")
}
// 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

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

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui;
package shirkneko.zako.sukisu.ui;
import android.content.Context;
import android.content.Intent;
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import me.weishu.kernelsu.IKsuInterface;
import shirkneko.zako.sukisu.IKsuInterface;
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.Bundle
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
@@ -29,8 +30,9 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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.unit.dp
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
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.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.screen.BottomBarDestination
import me.weishu.kernelsu.ui.theme.KernelSUTheme
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.rootAvailable
import me.weishu.kernelsu.ui.util.install
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import shirkneko.zako.sukisu.ui.screen.BottomBarDestination
import shirkneko.zako.sukisu.ui.theme.CardConfig
import shirkneko.zako.sukisu.ui.theme.KernelSUTheme
import shirkneko.zako.sukisu.ui.theme.loadCustomBackground
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() {
@@ -59,8 +64,14 @@ class MainActivity : ComponentActivity() {
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 {
KernelSUTheme {
@@ -96,8 +107,16 @@ private fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isManager = Natives.becomeManager(ksuApp.packageName)
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
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(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
)
@@ -127,8 +146,12 @@ private fun BottomBar(navController: NavHostController) {
}
},
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.layout.Column
@@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.R
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.R
@Preview
@Composable
@@ -72,7 +72,7 @@ private fun AboutCardContent() {
shape = CircleShape
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
painter = painterResource(id = R.drawable.ic_launcher_monochrome),
contentDescription = "icon",
modifier = Modifier.scale(1.4f)
)
@@ -98,8 +98,8 @@ private fun AboutCardContent() {
val annotatedString = AnnotatedString.Companion.fromHtml(
htmlString = stringResource(
id = R.string.about_source_code,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
"<b><a href=\"https://github.com/ShirkNeko/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/SukiKSU\">Telegram</a></b>"
),
linkStyles = TextLinkStyles(
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.os.Build
@@ -88,6 +88,7 @@ interface ConfirmDialogHandle : DialogHandle {
)
suspend fun awaitConfirm(
title: String,
content: String,
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.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 androidx.compose.animation.AnimatedVisibility
@@ -20,9 +20,11 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import shirkneko.zako.sukisu.ui.theme.CardConfig
private const val TAG = "SearchBar"
@@ -59,6 +62,11 @@ fun SearchAppBar(
val focusRequester = remember { FocusRequester() }
var onSearch by remember { mutableStateOf(false) }
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
if (onSearch) {
LaunchedEffect(Unit) { focusRequester.requestFocus() }
}
@@ -140,7 +148,11 @@ fun SearchAppBar(
},
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 },
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.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.material3.OutlinedTextField
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SwitchItem
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
@Composable
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.layout.Column
@@ -9,17 +9,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
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.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
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.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.util.isSepolicyValid
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class)
@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.material.icons.Icons
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.util.listAppProfileTemplates
import me.weishu.kernelsu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
/**
* @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.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.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.component.profile.TemplateConfig
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.forceStopApp
import me.weishu.kernelsu.ui.util.getSepolicy
import me.weishu.kernelsu.ui.util.launchApp
import me.weishu.kernelsu.ui.util.restartApp
import me.weishu.kernelsu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.forceStopApp
import shirkneko.zako.sukisu.ui.util.getSepolicy
import shirkneko.zako.sukisu.ui.util.launchApp
import shirkneko.zako.sukisu.ui.util.restartApp
import shirkneko.zako.sukisu.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
/**
* @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.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.ModuleScreenDestination
import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import me.weishu.kernelsu.R
import shirkneko.zako.sukisu.R
enum class BottomBarDestination(
val direction: DirectionDestinationSpec,
@@ -20,5 +21,6 @@ enum class BottomBarDestination(
) {
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
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 androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.runModuleAction
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.runModuleAction
import java.io.File
import java.text.SimpleDateFormat
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.os.Environment
import android.os.Parcelable
import androidx.compose.foundation.layout.Column
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.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.Refresh
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
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.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
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.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.FlashResult
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.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 shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.*
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* @author weishu
* @date 2023/1/1.
*/
import java.util.*
enum class FlashingStatus {
FLASHING,
@@ -78,27 +43,20 @@ enum class FlashingStatus {
FAILED
}
// Lets you flash modules sequentially when mutiple zipUris are selected
fun flashModulesSequentially(
uris: List<Uri>,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
for (uri in uris) {
flashModule(uri, onStdout, onStderr).apply {
if (code != 0) {
return FlashResult(code, err, showReboot)
}
}
}
return FlashResult(0, "", true)
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
fun getFlashingStatus(): FlashingStatus {
return currentFlashingStatus.value
}
fun setFlashingStatus(status: FlashingStatus) {
currentFlashingStatus.value = status
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Destination<RootGraph>
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
var text by rememberSaveable { mutableStateOf("") }
var tempText: String
val logContent = rememberSaveable { StringBuilder() }
@@ -108,18 +66,27 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
var flashing by rememberSaveable {
mutableStateOf(FlashingStatus.FLASHING)
}
LaunchedEffect(Unit) {
if (text.isNotEmpty()) {
return@LaunchedEffect
}
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"
if (tempText.startsWith("")) { // clear command
if (tempText.startsWith("[H[J")) { // clear command
text = tempText.substring(6)
} else {
text += tempText
@@ -127,24 +94,15 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
logContent.append(it).append("\n")
}, onStderr = {
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(
topBar = {
TopBar(
flashing,
onBack = dropUnlessResumed {
currentFlashingStatus.value,
onBack = dropUnlessResumed {
navigator.popBackStack()
},
onSave = {
@@ -207,37 +165,30 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
@Parcelize
sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
FlashIt()
data class FlashModules(val uris: List<Uri>) : FlashIt()
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt()
data class FlashModule(val uri: Uri) : FlashIt()
data object FlashRestore : FlashIt()
data object FlashUninstall : FlashIt()
}
fun flashIt(
flashIt: FlashIt,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
return when (flashIt) {
) {
when (flashIt) {
is FlashIt.FlashBoot -> installBoot(
flashIt.boot,
flashIt.lkm,
flashIt.ota,
onFinish,
onStdout,
onStderr
)
is FlashIt.FlashModules -> {
flashModulesSequentially(flashIt.uris, onStdout, onStderr)
}
FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr)
FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr)
is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr)
FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr)
FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr)
}
}
@@ -281,6 +232,6 @@ private fun TopBar(
@Preview
@Composable
fun InstallPreview() {
InstallScreen(EmptyDestinationsNavigator)
fun FlashScreenPreview() {
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.os.Build
import android.os.PowerManager
import android.system.Os
import android.util.Log
import androidx.annotation.StringRes
import androidx.compose.animation.*
import androidx.compose.foundation.clickable
@@ -11,20 +12,15 @@ 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.filled.Archive
import androidx.compose.material.icons.filled.Refresh
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.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.platform.LocalUriHandler
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -36,39 +32,59 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.*
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.util.*
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo
import shirkneko.zako.sukisu.*
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.util.*
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)
@Destination<RootGraph>(start = true)
@Composable
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 scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopBar(
kernelVersion,
onSettingsClick = {
navigator.navigate(SettingScreenDestination)
},
onInstallClick = {
navigator.navigate(InstallScreenDestination)
},
onInstallClick = { navigator.navigate(InstallScreenDestination) },
onSettingsClick = { navigator.navigate(SettingScreenDestination) },
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
.padding(top = 12.dp)
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
@@ -99,9 +115,42 @@ fun HomeScreen(navigator: DestinationsNavigator) {
if (checkUpdate) {
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()
DonateCard()
LearnMoreCard()
if (!isSimpleMode) {
DonateCard()
LearnMoreCard()
}
Spacer(Modifier)
}
}
@@ -122,6 +171,11 @@ fun UpdateCard() {
val newVersionUrl = newVersion.downloadUrl
val changelog = newVersion.changelog
Log.d("UpdateCard", "Current version code: $currentVersionCode")
Log.d("UpdateCard", "New version code: $newVersionCode")
val uriHandler = LocalUriHandler.current
val title = stringResource(id = R.string.module_changelog)
val updateText = stringResource(id = R.string.module_update)
@@ -167,30 +221,27 @@ private fun TopBar(
onSettingsClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null
) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
actions = {
if (kernelVersion.isGKI()) {
IconButton(onClick = onInstallClick) {
Icon(
imageVector = Icons.Filled.Archive,
contentDescription = stringResource(id = R.string.install)
)
Icon(Icons.Filled.Archive, stringResource(R.string.install))
}
}
var showDropdown by remember { mutableStateOf(false) }
IconButton(onClick = {
showDropdown = true
}) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
IconButton(onClick = { showDropdown = true }) {
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
) {
RebootDropdownItem(id = R.string.reboot)
@@ -205,13 +256,6 @@ private fun TopBar(
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),
scrollBehavior = scrollBehavior
@@ -226,10 +270,8 @@ private fun StatusCard(
onClickInstall: () -> Unit = {}
) {
ElevatedCard(
colors = CardDefaults.elevatedCardColors(containerColor = run {
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.errorContainer
})
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier
.fillMaxWidth()
@@ -247,7 +289,7 @@ private fun StatusCard(
}
val workingMode = when (lkmMode) {
null -> ""
null -> " <Non-GKI>"
true -> " <LKM>"
else -> " <GKI>"
}
@@ -277,6 +319,19 @@ private fun StatusCard(
text = stringResource(R.string.home_module_count, getModuleCount()),
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
) {
ElevatedCard(
colors = CardDefaults.elevatedCardColors(
containerColor = color
)
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(
modifier = Modifier
@@ -341,7 +395,10 @@ fun LearnMoreCard() {
val uriHandler = LocalUriHandler.current
val url = stringResource(R.string.home_learn_kernelsu_url)
ElevatedCard {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier
.fillMaxWidth()
@@ -368,7 +425,10 @@ fun LearnMoreCard() {
fun DonateCard() {
val uriHandler = LocalUriHandler.current
ElevatedCard {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier
.fillMaxWidth()
@@ -394,8 +454,13 @@ fun DonateCard() {
@Composable
private fun InfoCard() {
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(
modifier = Modifier
.fillMaxWidth()
@@ -405,26 +470,77 @@ private fun InfoCard() {
val uname = Os.uname()
@Composable
fun InfoCardItem(label: String, content: String) {
fun InfoCardItem(
label: String,
content: String,
) {
contents.appendLine(label).appendLine(content).appendLine()
Text(text = label, style = MaterialTheme.typography.bodyLarge)
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))
val managerVersion = getManagerVersion(context)
InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
if (!isSimpleMode) {
Spacer(Modifier.height(16.dp))
val androidVersion = Build.VERSION.RELEASE
InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
}
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
Spacer(Modifier.height(16.dp))
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.Intent
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
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.layout.Arrangement
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.only
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Wysiwyg
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.MoreVert
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.material.icons.automirrored.outlined.*
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -58,7 +53,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberTopAppBarState
@@ -74,7 +68,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
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.semantics.Role
import androidx.compose.ui.text.font.FontWeight
@@ -94,24 +88,30 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.util.DownloadListener
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.download
import me.weishu.kernelsu.ui.util.hasMagisk
import me.weishu.kernelsu.ui.util.reboot
import me.weishu.kernelsu.ui.util.restoreModule
import me.weishu.kernelsu.ui.util.toggleModule
import me.weishu.kernelsu.ui.util.uninstallModule
import me.weishu.kernelsu.ui.util.getFileName
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
import me.weishu.kernelsu.ui.webui.WebUIActivity
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.util.DownloadListener
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.ui.util.download
import shirkneko.zako.sukisu.ui.util.hasMagisk
import shirkneko.zako.sukisu.ui.util.reboot
import shirkneko.zako.sukisu.ui.util.restoreModule
import shirkneko.zako.sukisu.ui.util.toggleModule
import shirkneko.zako.sukisu.ui.util.uninstallModule
import shirkneko.zako.sukisu.ui.webui.WebUIActivity
import okhttp3.OkHttpClient
import shirkneko.zako.sukisu.ui.util.ModuleModify
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)
@Destination<RootGraph>
@@ -121,6 +121,122 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current
val snackBarHost = LocalSnackbarHost.current
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)
LaunchedEffect(Unit) {
@@ -160,43 +276,62 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contentDescription = stringResource(id = R.string.settings)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
DropdownMenuItem(text = {
Text(stringResource(R.string.module_sort_action_first))
}, trailingIcon = {
Checkbox(viewModel.sortActionFirst, null)
}, onClick = {
viewModel.sortActionFirst =
!viewModel.sortActionFirst
prefs.edit()
.putBoolean(
"module_sort_action_first",
viewModel.sortActionFirst
)
.apply()
scope.launch {
viewModel.fetchModuleList()
DropdownMenu(
expanded = showDropdown,
onDismissRequest = { showDropdown = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.module_sort_action_first)) },
trailingIcon = { Checkbox(viewModel.sortActionFirst, null) },
onClick = {
viewModel.sortActionFirst = !viewModel.sortActionFirst
prefs.edit()
.putBoolean("module_sort_action_first", viewModel.sortActionFirst)
.apply()
scope.launch {
viewModel.fetchModuleList()
}
}
})
DropdownMenuItem(text = {
Text(stringResource(R.string.module_sort_enabled_first))
}, trailingIcon = {
Checkbox(viewModel.sortEnabledFirst, null)
}, onClick = {
viewModel.sortEnabledFirst =
!viewModel.sortEnabledFirst
prefs.edit()
.putBoolean(
"module_sort_enabled_first",
viewModel.sortEnabledFirst
)
.apply()
scope.launch {
viewModel.fetchModuleList()
)
DropdownMenuItem(
text = { Text(stringResource(R.string.module_sort_enabled_first)) },
trailingIcon = { Checkbox(viewModel.sortEnabledFirst, null) },
onClick = {
viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
prefs.edit()
.putBoolean("module_sort_enabled_first", 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 = {
if (!hideInstallButton) {
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(
onClick = {
// Select the zip files to install
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
selectZipLauncher.launch(intent)
selectZipLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
)
},
icon = { Icon(Icons.Filled.Add, moduleInstall) },
text = { Text(text = moduleInstall) },
icon = {
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) }
) { innerPadding ->
when {
hasMagisk -> {
Box(
@@ -277,15 +385,14 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
)
}
}
else -> {
ModuleList(
navigator,
navigator = navigator,
viewModel = viewModel,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
boxModifier = Modifier.padding(innerPadding),
onInstallModule = {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(it))))
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
},
onClickModule = { id, name, hasWebUi ->
if (hasWebUi) {
@@ -345,7 +452,7 @@ private fun ModuleList(
val changelogResult = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
runCatching {
ksuApp.okhttpClient.newCall(
OkHttpClient().newCall(
okhttp3.Request.Builder().url(changelogUrl).build()
).execute().body!!.string()
}
@@ -560,7 +667,8 @@ fun ModuleItem(
onClick: (ModuleViewModel.ModuleInfo) -> Unit
) {
ElevatedCard(
modifier = Modifier.fillMaxWidth()
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
val textDecoration = if (!module.remove) null else TextDecoration.LineThrough
val interactionSource = remember { MutableInteractionSource() }
@@ -617,7 +725,7 @@ fun ModuleItem(
fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
textDecoration = textDecoration
textDecoration = textDecoration
)
}
@@ -668,7 +776,11 @@ fun ModuleItem(
navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId))
viewModel.markNeedRefresh()
},
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
Icon(
modifier = Modifier.size(20.dp),
@@ -694,7 +806,11 @@ fun ModuleItem(
enabled = !module.remove && module.enabled,
onClick = { onClick(module) },
interactionSource = interactionSource,
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
Icon(
modifier = Modifier.size(20.dp),
@@ -720,7 +836,11 @@ fun ModuleItem(
enabled = !module.remove,
onClick = { onUpdate(module) },
shape = ButtonDefaults.textShape,
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
Icon(
modifier = Modifier.size(20.dp),
@@ -743,7 +863,11 @@ fun ModuleItem(
FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
onClick = { onUninstallClicked(module) },
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
if (!module.remove) {
Icon(
@@ -756,6 +880,7 @@ fun ModuleItem(
modifier = Modifier.size(20.dp).rotate(180f),
imageVector = Icons.Outlined.Refresh,
contentDescription = null,
)
}
if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
@@ -792,3 +917,4 @@ fun ModuleItemPreview() {
)
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.Intent
@@ -18,23 +18,10 @@ import androidx.compose.foundation.layout.safeDrawing
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.automirrored.filled.Undo
import androidx.compose.material.icons.filled.BugReport
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.material.icons.filled.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ModalBottomSheet
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.unit.dp
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.IconSource
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.generated.destinations.AppProfileTemplateScreenDestination
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.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.AboutDialog
import me.weishu.kernelsu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.getBugreportFile
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.AboutDialog
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.getBugreportFile
import java.time.LocalDateTime
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
@@ -101,15 +92,14 @@ import java.time.format.DateTimeFormatter
@Destination<RootGraph>
@Composable
fun SettingScreen(navigator: DestinationsNavigator) {
// region 界面基础设置
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current
// endregion
Scaffold(
topBar = {
TopBar(
onBack = dropUnlessResumed {
navigator.popBackStack()
},
scrollBehavior = scrollBehavior
)
},
@@ -121,6 +111,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
val loadingDialog = rememberLoadingDialog()
val shrinkDialog = rememberConfirmDialog()
// endregion
Column(
modifier = Modifier
@@ -128,10 +119,12 @@ fun SettingScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
// region 上下文与协程
val context = LocalContext.current
val scope = rememberCoroutineScope()
// endregion
// region 日志导出功能
val exportBugreportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/gzip")
) { uri: Uri? ->
@@ -146,8 +139,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
loadingDialog.hide()
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
}
// endregion
}
// region 配置项列表
// 配置文件模板入口
val profileTemplate = stringResource(id = R.string.settings_profile_template)
ListItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
@@ -157,7 +152,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
navigator.navigate(AppProfileTemplateScreenDestination)
}
)
// 卸载模块开关
var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules())
}
@@ -171,7 +166,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
umountChecked = it
}
}
// SU 禁用开关(仅在兼容版本显示)
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled())
@@ -190,6 +185,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
// 更新检查开关
var checkUpdate by rememberSaveable {
mutableStateOf(
prefs.getBoolean("check_update", true)
@@ -205,6 +202,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
checkUpdate = it
}
// Web调试开关
var enableWebDebugging by rememberSaveable {
mutableStateOf(
prefs.getBoolean("enable_web_debugging", false)
@@ -219,6 +217,21 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.edit().putBoolean("enable_web_debugging", it).apply()
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) }
@@ -458,18 +471,17 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
onBack: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar(
title = { Text(stringResource(R.string.settings)) },
navigationIcon = {
IconButton(
onClick = onBack
) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
)
@@ -480,3 +492,5 @@ private fun TopBar(
private fun SettingsPreview() {
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 androidx.compose.foundation.clickable
@@ -49,7 +49,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
@@ -59,8 +58,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.ramcosta.composedestinations.result.getOr
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import androidx.lifecycle.compose.dropUnlessResumed
/**
* @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 androidx.activity.compose.BackHandler
@@ -44,18 +44,18 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.lifecycle.compose.dropUnlessResumed
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.result.ResultBackNavigator
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.util.deleteAppProfileTemplate
import me.weishu.kernelsu.ui.util.getAppProfileTemplate
import me.weishu.kernelsu.ui.util.setAppProfileTemplate
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
import me.weishu.kernelsu.ui.viewmodel.toJSON
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import shirkneko.zako.sukisu.ui.viewmodel.toJSON
import androidx.lifecycle.compose.dropUnlessResumed
/**
* @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.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.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.app.DownloadManager
@@ -8,11 +8,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.core.content.ContextCompat
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
/**
* @author weishu
@@ -63,45 +63,62 @@ fun download(
}
fun checkNewVersion(): LatestVersionInfo {
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
// default null value if failed
// 改为新的 release 接口
val url = "https://api.github.com/repos/ShirkNeko/KernelSU/releases/latest"
val defaultValue = LatestVersionInfo()
runCatching {
ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute()
return runCatching {
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
.use { response ->
if (!response.isSuccessful) {
Log.d("CheckUpdate", "Network request failed: ${response.message}")
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)
// 直接从 tag_name 提取版本号(如 v1.1
val tagName = json.optString("tag_name", "")
val versionName = tagName.removePrefix("v") // 移除前缀 "v"
// 从 body 字段获取更新日志(保留换行符)
val changelog = json.optString("body")
.replace("\\r\\n", "\n") // 转换换行符
val assets = json.getJSONArray("assets")
for (i in 0 until assets.length()) {
val asset = assets.getJSONObject(i)
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
}
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")
return LatestVersionInfo(
versionCode,
downloadUrl,
changelog
changelog,
versionName
)
}
Log.d("CheckUpdate", "No valid apk asset found, returning default value")
defaultValue
}
}
return defaultValue
}.getOrDefault(defaultValue)
}
@Composable
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
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
*

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.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.Context
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import org.json.JSONArray
import java.io.File
@@ -30,12 +30,7 @@ import java.io.File
private const val TAG = "KsuCli"
private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
}
data class FlashResult(val code: Int, val err: String, val showReboot: Boolean) {
constructor(result: Shell.Result, showReboot: Boolean) : this(result.code, result.err.joinToString("\n"), showReboot)
constructor(result: Shell.Result) : this(result, result.isSuccess)
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.so"
}
object KsuCli {
@@ -104,7 +99,7 @@ fun execKsud(args: String, newShell: Boolean = false): Boolean {
fun install() {
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)
Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms")
}
@@ -179,9 +174,10 @@ private fun flashWithIO(
fun flashModule(
uri: Uri,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
): Boolean {
val resolver = ksuApp.contentResolver
with(resolver.openInputStream(uri)) {
val file = File(ksuApp.cacheDir, "module.zip")
@@ -194,7 +190,8 @@ fun flashModule(
file.delete()
return FlashResult(result)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
}
@@ -223,19 +220,21 @@ fun runModuleAction(
}
fun restoreBoot(
onStdout: (String) -> Unit, onStderr: (String) -> Unit
): FlashResult {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
return FlashResult(result)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
fun uninstallPermanently(
onStdout: (String) -> Unit, onStderr: (String) -> Unit
): FlashResult {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
return FlashResult(result)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
@Parcelize
@@ -249,9 +248,10 @@ fun installBoot(
bootUri: Uri?,
lkm: LkmSelection,
ota: Boolean,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit,
): FlashResult {
): Boolean {
val resolver = ksuApp.contentResolver
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}"
cmd += if (bootFile == null) {
@@ -314,7 +314,8 @@ fun installBoot(
lkmFile?.delete()
// 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 = "") {
@@ -434,3 +435,48 @@ fun restartApp(packageName: String) {
forceStopApp(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.os.Build
import android.system.Os
import com.topjohnwu.superuser.ShellUtils
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ui.screen.getManagerVersion
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ui.screen.getManagerVersion
import java.io.File
import java.io.FileWriter
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.ui.res.stringResource
import com.topjohnwu.superuser.Shell
import me.weishu.kernelsu.R
import shirkneko.zako.sukisu.R
@Composable
fun getSELinuxStatus(): String {
val shell = Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR)
.build("sh")
val shell = Shell.Builder.create().build("sh")
val list = ArrayList<String>()
val result = shell.use {
it.newJob().add("getenforce").to(list, list).exec()
}
val output = result.out.joinToString("\n").trim()
if (result.isSuccess) {
return when (output) {
val output = list.joinToString("\n").trim()
return if (result.isSuccess) {
when (output) {
"Enforcing" -> stringResource(R.string.selinux_status_enforcing)
"Permissive" -> stringResource(R.string.selinux_status_permissive)
"Disabled" -> stringResource(R.string.selinux_status_disabled)
else -> stringResource(R.string.selinux_status_unknown)
}
}
return if (output.endsWith("Permission denied")) {
stringResource(R.string.selinux_status_enforcing)
} 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.util.Log
@@ -10,9 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.util.HanziToPinyin
import me.weishu.kernelsu.ui.util.listModules
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.listModules
import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator
@@ -134,8 +133,11 @@ class ModuleViewModel : ViewModel() {
val result = kotlin.runCatching {
val url = m.updateJson
Log.i(TAG, "checkUpdate url: $url")
val response = ksuApp.okhttpClient.newCall(
okhttp3.Request.Builder().url(url).build()
val response = okhttp3.OkHttpClient()
.newCall(
okhttp3.Request.Builder()
.url(url)
.build()
).execute()
Log.d(TAG, "checkUpdate code: ${response.code}")
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.Intent
@@ -18,19 +18,18 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.IKsuInterface
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.KsuService
import me.weishu.kernelsu.ui.util.HanziToPinyin
import me.weishu.kernelsu.ui.util.KsuCli
import shirkneko.zako.sukisu.IKsuInterface
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import shirkneko.zako.sukisu.ui.KsuService
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.KsuCli
import java.text.Collator
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class SuperUserViewModel : ViewModel() {
companion object {
private const val TAG = "SuperUserViewModel"
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
@@ -54,7 +53,6 @@ class SuperUserViewModel : ViewModel() {
if (profile == null) {
return false
}
return if (profile.allowSu) {
!profile.rootUseDefault
} else {
@@ -68,6 +66,12 @@ class SuperUserViewModel : ViewModel() {
var isRefreshing by mutableStateOf(false)
private set
// 批量操作相关状态
var showBatchActions by mutableStateOf(false)
private set
var selectedApps by mutableStateOf<Set<String>>(emptySet())
private set
private val sortedList by derivedStateOf {
val comparator = compareBy<AppInfo> {
when {
@@ -89,21 +93,65 @@ class SuperUserViewModel : ViewModel() {
) || HanziToPinyin.getInstance()
.toPinyinString(it.label).contains(search, true)
}.filter {
it.uid == 2000 // Always show shell
|| showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
it.uid == 2000 || showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
}
}
private suspend inline fun connectKsuService(
crossinline onDisconnect: () -> Unit = {}
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
// 切换批量操作模式
fun toggleBatchMode() {
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 {
override fun onServiceDisconnected(name: ComponentName?) {
onDisconnect()
}
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() {
isRefreshing = true
val result = connectKsuService {
@@ -157,4 +204,4 @@ class SuperUserViewModel : ViewModel() {
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.util.Log
@@ -10,18 +10,19 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups
import me.weishu.kernelsu.ui.util.getAppProfileTemplate
import me.weishu.kernelsu.ui.util.listAppProfileTemplates
import me.weishu.kernelsu.ui.util.setAppProfileTemplate
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator
import java.util.Locale
import java.util.concurrent.TimeUnit
/**
@@ -137,7 +138,13 @@ class TemplateViewModel : ViewModel() {
private fun fetchRemoteTemplates() {
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()
).execute().use { response ->
if (!response.isSuccessful) {
@@ -148,7 +155,7 @@ private fun fetchRemoteTemplates() {
0.until(remoteTemplateIds.length()).forEach { i ->
val id = remoteTemplateIds.getString(i)
Log.i(TAG, "fetch template: $id")
val templateJson = ksuApp.okhttpClient.newCall(
val templateJson = client.newCall(
Request.Builder().url(TEMPLATE_URL.format(id)).build()
).runCatching {
execute().use { response ->

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package me.weishu.kernelsu.ui.webui;
package shirkneko.zako.sukisu.ui.webui;
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.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.app.ActivityManager
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell
import me.weishu.kernelsu.ui.util.createRootShell
import shirkneko.zako.sukisu.ui.util.createRootShell
import java.io.File
@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.content.Context
@@ -14,9 +14,9 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler
import me.weishu.kernelsu.ui.util.createRootShell
import me.weishu.kernelsu.ui.util.listModules
import me.weishu.kernelsu.ui.util.withNewRootShell
import shirkneko.zako.sukisu.ui.util.createRootShell
import shirkneko.zako.sukisu.ui.util.listModules
import shirkneko.zako.sukisu.ui.util.withNewRootShell
import org.json.JSONArray
import org.json.JSONObject
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_unknown">مجهول</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_empty">لا توجد إضافات مثبتة</string>
<string name="module">الإضافات</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">تعرف على كيفية تثبيت KernelSU واستخدام الإضافات</string>
<string name="home_support_title">إدعمنا</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="module_update">تحديث</string>
<string name="module_downloading">تحميل الإضافة: %s</string>
@@ -73,11 +72,10 @@
<string name="settings_umount_modules_default_summary">القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف.</string>
<string name="profile_umount_modules_summary">سيسمح تمكين هذا الخيار لـKernelSU باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق.</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="failed_to_update_sepolicy">فشل تحديث قواعد SELinux لـ %s</string>
<string name="profile_name">اسم الملف الشخصي</string>
<string name="require_kernel_version">إصدار KernelSU الحالي %d منخفض جدًا بحيث لا يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %d أو أعلى!</string>
<string name="module_changelog">سجل التغييرات</string>
<string name="app_profile_template_import_success">تم الاستيراد بنجاح</string>
<string name="app_profile_export_to_clipboard">تصدير إلى الحافظة</string>
@@ -132,6 +130,4 @@
<string name="log_saved">السجلات محفوظة</string>
<string name="module_sort_enabled_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>

View File

@@ -53,7 +53,6 @@
<string name="profile_default">Defolt</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="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_capabilities">Bacarıqlar</string>
<string name="profile_umount_modules">Modulları umount et</string>

View File

@@ -41,7 +41,7 @@
<string name="refresh">রিফ্রেশ</string>
<string name="show_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="reboot_to_apply">রিবুট এপ্লাই</string>
<string name="module_magisk_conflict">মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে!</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন</string>
<string name="home_support_title">সাপোর্ট টাইটেল</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_namespace">নেমস্পেস মাউন্ট</string>
<string name="profile_groups">গ্রুপস</string>
@@ -62,6 +61,5 @@
<string name="profile_namespace_global">গ্লোবাল</string>
<string name="profile_namespace_individual">আলাদাভাবে</string>
<string name="profile_umount_modules">আনমাউন্ট মোডিউল</string>
<string name="require_kernel_version">ম্যানেজার সঠিকভাবে কাজ করার জন্য বর্তমান KernelSU সংস্করণ %d খুবই কম। অনুগ্রহ করে %d বা উচ্চতর সংস্করণে আপগ্রেড করুন!</string>
<string name="save_log">লগ সংরক্ষণ করুন</string>
</resources>

View File

@@ -9,7 +9,6 @@
<string name="profile_selinux_context">SELinux kontekst</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="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_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>
@@ -18,7 +17,7 @@
<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="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="selinux_status_enforcing">U Provođenju</string>
<string name="home">Početna</string>
@@ -48,7 +47,6 @@
<string name="home_support_title">Podržite Nas</string>
<string name="send_log">Pošaljite Izvještaj</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_rules">Pravila</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_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="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_template">Skabelon</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="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_rules">Regler</string>
<string name="profile_selinux_rules" formatted="false">Regler</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>
</resources>

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