22 Commits
v3.1 ... v3.1.2

Author SHA1 Message Date
ShirkNeko
377ea183a7 Updated Vietnamese Translation 2025-05-16 22:01:04 +08:00
ShirkNeko
72361ab8bf manager: Modify the batch selection ui on the superuser page
- Add more convenient buttons for it
2025-05-16 16:00:51 +08:00
ShirkNeko
f708e583c3 docs: updated to reflect changes to support for non-GKI devices.
- Adjusted branch usage instructions and KPM support information
2025-05-15 22:55:18 +08:00
ShirkNeko
d753e1dc48 [ship ci]: Updated Vietnamese Translation 2025-05-15 22:06:49 +08:00
ShirkNeko
315a8a3805 Normalize kernel related constants to restore 2025-05-15 20:59:44 +08:00
ShirkNeko
129fed9c9f manager: simplify kernel arch
Previously:

Kernel
4.19.331-Rissu

Kernel Arch
armv8l

This changes:

Kernel
4.19.331-Rissu (armv8l)

Suggested-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
Signed-off-by: rsuntk <90097027+rsuntk@users.noreply.github.com>
2025-05-15 20:40:50 +08:00
ShirkNeko
0baccb7621 Add ksud support for the armeabi-v7a architecture
Co-authored-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
Co-authored-by: SChernykh <15806605+SChernykh@users.noreply.github.com>
Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-05-15 20:00:51 +08:00
backslashxx
842a8aa45a kernel/selinux: fix pointer mismatch with 32-bit ksud on 64-bit kernels
Since KernelSU Manager can now be built for 32-bit, theres this problematic
setup where userspace is 32-bit (armeabi-v7a) and kernel is 64bit (aarch64).

On 64-bit kernels with CONFIG_COMPAT=y, 32-bit userspace passes 32-bit pointers.
These values are interpreted as 64-bit pointers without proper casting and that
results in invalid or near-null memory access.

This patch adds proper compat-mode handling with the ff changes:
- introduce a dedicated struct (`sepol_compat_data`) using u32 fields
- use `compat_ptr()` to safely convert 32-bit user pointers to kernel pointers
- adding a runtime `ksu_is_compat` flag to dynamically select between struct layouts

This prevents a near-null pointer dereference when handling SELinux
policy updates from 32-bit ksud in a 64-bit kernel.

Truth table:

kernel 32 + ksud 32, struct is u32, no compat_ptr
kernel 64 + ksud 32, struct is u32, yes compat_ptr
kernel 64 + ksud 64, struct is u64, no compat_ptr

Preprocessor check

64BIT=y COMPAT=y: define both structs, select dynamically
64BIT=y COMPAT=n: struct u64
64BIT=n: struct u32

Tested-by: ...
Tested-by: ...
Tested-by: ...
Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
2025-05-15 17:39:41 +08:00
backslashxx
d17843479c kernel/throne_tracker: we just uninstalled the manager, stop looking for it
When the manager UID disappears from packages.list, we correctly
invalidate it — good. But, in the very next breath, we start scanning
/data/app hoping to find it again?

This event is just unnecessary I/O, exactly when we should be doing less.
Apparently this causes hangups and stuckups which is REALLY noticeable
on Ultra-Legacy devices.

Skip the scan — we’ll catch the reinstall next time packages.list updates.

Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
2025-05-15 17:39:41 +08:00
backslashxx
0d70cc8e58 kernel: sucompat: sucompat toggle support for non-kp (tiann#2506)
This is done like how vfs_read_hook, input_hook and execve_hook is disabled.
While this is not exactly the same thing, this CAN achieve the same results.
The complete disabling of all KernelSU hooks.

While this is likely unneeded, It keeps feature parity to non-kprobe builds.

adapted from upstream:
	kernel: Allow to re-enable sucompat - 4593ae81c7

Rejected: https://github.com/tiann/KernelSU/pull/2506

Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
2025-05-15 17:39:41 +08:00
Re*Index. (ot_inc)
4e6cacb206 [skip ci]: Update Japanese. (#74)
* Update strings.xml

* fix typo & change Japanese text.
2025-05-15 16:41:38 +08:00
ShirkNeko
52514ba35b [skip ci]: Move the language selection into the card 2025-05-15 16:40:27 +08:00
ShirkNeko
4d59ce435e Add card darkness adjustment function
- Updated some string translations
2025-05-14 19:55:11 +08:00
ShirkNeko
b3b7fa6f4d [skip ci]: Update language options
- Add Vietnamese support (from bro in the group)
2025-05-14 18:33:31 +08:00
ShirkNeko
c057c16391 Stand alone theme configuration for webuiX
- Add secondary color interface: isSecondaryPage (bool)
2025-05-14 16:29:27 +08:00
ShirkNeko
dee7cc6f2b Add language options
- Fix some icon color issues
2025-05-14 15:01:59 +08:00
ShirkNeko
3d0d87cb0c Add application DPI setting function
- Allow users to customize the display density of the current application

- Fix some popup color errors
2025-05-13 23:56:18 +08:00
ShirkNeko
6b66d9b3f8 Remove redundant definitions of KPM strings
Clean up unused code in build scripts
2025-05-13 21:57:42 +08:00
ShirkNeko
a301d94858 [skip ci]: Fix missing brackets in KPM feature information summary 2025-05-13 21:51:21 +08:00
ShirkNeko
01199470f2 [manager]: Add KPM function display options and related settings
- Eruda injection web UI X will not be displayed when the modification is not enabled.

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-05-13 21:44:42 +08:00
ShirkNeko
9e7ea19567 [skip ci]:Update some descriptions 2025-05-13 18:30:39 +08:00
Der_Googler
cdc6a6cb4a Add option to use WebUI X
- Added WebUI X from MMRL

Co-authored-by:Der_Googler <54764558+DerGoogler@users.noreply.github.com>
Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-05-13 15:44:20 +08:00
46 changed files with 2654 additions and 692 deletions

View File

@@ -119,6 +119,8 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- target: x86_64-linux-android - target: x86_64-linux-android
os: ubuntu-latest os: ubuntu-latest
- target: armv7-linux-androideabi
os: ubuntu-latest
uses: ./.github/workflows/ksud.yml uses: ./.github/workflows/ksud.yml
with: with:
target: ${{ matrix.target }} target: ${{ matrix.target }}
@@ -198,12 +200,20 @@ jobs:
name: ksud-x86_64-linux-android name: ksud-x86_64-linux-android
path: . path: .
- name: Download arm ksud
uses: actions/download-artifact@v4
with:
name: ksud-armv7-linux-androideabi
path: .
- name: Copy ksud to app jniLibs - name: Copy ksud to app jniLibs
run: | run: |
mkdir -p app/src/main/jniLibs/arm64-v8a mkdir -p app/src/main/jniLibs/arm64-v8a
mkdir -p app/src/main/jniLibs/x86_64 mkdir -p app/src/main/jniLibs/x86_64
mkdir -p app/src/main/jniLibs/armeabi-v7a
cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so
cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so
cp -f ../armv7-linux-androideabi/release/zakozako ../manager/app/src/main/jniLibs/armeabi-v7a/libzakozako.so
- name: Copy kpmmgr to app jniLibs - name: Copy kpmmgr to app jniLibs
run: | run: |

View File

@@ -10,32 +10,53 @@ Android device root solution based on [KernelSU](https://github.com/tiann/Kernel
> >
> However, we will be a separately maintained branch of KSU in the future > However, we will be a separately maintained branch of KSU in the future
- Fully adapted for non-GKI devices (susfs-dev and unsusfs-patched dev branches only)
## How to add ## How to add
Use the susfs-stable or susfs-dev branch (integrated susfs with support for non-GKI devices) Using main branching (non-GKI device builds are not supported)
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
Use the main branch
``` ```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
``` ```
Using branches that support non-GKI devices
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
```
## How to use integrated susfs ## How to use integrated susfs
1. Use the susfs-dev branch directly without any patching 1. Use the susfs-dev branch directly without any patching
## KPM support ```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
- We have removed duplicate KSU functions based on KernelPatch and retained KPM support. ## KPM Support
- Based on KernelPatch, we have removed duplicates of KSU and kept only KPM support.
- We will introduce more APatch-compatible functions to ensure the integrity of KPM functionality. - We will introduce more APatch-compatible functions to ensure the integrity of KPM functionality.
Open source address: https://github.com/ShirkNeko/SukiSU_KernelPatch_patch We will introduce more APatch-compatible functions to ensure the completeness of KPM functionality.
KPM templates: https://github.com/udochina/KPM-Build-Anywhere
> [!Note]
> 1. `CONFIG_KPM=y` needs to be added.
> 2. Non-GKI devices need to add `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=y` as well.
> 3. Some kernel source code below `4.19` also needs to be backport from `4.19` to the header file `set_memory.h`.
## How to do a system update to retain ROOT
- After OTA, don't reboot first, go to the manager flashing/patching kernel interface, find `GKI/non_GKI install` and select the Anykernel3 kernel zip file that needs to be flashed, select the slot that is opposite to the current running slot of the system for flashing, and then reboot to retain the GKI mode update This method is not supported for all non-GKI devices, so please try it yourself. It is the safest way to use TWRP for non-GKI devices.
- Or use LKM mode to install to the unused slot (after OTA).
## Compatibility Status
- KernelSU (versions prior to v1.0.0) officially supports Android GKI 2.0 devices (kernel 5.10+)
- Older kernels (4.4+) are also compatible, but the kernel must be built manually
- KernelSU can support 3.x kernels (3.4-3.18) through additional reverse ports
- Currently supports `arm64-v8a`, `armeabi-v7a (bare)` and some `X86_64`
KPM template address: https://github.com/udochina/KPM-Build-Anywhere
## More links ## More links
@@ -59,11 +80,14 @@ Projects compiled based on Sukisu and susfs
## Usage ## Usage
### GKI ### Universal GKI
Please follow this guide. Please **all** refer to https://kernelsu.org/zh_CN/guide/installation.html
https://kernelsu.org/guide/installation.html > [!Note]
> 1. for devices with GKI 2.0 such as Xiaomi, Redmi, Samsung, etc. (excludes kernel-modified manufacturers such as Meizu, OnePlus, Zenith, and oppo)
> 2. Find the GKI build in [more links](#%E6%9B%B4%E5%A4%9A%E9%93%BE%E6%8E%A5). Find the device kernel version. Then download it and use TWRP or kernel flashing tool to flash the zip file with AnyKernel3 suffix.
> 3. The .zip archive without suffix is uncompressed, the gz suffix is the compression used by Tenguet models.
### OnePlus ### OnePlus
@@ -109,4 +133,4 @@ If the above list does not have your name, I will update it as soon as possible,
- [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility - [Magisk](https://github.com/topjohnwu/Magisk): Powerful root utility
- [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification - [genuine](https://github.com/brevent/genuine/): APK v2 Signature Verification
- [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit utilities. - [Diamorphine](https://github.com/m0nad/Diamorphine): Some rootkit utilities.
- [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch is a key part of the APatch implementation of the kernel module - [KernelPatch](https://github.com/bmax121/KernelPatch): KernelPatch is a key part of the APatch implementation of the kernel module

View File

@@ -11,22 +11,24 @@
> >
> ただし、将来的には KSU とは別に管理されるブランチとなる予定です。 > ただし、将来的には KSU とは別に管理されるブランチとなる予定です。
- GKI 非対応なデバイスに完全に適応 (susfs-dev と unsusfs-patched dev ブランチのみ)
## 追加方法 ## 追加方法
susfs-stable または susfs-dev ブランチ (GKI 非対応デバイスに対応する統合された susfs) 使用してください。 メイン分岐の使用(GKI デバイス以外のビルドはサポートされていません。)
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
メインブランチを使用する場合
``` ```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setup.sh" | bash -s main
``` ```
GKI以外のデバイスをサポートするブランチを使用する
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
```
## 統合された susfs の使い方 ## 統合された susfs の使い方
1. パッチを当てずに susfs-dev ブランチを直接使用してください。 1. パッチを当てずに susfs-dev ブランチを直接使用してください。
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
## KPM に対応 ## KPM に対応
@@ -37,6 +39,25 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/KernelSU/main/kernel/setu
KPM テンプレートのアドレス: https://github.com/udochina/KPM-Build-Anywhere KPM テンプレートのアドレス: https://github.com/udochina/KPM-Build-Anywhere
> [!Note]
> 1. `CONFIG_KPM=y` が必要である。
> 2.非 GKI デバイスには `CONFIG_KALLSYMS=y` と `CONFIG_KALLSYMS_ALL=y` も必要です。
> 3.いくつかのカーネル `4.19` およびそれ以降のソースコードでは、 `4.19` からバックポートされた `set_memory.h` ヘッダーファイルも必要です。
## ROOT を保持するシステムアップデートの方法
- OTAの後、最初に再起動せず、マネージャのフラッシュ/パッチカーネルインターフェイスに移動し、`GKI/non_GKI 取り付け`を見つけ、フラッシュする必要があるAnykernel3カーネルzipファイルを選択し、フラッシュするためにシステムの現在の実行スロットと反対のスロットを選択し、GKIモードアップデートを保持するために再起動しますこの方法は、現時点ではすべてのnon_GKIデバイスでサポートされていませんので、各自でお試しください。 (この方法は、すべての非GKIデバイスでサポートされていませんので、ご自身でお試しください)。
- または、LKMモードを使用して未使用のスロットにインストールします(OTA後)。
## 互換性ステータス
- KernelSUv1.0.0より前のバージョンはAndroid GKI 2.0デバイスカーネル5.10以上)を公式にサポートしています。
- 古いカーネル4.4+)も互換性がありますが、カーネルは手動でビルドする必要があります。
- KernelSU は追加のリバースポートを通じて 3.x カーネル (3.4-3.18) をサポートしています。
- 現在は `arm64-v8a``armeabi-v7a (bare)`、いくつかの `X86_64` をサポートしています。
## その他のリンク ## その他のリンク
SukiSU と susfs をベースにコンパイルされたプロジェクトです。 SukiSU と susfs をベースにコンパイルされたプロジェクトです。
@@ -61,12 +82,14 @@ SukiSU と susfs をベースにコンパイルされたプロジェクトです
## 使い方 ## 使い方
### GKI ### ユニバーサルGKI
このガイドに従ってください。 https://kernelsu.org/zh_CN/guide/installation.html をご参照ください。
https://kernelsu.org/ja_JP/guide/installation.html
> [!Note]
> 1.Xiaomi、Redmi、Samsung などの GKI 2.0 を搭載したデバイス用 (Meizu、Yiga、Zenith、oppo などのマジックカーネルを搭載したメーカーは除く)。
> 2. [more links](#%E6%9B%B4%E5%A4%9A%E9%93%BE%E6%8E%A5) で GKI ビルドを検索します。 デバイスのカーネルバージョンを検索します。 次に、それをダウンロードし、TWRPまたはカーネルフラッシングツールを使用して、AnyKernel3の接尾辞が付いたzipファイルをフラッシュします。
> 接尾辞なしの.zipアーカイブは非圧縮で、接尾辞gzはTenguetモデルで使用されている圧縮方法です。
### OnePlus ### OnePlus
1. `その他のリンク`の項目に記載されているリンクを開き、デバイス情報を使用してカスタマイズされたカーネルをビルドし、AnyKernel3 の接頭辞を持つ .zip ファイルをフラッシュします。 1. `その他のリンク`の項目に記載されているリンクを開き、デバイス情報を使用してカスタマイズされたカーネルをビルドし、AnyKernel3 の接頭辞を持つ .zip ファイルをフラッシュします。

View File

@@ -14,20 +14,24 @@
在内核源码的根目录下执行以下命令: 在内核源码的根目录下执行以下命令:
使用 susfs-dev 分支(已集成 susfs带非 GKI 设备的支持) 使用 main 分支 (不支持非GKI设备构建)
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
使用 main 分支
``` ```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s main
``` ```
使用支持非 GKI 设备的分支
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s nongki
```
## 如何集成 susfs ## 如何集成 susfs
1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs 1. 直接使用 susfs-stable 或者 susfs-dev 分支,不需要再集成 susfs
```
curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/setup.sh" | bash -s susfs-dev
```
## 钩子方法 ## 钩子方法
- 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU) - 此部分引用自 [rsuntk 的钩子方法](https://github.com/rsuntk/KernelSU)
@@ -52,6 +56,25 @@ curl -LSs "https://raw.githubusercontent.com/ShirkNeko/SukiSU-Ultra/main/kernel/
KPM 模板地址: https://github.com/udochina/KPM-Build-Anywhere KPM 模板地址: https://github.com/udochina/KPM-Build-Anywhere
> [!Note]
> 1. 需要 `CONFIG_KPM=y`
> 2. 非GKI设备还需要 `CONFIG_KALLSYMS=y` 和 `CONFIG_KALLSYMS_ALL=y`
> 3. 部分内核 `4.19` 以下源码还需要从 `4.19` 向后移植头文件 `set_memory.h`
## 如何进行系统更新保留ROOT
- OTA后先不要重启进入管理器刷写/修补内核界面,找到 `GKI/non_GKI安装` 选择需要刷写的Anykernel3内核压缩文件选择与现在系统运行槽位相反的槽位进行刷写并重启即可保留GKI模式更新暂不支持所有非GKI设备使用这种方法请自行尝试。非GKI设备使用TWRP刷写是最稳妥的
- 或者使用LKM模式的安装到未使用的槽位OTA后
## 兼容状态
- KernelSUv1.0.0 之前版本)正式支持 Android GKI 2.0 设备(内核 5.10+
- 旧内核4.4+)也兼容,但必须手动构建内核
- 通过更多的反向移植KernelSU 可以支持 3.x 内核3.4-3.18
- 目前支持 `arm64-v8a` `armeabi-v7a (bare)` 和部分 `X86_64`
## 更多链接 ## 更多链接
基于 SukiSU 和 susfs 编译的项目 基于 SukiSU 和 susfs 编译的项目

View File

@@ -63,6 +63,10 @@ u32 ksu_devpts_sid;
// Detect whether it is on or not // Detect whether it is on or not
static bool is_boot_phase = true; static bool is_boot_phase = true;
#ifdef CONFIG_COMPAT
bool ksu_is_compat __read_mostly = false;
#endif
void on_post_fs_data(void) void on_post_fs_data(void)
{ {
static bool done = false; static bool done = false;
@@ -107,6 +111,7 @@ static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
if (get_user(compat, argv.ptr.compat + nr)) if (get_user(compat, argv.ptr.compat + nr))
return ERR_PTR(-EFAULT); return ERR_PTR(-EFAULT);
ksu_is_compat = true;
return compat_ptr(compat); return compat_ptr(compat);
} }
#endif #endif

View File

@@ -137,17 +137,45 @@ void apply_kernelsu_rules()
#define CMD_TYPE_CHANGE 8 #define CMD_TYPE_CHANGE 8
#define CMD_GENFSCON 9 #define CMD_GENFSCON 9
#ifdef CONFIG_64BIT
struct sepol_data { struct sepol_data {
u32 cmd; u32 cmd;
u32 subcmd; u32 subcmd;
char __user *sepol1; u64 field_sepol1;
char __user *sepol2; u64 field_sepol2;
char __user *sepol3; u64 field_sepol3;
char __user *sepol4; u64 field_sepol4;
char __user *sepol5; u64 field_sepol5;
char __user *sepol6; u64 field_sepol6;
char __user *sepol7; u64 field_sepol7;
}; };
#ifdef CONFIG_COMPAT
extern bool ksu_is_compat __read_mostly;
struct sepol_compat_data {
u32 cmd;
u32 subcmd;
u32 field_sepol1;
u32 field_sepol2;
u32 field_sepol3;
u32 field_sepol4;
u32 field_sepol5;
u32 field_sepol6;
u32 field_sepol7;
};
#endif // CONFIG_COMPAT
#else
struct sepol_data {
u32 cmd;
u32 subcmd;
u32 field_sepol1;
u32 field_sepol2;
u32 field_sepol3;
u32 field_sepol4;
u32 field_sepol5;
u32 field_sepol6;
u32 field_sepol7;
};
#endif // CONFIG_64BIT
static int get_object(char *buf, char __user *user_object, size_t buf_sz, static int get_object(char *buf, char __user *user_object, size_t buf_sz,
char **object) char **object)
@@ -191,15 +219,59 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
if (!getenforce()) { if (!getenforce()) {
pr_info("SELinux permissive or disabled when handle policy!\n"); pr_info("SELinux permissive or disabled when handle policy!\n");
} }
u32 cmd, subcmd;
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
if (unlikely(ksu_is_compat)) {
struct sepol_compat_data compat_data;
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
pr_err("sepol: copy sepol_data failed.\n");
return -1;
}
sepol1 = compat_ptr(compat_data.field_sepol1);
sepol2 = compat_ptr(compat_data.field_sepol2);
sepol3 = compat_ptr(compat_data.field_sepol3);
sepol4 = compat_ptr(compat_data.field_sepol4);
sepol5 = compat_ptr(compat_data.field_sepol5);
sepol6 = compat_ptr(compat_data.field_sepol6);
sepol7 = compat_ptr(compat_data.field_sepol7);
cmd = compat_data.cmd;
subcmd = compat_data.subcmd;
} else {
struct sepol_data data;
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
pr_err("sepol: copy sepol_data failed.\n");
return -1;
}
sepol1 = data.field_sepol1;
sepol2 = data.field_sepol2;
sepol3 = data.field_sepol3;
sepol4 = data.field_sepol4;
sepol5 = data.field_sepol5;
sepol6 = data.field_sepol6;
sepol7 = data.field_sepol7;
cmd = data.cmd;
subcmd = data.subcmd;
}
#else
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
struct sepol_data data; struct sepol_data data;
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) { if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
pr_err("sepol: copy sepol_data failed.\n"); pr_err("sepol: copy sepol_data failed.\n");
return -1; return -1;
} }
sepol1 = data.field_sepol1;
u32 cmd = data.cmd; sepol2 = data.field_sepol2;
u32 subcmd = data.subcmd; sepol3 = data.field_sepol3;
sepol4 = data.field_sepol4;
sepol5 = data.field_sepol5;
sepol6 = data.field_sepol6;
sepol7 = data.field_sepol7;
cmd = data.cmd;
subcmd = data.subcmd;
#endif
rcu_read_lock(); rcu_read_lock();
@@ -213,22 +285,22 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
char perm_buf[MAX_SEPOL_LEN]; char perm_buf[MAX_SEPOL_LEN];
char *s, *t, *c, *p; char *s, *t, *c, *p;
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) { if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) { if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) { if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) < if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
0) { 0) {
pr_err("sepol: copy perm failed.\n"); pr_err("sepol: copy perm failed.\n");
goto exit; goto exit;
@@ -258,24 +330,24 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
char perm_set[MAX_SEPOL_LEN]; char perm_set[MAX_SEPOL_LEN];
char *s, *t, *c; char *s, *t, *c;
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) { if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) { if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) { if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(operation, data.sepol4, if (strncpy_from_user(operation, sepol4,
sizeof(operation)) < 0) { sizeof(operation)) < 0) {
pr_err("sepol: copy operation failed.\n"); pr_err("sepol: copy operation failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) < if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
0) { 0) {
pr_err("sepol: copy perm_set failed.\n"); pr_err("sepol: copy perm_set failed.\n");
goto exit; goto exit;
@@ -295,7 +367,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
} else if (cmd == CMD_TYPE_STATE) { } else if (cmd == CMD_TYPE_STATE) {
char src[MAX_SEPOL_LEN]; char src[MAX_SEPOL_LEN];
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
@@ -315,11 +387,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
char type[MAX_SEPOL_LEN]; char type[MAX_SEPOL_LEN];
char attr[MAX_SEPOL_LEN]; char attr[MAX_SEPOL_LEN];
if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) { if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
pr_err("sepol: copy type failed.\n"); pr_err("sepol: copy type failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) { if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
pr_err("sepol: copy attr failed.\n"); pr_err("sepol: copy attr failed.\n");
goto exit; goto exit;
} }
@@ -339,7 +411,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
} else if (cmd == CMD_ATTR) { } else if (cmd == CMD_ATTR) {
char attr[MAX_SEPOL_LEN]; char attr[MAX_SEPOL_LEN];
if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) { if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
pr_err("sepol: copy attr failed.\n"); pr_err("sepol: copy attr failed.\n");
goto exit; goto exit;
} }
@@ -356,28 +428,28 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
char default_type[MAX_SEPOL_LEN]; char default_type[MAX_SEPOL_LEN];
char object[MAX_SEPOL_LEN]; char object[MAX_SEPOL_LEN];
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) { if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) { if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(default_type, data.sepol4, if (strncpy_from_user(default_type, sepol4,
sizeof(default_type)) < 0) { sizeof(default_type)) < 0) {
pr_err("sepol: copy default_type failed.\n"); pr_err("sepol: copy default_type failed.\n");
goto exit; goto exit;
} }
char *real_object; char *real_object;
if (data.sepol5 == NULL) { if (sepol5 == NULL) {
real_object = NULL; real_object = NULL;
} else { } else {
if (strncpy_from_user(object, data.sepol5, if (strncpy_from_user(object, sepol5,
sizeof(object)) < 0) { sizeof(object)) < 0) {
pr_err("sepol: copy object failed.\n"); pr_err("sepol: copy object failed.\n");
goto exit; goto exit;
@@ -396,19 +468,19 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
char cls[MAX_SEPOL_LEN]; char cls[MAX_SEPOL_LEN];
char default_type[MAX_SEPOL_LEN]; char default_type[MAX_SEPOL_LEN];
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) { if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) { if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(default_type, data.sepol4, if (strncpy_from_user(default_type, sepol4,
sizeof(default_type)) < 0) { sizeof(default_type)) < 0) {
pr_err("sepol: copy default_type failed.\n"); pr_err("sepol: copy default_type failed.\n");
goto exit; goto exit;
@@ -429,15 +501,15 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
char name[MAX_SEPOL_LEN]; char name[MAX_SEPOL_LEN];
char path[MAX_SEPOL_LEN]; char path[MAX_SEPOL_LEN];
char context[MAX_SEPOL_LEN]; char context[MAX_SEPOL_LEN];
if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) { if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
pr_err("sepol: copy name failed.\n"); pr_err("sepol: copy name failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) { if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
pr_err("sepol: copy path failed.\n"); pr_err("sepol: copy path failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(context, data.sepol3, sizeof(context)) < if (strncpy_from_user(context, sepol3, sizeof(context)) <
0) { 0) {
pr_err("sepol: copy context failed.\n"); pr_err("sepol: copy context failed.\n");
goto exit; goto exit;

View File

@@ -22,6 +22,10 @@
extern void escape_to_root(); extern void escape_to_root();
#ifndef CONFIG_KPROBES
static bool ksu_sucompat_non_kp __read_mostly = true;
#endif
static void __user *userspace_stack_buffer(const void *d, size_t len) static void __user *userspace_stack_buffer(const void *d, size_t len)
{ {
/* To avoid having to mmap a page in userspace, just write below the stack /* To avoid having to mmap a page in userspace, just write below the stack
@@ -50,6 +54,12 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
{ {
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KPROBES
if (!ksu_sucompat_non_kp) {
return 0;
}
#endif
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
@@ -71,6 +81,11 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
// const char sh[] = SH_PATH; // const char sh[] = SH_PATH;
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KPROBES
if (!ksu_sucompat_non_kp) {
return 0;
}
#endif
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
@@ -115,6 +130,11 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
const char sh[] = KSUD_PATH; const char sh[] = KSUD_PATH;
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KPROBES
if (!ksu_sucompat_non_kp) {
return 0;
}
#endif
if (unlikely(!filename_ptr)) if (unlikely(!filename_ptr))
return 0; return 0;
@@ -144,6 +164,11 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
const char su[] = SU_PATH; const char su[] = SU_PATH;
char path[sizeof(su) + 1]; char path[sizeof(su) + 1];
#ifndef CONFIG_KPROBES
if (!ksu_sucompat_non_kp){
return 0;
}
#endif
if (unlikely(!filename_user)) if (unlikely(!filename_user))
return 0; return 0;
@@ -237,6 +262,9 @@ void ksu_sucompat_init()
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre); su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre); su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
#else
ksu_sucompat_non_kp = true;
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
#endif #endif
} }
@@ -246,5 +274,8 @@ void ksu_sucompat_exit()
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) { for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
destroy_kprobe(&su_kps[i]); destroy_kprobe(&su_kps[i]);
} }
#else
ksu_sucompat_non_kp = false;
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
#endif #endif
} }

View File

@@ -358,12 +358,14 @@ void track_throne()
if (ksu_is_manager_uid_valid()) { if (ksu_is_manager_uid_valid()) {
pr_info("manager is uninstalled, invalidate it!\n"); pr_info("manager is uninstalled, invalidate it!\n");
ksu_invalidate_manager_uid(); ksu_invalidate_manager_uid();
goto prune;
} }
pr_info("Searching manager...\n"); pr_info("Searching manager...\n");
search_manager("/data/app", 2, &uid_list); search_manager("/data/app", 2, &uid_list);
pr_info("Search manager finished\n"); pr_info("Search manager finished\n");
} }
prune:
// then prune the allowlist // then prune the allowlist
ksu_prune_allowlist(is_uid_exist, &uid_list); ksu_prune_allowlist(is_uid_exist, &uid_list);
out: out:

View File

@@ -154,4 +154,9 @@ dependencies {
implementation(libs.com.github.topjohnwu.libsu.core) implementation(libs.com.github.topjohnwu.libsu.core)
implementation(libs.mmrl.platform)
compileOnly(libs.mmrl.hidden.api)
implementation(libs.mmrl.webui)
implementation(libs.mmrl.ui)
} }

View File

@@ -0,0 +1,47 @@
-verbose
-optimizationpasses 5
-dontwarn org.conscrypt.**
-dontwarn kotlinx.serialization.**
# Please add these rules to your existing keep rules in order to suppress warnings.
# This is generated automatically by the Android Gradle plugin.
-dontwarn com.google.auto.service.AutoService
-dontwarn com.google.j2objc.annotations.RetainedWith
-dontwarn javax.lang.model.SourceVersion
-dontwarn javax.lang.model.element.AnnotationMirror
-dontwarn javax.lang.model.element.AnnotationValue
-dontwarn javax.lang.model.element.Element
-dontwarn javax.lang.model.element.ElementKind
-dontwarn javax.lang.model.element.ElementVisitor
-dontwarn javax.lang.model.element.ExecutableElement
-dontwarn javax.lang.model.element.Modifier
-dontwarn javax.lang.model.element.Name
-dontwarn javax.lang.model.element.PackageElement
-dontwarn javax.lang.model.element.TypeElement
-dontwarn javax.lang.model.element.TypeParameterElement
-dontwarn javax.lang.model.element.VariableElement
-dontwarn javax.lang.model.type.ArrayType
-dontwarn javax.lang.model.type.DeclaredType
-dontwarn javax.lang.model.type.ExecutableType
-dontwarn javax.lang.model.type.TypeKind
-dontwarn javax.lang.model.type.TypeMirror
-dontwarn javax.lang.model.type.TypeVariable
-dontwarn javax.lang.model.type.TypeVisitor
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
-dontwarn javax.lang.model.util.ElementFilter
-dontwarn javax.lang.model.util.Elements
-dontwarn javax.lang.model.util.SimpleElementVisitor8
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
-dontwarn javax.lang.model.util.Types
-dontwarn javax.tools.Diagnostic$Kind
# MMRL:webui reflection
-keep class com.dergoogler.mmrl.webui.model.ModId { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.** { *; }
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }

View File

@@ -39,6 +39,13 @@
android:exported="false" android:exported="false"
android:theme="@style/Theme.KernelSU.WebUI" /> android:theme="@style/Theme.KernelSU.WebUI" />
<activity
android:name=".ui.webui.WebUIXActivity"
android:autoRemoveFromRecents="true"
android:documentLaunchMode="intoExisting"
android:exported="false"
android:theme="@style/Theme.KernelSU.WebUI" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"

View File

@@ -1,20 +1,69 @@
package com.sukisu.ultra package com.sukisu.ultra
import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import coil.Coil import coil.Coil
import coil.ImageLoader import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform
import me.zhanghai.android.appiconloader.coil.AppIconFetcher import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File import java.io.File
import java.util.Locale
lateinit var ksuApp: KernelSUApplication lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() { class KernelSUApplication : Application() {
override fun attachBaseContext(base: Context) {
val prefs = base.getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
var context = base
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val config = Configuration(base.resources.configuration)
config.setLocale(locale)
context = base.createConfigurationContext(config)
}
super.attachBaseContext(context)
}
@SuppressLint("ObsoleteSdkInt")
override fun getResources(): Resources {
val resources = super.getResources()
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return createConfigurationContext(config).resources
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
return resources
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ksuApp = this ksuApp = this
Platform.setHiddenApiExemptions()
val context = this val context = this
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size) val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
Coil.setImageLoader( Coil.setImageLoader(
@@ -32,5 +81,30 @@ class KernelSUApplication : Application() {
} }
} }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
applyLanguageSetting()
}
@SuppressLint("ObsoleteSdkInt")
private fun applyLanguageSetting() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}
} }

View File

@@ -16,14 +16,14 @@ object Natives {
// 10946: add capabilities // 10946: add capabilities
// 10977: change groups_count and groups to avoid overflow write // 10977: change groups_count and groups to avoid overflow write
// 11071: Fix the issue of failing to set a custom SELinux type. // 11071: Fix the issue of failing to set a custom SELinux type.
const val MINIMAL_SUPPORTED_KERNEL = 12800 const val MINIMAL_SUPPORTED_KERNEL = 11071
// 11640: Support query working mode, LKM or GKI // 11640: Support query working mode, LKM or GKI
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant. // when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12800 const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
// 12040: Support disable sucompat mode // 12040: Support disable sucompat mode
const val MINIMAL_SUPPORTED_SU_COMPAT = 12800 const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
const val KERNEL_SU_DOMAIN = "u:r:su:s0" const val KERNEL_SU_DOMAIN = "u:r:su:s0"
const val ROOT_UID = 0 const val ROOT_UID = 0

View File

@@ -1,77 +0,0 @@
package com.sukisu.ultra.ui;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
import com.topjohnwu.superuser.ipc.RootService;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.sukisu.zako.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice;
/**
* @author weishu
* @date 2023/4/18.
*/
public class KsuService extends RootService {
private static final String TAG = "KsuService";
class Stub extends IKsuInterface.Stub {
@Override
public ParcelableListSlice<PackageInfo> getPackages(int flags) {
List<PackageInfo> list = getInstalledPackagesAll(flags);
Log.i(TAG, "getPackages: " + list.size());
return new ParcelableListSlice<>(list);
}
}
@Override
public IBinder onBind(@NonNull Intent intent) {
return new Stub();
}
List<Integer> getUserIds() {
List<Integer> result = new ArrayList<>();
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
List<UserHandle> userProfiles = um.getUserProfiles();
for (UserHandle userProfile : userProfiles) {
int userId = userProfile.hashCode();
result.add(userProfile.hashCode());
}
return result;
}
ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
ArrayList<PackageInfo> packages = new ArrayList<>();
for (Integer userId : getUserIds()) {
Log.i(TAG, "getInstalledPackagesAll: " + userId);
packages.addAll(getInstalledPackagesAsUser(flags, userId));
}
return packages;
}
List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
try {
PackageManager pm = getPackageManager();
Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class);
return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);
} catch (Throwable e) {
Log.e(TAG, "err", e);
}
return new ArrayList<>();
}
}

View File

@@ -1,5 +1,8 @@
package com.sukisu.ultra.ui package com.sukisu.ultra.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.database.ContentObserver import android.database.ContentObserver
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -13,8 +16,8 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
@@ -34,6 +37,8 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.webui.initPlatform
import java.util.Locale
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private inner class ThemeChangeContentObserver( private inner class ThemeChangeContentObserver(
@@ -46,7 +51,51 @@ class MainActivity : ComponentActivity() {
} }
} }
// 应用保存的语言设置
@SuppressLint("ObsoleteSdkInt")
private fun applyLanguageSetting() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}
override fun attachBaseContext(newBase: Context) {
val prefs = newBase.getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
var context = newBase
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val config = Configuration(newBase.resources.configuration)
config.setLocale(locale)
context = newBase.createConfigurationContext(config)
}
super.attachBaseContext(context)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// 确保应用正确的语言设置
applyLanguageSetting()
applyCustomDpi()
// Enable edge to edge // Enable edge to edge
enableEdgeToEdge() enableEdgeToEdge()
@@ -104,6 +153,11 @@ class MainActivity : ComponentActivity() {
val navController = rememberNavController() val navController = rememberNavController()
val snackBarHostState = remember { SnackbarHostState() } val snackBarHostState = remember { SnackbarHostState() }
// pre-init platform to faster start WebUI X activities
LaunchedEffect(Unit) {
initPlatform()
}
Scaffold( Scaffold(
bottomBar = { BottomBar(navController) }, bottomBar = { BottomBar(navController) },
contentWindowInsets = WindowInsets(0, 0, 0, 0) contentWindowInsets = WindowInsets(0, 0, 0, 0)
@@ -128,10 +182,29 @@ class MainActivity : ComponentActivity() {
} }
} }
// 应用自定义DPI设置
private fun applyCustomDpi() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val customDpi = prefs.getInt("app_dpi", 0)
if (customDpi > 0) {
try {
val resources = resources
val metrics = resources.displayMetrics
metrics.density = customDpi / 160f
@Suppress("DEPRECATION")
metrics.scaledDensity = customDpi / 160f
metrics.densityDpi = customDpi
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
CardConfig.save(applicationContext) CardConfig.save(applicationContext)
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit() { getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
putBoolean("prevent_background_refresh", true) putBoolean("prevent_background_refresh", true)
} }
ThemeConfig.preventBackgroundRefresh = true ThemeConfig.preventBackgroundRefresh = true
@@ -139,6 +212,8 @@ class MainActivity : ComponentActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
applyLanguageSetting()
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) { if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
loadCustomBackground() loadCustomBackground()
} }
@@ -150,6 +225,11 @@ class MainActivity : ComponentActivity() {
destroyListeners.forEach { it() } destroyListeners.forEach { it() }
super.onDestroy() super.onDestroy()
} }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
applyLanguageSetting()
}
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -162,6 +242,10 @@ private fun BottomBar(navController: NavHostController) {
val containerColor = MaterialTheme.colorScheme.surfaceVariant val containerColor = MaterialTheme.colorScheme.surfaceVariant
val cardColor = MaterialTheme.colorScheme.surfaceVariant val cardColor = MaterialTheme.colorScheme.surfaceVariant
// 检查是否显示KPM
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("show_kpm_info", true)
NavigationBar( NavigationBar(
modifier = Modifier.windowInsetsPadding( modifier = Modifier.windowInsetsPadding(
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
@@ -174,7 +258,7 @@ private fun BottomBar(navController: NavHostController) {
) { ) {
BottomBarDestination.entries.forEach { destination -> BottomBarDestination.entries.forEach { destination ->
if (destination == BottomBarDestination.Kpm) { if (destination == BottomBarDestination.Kpm) {
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) { if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo) {
if (!fullFeatured && destination.rootRequired) return@forEach if (!fullFeatured && destination.rootRequired) return@forEach
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction) val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
NavigationBarItem( NavigationBarItem(

View File

@@ -5,14 +5,18 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import com.dergoogler.mmrl.ui.component.LabelItem
import com.dergoogler.mmrl.ui.component.text.TextRow
@Composable @Composable
fun SwitchItem( fun SwitchItem(
@@ -21,9 +25,11 @@ fun SwitchItem(
summary: String? = null, summary: String? = null,
checked: Boolean, checked: Boolean,
enabled: Boolean = true, enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit beta: Boolean = false,
onCheckedChange: (Boolean) -> Unit,
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
ListItem( ListItem(
modifier = Modifier modifier = Modifier
@@ -36,10 +42,31 @@ fun SwitchItem(
onValueChange = onCheckedChange onValueChange = onCheckedChange
), ),
headlineContent = { headlineContent = {
Text(title) TextRow(
leadingContent = if (beta) {
{
LabelItem(
modifier = Modifier.then(stateAlpha),
text = "Beta"
)
}
} else null
) {
Text(
modifier = Modifier.then(stateAlpha),
text = title,
)
}
}, },
leadingContent = icon?.let { leadingContent = icon?.let {
{ Icon(icon, title) } {
Icon(
modifier = Modifier.then(stateAlpha),
imageVector = icon,
contentDescription = title,
tint = MaterialTheme.colorScheme.primary
)
}
}, },
trailingContent = { trailingContent = {
Switch( Switch(
@@ -51,7 +78,10 @@ fun SwitchItem(
}, },
supportingContent = { supportingContent = {
if (summary != null) { if (summary != null) {
Text(summary) Text(
modifier = Modifier.then(stateAlpha),
text = summary
)
} }
} }
) )
@@ -71,4 +101,4 @@ fun RadioItem(
RadioButton(selected = selected, onClick = onClick) RadioButton(selected = selected, onClick = onClick)
} }
) )
} }

View File

@@ -47,11 +47,7 @@ fun SlotSelectionDialog(
} }
if (show) { if (show) {
val cardColor = if (!ThemeConfig.useDynamicColor) { val cardColor = MaterialTheme.colorScheme.surfaceContainerHighest
ThemeConfig.currentTheme.ButtonContrast
} else {
MaterialTheme.colorScheme.surfaceContainerHigh
}
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,

View File

@@ -60,7 +60,7 @@ fun SwitchItem(
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
maxLines = 1, maxLines = Int.MAX_VALUE,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
}, },

View File

@@ -133,6 +133,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) } var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) } var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
var isHideLinkCard by rememberSaveable { mutableStateOf(false) } var isHideLinkCard by rememberSaveable { mutableStateOf(false) }
var showKpmInfo by rememberSaveable { mutableStateOf(true) }
// 从 SharedPreferences 加载简洁模式状态 // 从 SharedPreferences 加载简洁模式状态
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -150,6 +151,9 @@ fun HomeScreen(navigator: DestinationsNavigator) {
isHideLinkCard = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideLinkCard = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_link_card", false) .getBoolean("is_hide_link_card", false)
showKpmInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("show_kpm_info", true)
} }
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
@@ -487,6 +491,9 @@ private fun StatusCard(
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_hide_susfs_status", false) .getBoolean("is_hide_susfs_status", false)
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("show_kpm_info", true)
Icon( Icon(
Icons.Outlined.CheckCircle, Icons.Outlined.CheckCircle,
contentDescription = stringResource(R.string.home_working), contentDescription = stringResource(R.string.home_working),
@@ -526,7 +533,7 @@ private fun StatusCard(
) )
val kpmVersion = getKpmVersion() val kpmVersion = getKpmVersion()
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) { if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_kpm_module, getKpmModuleCount()), text = stringResource(R.string.home_kpm_module, getKpmModuleCount()),
@@ -790,6 +797,8 @@ private fun InfoCard() {
val context = LocalContext.current val context = LocalContext.current
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false) .getBoolean("is_simple_mode", false)
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("show_kpm_info", true)
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHighest), colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHighest),
@@ -853,7 +862,7 @@ private fun InfoCard() {
InfoCardItem( InfoCardItem(
stringResource(R.string.home_kernel), stringResource(R.string.home_kernel),
uname.release, "${uname.release} (${uname.machine})",
icon = Icons.Default.Memory, icon = Icons.Default.Memory,
) )
@@ -891,22 +900,25 @@ private fun InfoCard() {
val kpmVersion = getKpmVersion() val kpmVersion = getKpmVersion()
val isKpmConfigured = checkKpmConfigured() val isKpmConfigured = checkKpmConfigured()
val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) { // 根据showKpmInfo决定是否显示KPM信息
val statusText = if (isKpmConfigured) { if (showKpmInfo) {
stringResource(R.string.kernel_patched) val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
val statusText = if (isKpmConfigured) {
stringResource(R.string.kernel_patched)
} else {
stringResource(R.string.kernel_not_enabled)
}
"${stringResource(R.string.not_supported)} ($statusText)"
} else { } else {
stringResource(R.string.kernel_not_enabled) "${stringResource(R.string.supported)} ($kpmVersion)"
} }
"${stringResource(R.string.not_supported)} ($statusText)"
} else {
"${stringResource(R.string.supported)} ($kpmVersion)"
}
InfoCardItem( InfoCardItem(
stringResource(R.string.home_kpm_version), stringResource(R.string.home_kpm_version),
displayVersion, displayVersion,
icon = Icons.Default.Code icon = Icons.Default.Code
) )
}
} }
} }

View File

@@ -68,9 +68,11 @@ import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.webui.WebUIXActivity
import com.dergoogler.mmrl.platform.Platform
import androidx.core.net.toUri
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -394,10 +396,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
onClickModule = { id, name, hasWebUi -> onClickModule = { id, name, hasWebUi ->
if (hasWebUi) { if (hasWebUi) {
webUILauncher.launch( webUILauncher.launch(
Intent(context, WebUIActivity::class.java) if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
.setData("kernelsu://webui/$id".toUri()) Intent(context, WebUIXActivity::class.java)
.putExtra("id", id) .setData("kernelsu://webuix/$id".toUri())
.putExtra("name", name) .putExtra("id", id)
.putExtra("name", name)
} else {
Intent(context, WebUIActivity::class.java)
.setData("kernelsu://webui/$id".toUri())
.putExtra("id", id)
.putExtra("name", name)
}
) )
} }
}, },

View File

@@ -1,6 +1,10 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
@@ -29,11 +33,14 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.NavigateNext import androidx.compose.material.icons.automirrored.filled.NavigateNext
import androidx.compose.material.icons.filled.AcUnit
import androidx.compose.material.icons.filled.Brush import androidx.compose.material.icons.filled.Brush
import androidx.compose.material.icons.filled.ColorLens import androidx.compose.material.icons.filled.ColorLens
import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.Opacity import androidx.compose.material.icons.filled.Opacity
import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Security
@@ -81,9 +88,8 @@ import androidx.core.content.edit
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.component.ImageEditorDialog import com.sukisu.ultra.ui.component.ImageEditorDialog
import com.sukisu.ultra.ui.component.KsuIsValid import com.sukisu.ultra.ui.component.KsuIsValid
import com.sukisu.ultra.ui.component.SwitchItem import com.sukisu.ultra.ui.component.SwitchItem
@@ -103,12 +109,14 @@ import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun saveCardConfig(context: Context) { fun saveCardConfig(context: Context) {
CardConfig.save(context) CardConfig.save(context)
} }
@SuppressLint("LocalContextConfigurationRead", "ObsoleteSdkInt")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
@@ -143,6 +151,129 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
stringResource(R.string.theme_dark) stringResource(R.string.theme_dark)
) )
// 获取当前语言设置
var currentLanguage by remember {
mutableStateOf(prefs.getString("app_language", "") ?: "")
}
// 获取支持的语言列表
val supportedLanguages = remember {
val languages = mutableListOf<Pair<String, String>>()
languages.add("" to context.getString(R.string.language_follow_system))
val locales = context.resources.configuration.locales
for (i in 0 until locales.size()) {
val locale = locales.get(i)
val code = locale.toLanguageTag()
if (!languages.any { it.first == code }) {
languages.add(code to locale.getDisplayName(locale))
}
}
val commonLocales = listOf(
Locale.forLanguageTag("en"), // 英语
Locale.forLanguageTag("zh-CN"), // 简体中文
Locale.forLanguageTag("zh-HK"), // 繁体中文(香港)
Locale.forLanguageTag("zh-TW"), // 繁体中文(台湾)
Locale.forLanguageTag("ja"), // 日语
Locale.forLanguageTag("fr"), // 法语
Locale.forLanguageTag("de"), // 德语
Locale.forLanguageTag("es"), // 西班牙语
Locale.forLanguageTag("it"), // 意大利语
Locale.forLanguageTag("ru"), // 俄语
Locale.forLanguageTag("pt"), // 葡萄牙语
Locale.forLanguageTag("ko"), // 韩语
Locale.forLanguageTag("vi") // 越南语
)
for (locale in commonLocales) {
val code = locale.toLanguageTag()
if (!languages.any { it.first == code }) {
val config = Configuration(context.resources.configuration)
config.setLocale(locale)
try {
val testContext = context.createConfigurationContext(config)
testContext.getString(R.string.language_follow_system)
languages.add(code to locale.getDisplayName(locale))
} catch (_: Exception) {
}
}
}
languages
}
var showLanguageDialog by remember { mutableStateOf(false) }
// 语言切换对话框
if (showLanguageDialog) {
AlertDialog(
onDismissRequest = { showLanguageDialog = false },
title = { Text(stringResource(R.string.language_setting)) },
text = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
supportedLanguages.forEach { (code, name) ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
if (currentLanguage != code) {
prefs.edit {
putString("app_language", code)
commit()
}
currentLanguage = code
Toast.makeText(
context,
context.getString(R.string.language_changed),
Toast.LENGTH_SHORT
).show()
val locale = if (code.isEmpty()) Locale.getDefault() else Locale.forLanguageTag(code)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
context.resources.updateConfiguration(config, context.resources.displayMetrics)
}
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
if (context is Activity) {
context.finish()
}
}
showLanguageDialog = false
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = currentLanguage == code,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(name)
}
}
}
},
confirmButton = {
TextButton(
onClick = { showLanguageDialog = false }
) {
Text(stringResource(R.string.cancel))
}
}
)
}
// 简洁模式开关状态 // 简洁模式开关状态
var isSimpleMode by remember { var isSimpleMode by remember {
mutableStateOf(prefs.getBoolean("is_simple_mode", false)) mutableStateOf(prefs.getBoolean("is_simple_mode", false))
@@ -176,6 +307,17 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
isHideOtherInfo = newValue isHideOtherInfo = newValue
} }
// 显示KPM开关状态
var isShowKpmInfo by remember {
mutableStateOf(prefs.getBoolean("show_kpm_info", true))
}
// 更新显示KPM开关状态
val onShowKpmInfoChange = { newValue: Boolean ->
prefs.edit { putBoolean("show_kpm_info", newValue) }
isShowKpmInfo = newValue
}
// 隐藏SuSFS状态开关状态 // 隐藏SuSFS状态开关状态
var isHideSusfsStatus by remember { var isHideSusfsStatus by remember {
mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false)) mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false))
@@ -204,6 +346,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
// 卡片配置状态 // 卡片配置状态
var cardAlpha by rememberSaveable { mutableFloatStateOf(CardConfig.cardAlpha) } var cardAlpha by rememberSaveable { mutableFloatStateOf(CardConfig.cardAlpha) }
var cardDim by rememberSaveable { mutableFloatStateOf(CardConfig.cardDim) }
var isCustomBackgroundEnabled by rememberSaveable { var isCustomBackgroundEnabled by rememberSaveable {
mutableStateOf(ThemeConfig.customBackgroundUri != null) mutableStateOf(ThemeConfig.customBackgroundUri != null)
} }
@@ -216,12 +359,43 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
var isCustomizeExpanded by remember { mutableStateOf(false) } var isCustomizeExpanded by remember { mutableStateOf(false) }
var isAppearanceExpanded by remember { mutableStateOf(true) } var isAppearanceExpanded by remember { mutableStateOf(true) }
var isAdvancedExpanded by remember { mutableStateOf(false) } var isAdvancedExpanded by remember { mutableStateOf(false) }
var isDpiExpanded by remember { mutableStateOf(false) }
// DPI 设置
val systemDpi = remember { context.resources.displayMetrics.densityDpi }
var currentDpi by remember {
mutableIntStateOf(prefs.getInt("app_dpi", systemDpi))
}
var tempDpi by remember { mutableIntStateOf(currentDpi) }
var isDpiCustom by remember { mutableStateOf(true) }
var showDpiConfirmDialog by remember { mutableStateOf(false) }
// 预设 DPI 选项
val dpiPresets = mapOf(
stringResource(R.string.dpi_size_small) to 240,
stringResource(R.string.dpi_size_medium) to 320,
stringResource(R.string.dpi_size_large) to 420,
stringResource(R.string.dpi_size_extra_large) to 560
)
// 获取DPI大小
@Composable
fun getDpiFriendlyName(dpi: Int): String {
return when (dpi) {
240 -> stringResource(R.string.dpi_size_small)
320 -> stringResource(R.string.dpi_size_medium)
420 -> stringResource(R.string.dpi_size_large)
560 -> stringResource(R.string.dpi_size_extra_large)
else -> stringResource(R.string.dpi_size_custom)
}
}
// 初始化卡片配置 // 初始化卡片配置
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// 加载设置 // 加载设置
CardConfig.load(context) CardConfig.load(context)
cardAlpha = CardConfig.cardAlpha cardAlpha = CardConfig.cardAlpha
cardDim = CardConfig.cardDim
isCustomBackgroundEnabled = ThemeConfig.customBackgroundUri != null isCustomBackgroundEnabled = ThemeConfig.customBackgroundUri != null
// 设置主题模式 // 设置主题模式
@@ -252,10 +426,37 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
CardConfig.setDarkModeDefaults() CardConfig.setDarkModeDefaults()
} }
// 保存设置 currentDpi = prefs.getInt("app_dpi", systemDpi)
tempDpi = currentDpi
CardConfig.save(context) CardConfig.save(context)
} }
// 应用 DPI 设置
val applyDpiSetting = { dpi: Int ->
if (dpi != currentDpi) {
// 保存到 SharedPreferences
prefs.edit {
putInt("app_dpi", dpi)
}
// 只修改应用级别的DPI设置
currentDpi = dpi
tempDpi = dpi
Toast.makeText(
context,
context.getString(R.string.dpi_applied_success, dpi),
Toast.LENGTH_SHORT
).show()
val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
restartIntent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(restartIntent)
showDpiConfirmDialog = false
}
}
// 主题色选项 // 主题色选项
val themeColorOptions = listOf( val themeColorOptions = listOf(
stringResource(R.string.color_default) to ThemeColors.Default, stringResource(R.string.color_default) to ThemeColors.Default,
@@ -351,6 +552,38 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
modifier = Modifier.padding(bottom = 16.dp) modifier = Modifier.padding(bottom = 16.dp)
) { ) {
Column { Column {
// 语言设置
ListItem(
headlineContent = { Text(stringResource(R.string.language_setting)) },
supportingContent = {
Text(supportedLanguages.find { it.first == currentLanguage }?.second
?: stringResource(R.string.language_follow_system))
},
leadingContent = {
Icon(
Icons.Default.Language,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
},
trailingContent = {
Icon(
Icons.AutoMirrored.Filled.NavigateNext,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
},
colors = ListItemDefaults.colors(
containerColor = Color.Transparent
),
modifier = Modifier.clickable { showLanguageDialog = true }
)
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.outlineVariant
)
// 主题模式 // 主题模式
ListItem( ListItem(
headlineContent = { Text(stringResource(R.string.theme_mode)) }, headlineContent = { Text(stringResource(R.string.theme_mode)) },
@@ -472,10 +705,13 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
isCustomBackgroundEnabled = false isCustomBackgroundEnabled = false
CardConfig.cardElevation CardConfig.cardElevation
CardConfig.cardAlpha = 1f CardConfig.cardAlpha = 1f
CardConfig.cardDim = 0f
CardConfig.isCustomAlphaSet = false CardConfig.isCustomAlphaSet = false
CardConfig.isCustomDimSet = false
CardConfig.isCustomBackgroundEnabled = false CardConfig.isCustomBackgroundEnabled = false
saveCardConfig(context) saveCardConfig(context)
cardAlpha = 1f cardAlpha = 1f
cardDim = 0f
// 重置其他相关设置 // 重置其他相关设置
ThemeConfig.needsResetOnThemeChange = true ThemeConfig.needsResetOnThemeChange = true
@@ -503,7 +739,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
) )
) )
// 透明度 Slider // 透明度和亮度调节滑动条
AnimatedVisibility( AnimatedVisibility(
visible = ThemeConfig.customBackgroundUri != null, visible = ThemeConfig.customBackgroundUri != null,
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(),
@@ -511,6 +747,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
modifier = Modifier.padding(horizontal = 32.dp) modifier = Modifier.padding(horizontal = 32.dp)
) { ) {
Column(modifier = Modifier.padding(vertical = 8.dp)) { Column(modifier = Modifier.padding(vertical = 8.dp)) {
// 透明度滑动条
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(bottom = 4.dp) modifier = Modifier.padding(bottom = 4.dp)
@@ -558,12 +795,169 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant
) )
) )
// 亮度调节滑动条
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 16.dp, bottom = 4.dp)
) {
Icon(
Icons.Filled.LightMode,
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.settings_card_dim),
style = MaterialTheme.typography.titleSmall
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "${(cardDim * 100).roundToInt()}%",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Slider(
value = cardDim,
onValueChange = { newValue ->
cardDim = newValue
CardConfig.cardDim = newValue
CardConfig.isCustomDimSet = true
prefs.edit {
putBoolean("is_custom_dim_set", true)
putFloat("card_dim", newValue)
}
},
onValueChangeFinished = {
coroutineScope.launch(Dispatchers.IO) {
saveCardConfig(context)
}
},
valueRange = 0f..1f,
steps = 20,
colors = SliderDefaults.colors(
thumbColor = MaterialTheme.colorScheme.primary,
activeTrackColor = MaterialTheme.colorScheme.primary,
inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant
)
)
} }
} }
} }
} }
} }
// DPI 设置部分
SectionHeader(
title = stringResource(R.string.dpi_settings),
expanded = isDpiExpanded,
onToggle = { isDpiExpanded = !isDpiExpanded }
)
AnimatedVisibility(
visible = isDpiExpanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Surface(
shape = RoundedCornerShape(16.dp),
tonalElevation = 1.dp,
modifier = Modifier.padding(bottom = 16.dp)
) {
Column {
ListItem(
headlineContent = { Text(stringResource(R.string.app_dpi_title)) },
supportingContent = { Text(stringResource(R.string.app_dpi_summary)) },
leadingContent = {
Icon(
Icons.Default.AcUnit,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
},
trailingContent = {
Text(
text = getDpiFriendlyName(tempDpi),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
},
colors = ListItemDefaults.colors(
containerColor = Color.Transparent
)
)
// DPI 滑动条
Column(modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)) {
Slider(
value = tempDpi.toFloat(),
onValueChange = {
tempDpi = it.toInt()
isDpiCustom = !dpiPresets.containsValue(tempDpi)
},
valueRange = 160f..600f,
steps = 11,
colors = SliderDefaults.colors(
thumbColor = MaterialTheme.colorScheme.primary,
activeTrackColor = MaterialTheme.colorScheme.primary,
inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant
)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
) {
dpiPresets.forEach { (name, dpi) ->
TextButton(
onClick = {
tempDpi = dpi
isDpiCustom = false
},
modifier = Modifier.weight(1f)
) {
Text(
text = name,
color = if (tempDpi == dpi)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
TextButton(
onClick = {
if (tempDpi != currentDpi) {
showDpiConfirmDialog = true
}
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
) {
Text(stringResource(R.string.dpi_apply_settings))
}
Text(
text = if (isDpiCustom)
"${stringResource(R.string.dpi_size_custom)}: $tempDpi"
else
"${getDpiFriendlyName(tempDpi)}: $tempDpi",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}
// 自定义设置部分 // 自定义设置部分
SectionHeader( SectionHeader(
title = stringResource(R.string.custom_settings), title = stringResource(R.string.custom_settings),
@@ -642,6 +1036,21 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
color = MaterialTheme.colorScheme.outlineVariant color = MaterialTheme.colorScheme.outlineVariant
) )
// 显示KPM开关
SwitchItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.show_kpm_info),
summary = stringResource(R.string.show_kpm_info_summary),
checked = isShowKpmInfo
) {
onShowKpmInfoChange(it)
}
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.outlineVariant
)
// 隐藏链接信息 // 隐藏链接信息
SwitchItem( SwitchItem(
icon = Icons.Filled.VisibilityOff, icon = Icons.Filled.VisibilityOff,
@@ -794,6 +1203,9 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
if (!CardConfig.isCustomAlphaSet) { if (!CardConfig.isCustomAlphaSet) {
CardConfig.cardAlpha = 1f CardConfig.cardAlpha = 1f
} }
if (!CardConfig.isCustomDimSet) {
CardConfig.cardDim = 0.5f
}
CardConfig.save(context) CardConfig.save(context)
} }
1 -> { // 浅色 1 -> { // 浅色
@@ -803,6 +1215,9 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
if (!CardConfig.isCustomAlphaSet) { if (!CardConfig.isCustomAlphaSet) {
CardConfig.cardAlpha = 1f CardConfig.cardAlpha = 1f
} }
if (!CardConfig.isCustomDimSet) {
CardConfig.cardDim = 0f
}
CardConfig.save(context) CardConfig.save(context)
} }
0 -> { // 跟随系统 0 -> { // 跟随系统
@@ -837,6 +1252,42 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
) )
} }
// DPI 设置确认对话框
if (showDpiConfirmDialog) {
AlertDialog(
onDismissRequest = { showDpiConfirmDialog = false },
title = { Text(stringResource(R.string.dpi_confirm_title)) },
text = {
Column {
Text(stringResource(R.string.dpi_confirm_message, currentDpi, tempDpi))
Text(
stringResource(R.string.dpi_confirm_summary),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
}
},
confirmButton = {
TextButton(
onClick = { applyDpiSetting(tempDpi) }
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(
onClick = {
showDpiConfirmDialog = false
tempDpi = currentDpi
}
) {
Text(stringResource(R.string.cancel))
}
}
)
}
// 主题色选择对话框 // 主题色选择对话框
if (showThemeColorDialog) { if (showThemeColorDialog) {
AlertDialog( AlertDialog(

View File

@@ -6,6 +6,11 @@ import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -58,6 +63,7 @@ import com.sukisu.ultra.ui.util.getBugreportFile
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import com.sukisu.ultra.ui.component.KsuIsValid import com.sukisu.ultra.ui.component.KsuIsValid
import com.dergoogler.mmrl.platform.Platform
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -80,7 +86,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
AboutDialog(it) AboutDialog(it)
} }
val loadingDialog = rememberLoadingDialog() val loadingDialog = rememberLoadingDialog()
// endregion
Column( Column(
modifier = Modifier modifier = Modifier
@@ -88,12 +93,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
// region 上下文与协程
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// endregion
// region 日志导出功能
val exportBugreportLauncher = rememberLauncherForActivityResult( val exportBugreportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/gzip") ActivityResultContracts.CreateDocument("application/gzip")
) { uri: Uri? -> ) { uri: Uri? ->
@@ -183,7 +184,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
} }
// 设置分组卡片 - 应用设置 // 应用设置
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -239,6 +240,52 @@ fun SettingScreen(navigator: DestinationsNavigator) {
) )
} }
// Web X 开关
var useWebUIX by rememberSaveable {
mutableStateOf(
prefs.getBoolean("use_webuix", false)
)
}
KsuIsValid {
SwitchItem(
beta = true,
enabled = Platform.isAlive,
icon = Icons.Filled.WebAsset,
title = stringResource(id = R.string.use_webuix),
summary = stringResource(id = R.string.use_webuix_summary),
checked = useWebUIX
) {
prefs.edit { putBoolean("use_webuix", it) }
useWebUIX = it
}
}
// Web X Eruda 开关
var useWebUIXEruda by rememberSaveable {
mutableStateOf(
prefs.getBoolean("use_webuix_eruda", false)
)
}
KsuIsValid {
AnimatedVisibility(
visible = useWebUIX && enableWebDebugging,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
SwitchItem(
beta = true,
enabled = Platform.isAlive && useWebUIX && enableWebDebugging,
icon = Icons.Filled.FormatListNumbered,
title = stringResource(id = R.string.use_webuix_eruda),
summary = stringResource(id = R.string.use_webuix_eruda_summary),
checked = useWebUIXEruda
) {
prefs.edit { putBoolean("use_webuix_eruda", it) }
useWebUIXEruda = it
}
}
}
// 更多设置 // 更多设置
SettingItem( SettingItem(
icon = Icons.Filled.Settings, icon = Icons.Filled.Settings,
@@ -251,7 +298,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
} }
// 设置分组卡片 - 工具 // 工具
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View File

@@ -1,13 +1,15 @@
package com.sukisu.ultra.ui.screen
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -40,7 +42,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.component.SearchAppBar import com.sukisu.ultra.ui.component.SearchAppBar
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.util.ModuleModify import com.sukisu.ultra.ui.util.ModuleModify
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
@@ -68,7 +69,8 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
LaunchedEffect(viewModel.search) { LaunchedEffect(viewModel.search) {
if (viewModel.search.isEmpty()) { if (viewModel.search.isEmpty()) {
listState.scrollToItem(0) // 取消自动滚动到顶部的行为
// listState.scrollToItem(0)
} }
} }
@@ -165,77 +167,309 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
}, },
snackbarHost = { SnackbarHost(snackBarHostState) }, snackbarHost = { SnackbarHost(snackBarHostState) },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
bottomBar = { floatingActionButton = {
AnimatedVisibility( // 侧边悬浮按钮集合
visible = viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty(), Column(
enter = slideInVertically(initialOffsetY = { it }), verticalArrangement = Arrangement.spacedBy(8.dp),
exit = slideOutVertically(targetOffsetY = { it }) horizontalAlignment = Alignment.End
) { ) {
Surface( // 批量操作相关按钮
color = MaterialTheme.colorScheme.surfaceContainerHighest, // 只有在批量模式且有选中应用时才显示批量操作按钮
tonalElevation = cardElevation, if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
shadowElevation = cardElevation // 取消按钮
) { val cancelInteractionSource = remember { MutableInteractionSource() }
Row( val isCancelPressed by cancelInteractionSource.collectIsPressedAsState()
modifier = Modifier
.fillMaxWidth() FloatingActionButton(
.padding(16.dp), onClick = {
horizontalArrangement = Arrangement.SpaceEvenly viewModel.selectedApps = emptySet()
viewModel.showBatchActions = false
},
modifier = Modifier.size(if (isCancelPressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
shape = CircleShape,
interactionSource = cancelInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) { ) {
OutlinedButton( Row(
onClick = { verticalAlignment = Alignment.CenterVertically,
viewModel.selectedApps = emptySet() horizontalArrangement = Arrangement.Center
viewModel.showBatchActions = false
},
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.primary
)
) { ) {
Icon( Icon(
imageVector = Icons.Filled.Close, imageVector = Icons.Filled.Close,
contentDescription = null, contentDescription = stringResource(android.R.string.cancel),
modifier = Modifier.size(16.dp) modifier = Modifier.size(24.dp)
) )
Spacer(modifier = Modifier.width(6.dp)) AnimatedVisibility(
Text(stringResource(android.R.string.cancel)) visible = isCancelPressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(android.R.string.cancel),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
)
}
} }
}
Button( // 取消授权按钮
onClick = { val unauthorizeInteractionSource = remember { MutableInteractionSource() }
scope.launch { val isUnauthorizePressed by unauthorizeInteractionSource.collectIsPressedAsState()
viewModel.updateBatchPermissions(true)
}
},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(stringResource(R.string.batch_authorization))
}
Button( FloatingActionButton(
onClick = { onClick = {
scope.launch { scope.launch {
viewModel.updateBatchPermissions(false) viewModel.updateBatchPermissions(false)
} }
}, },
colors = ButtonDefaults.buttonColors( modifier = Modifier.size(if (isUnauthorizePressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.error containerColor = MaterialTheme.colorScheme.errorContainer,
) contentColor = MaterialTheme.colorScheme.onErrorContainer,
shape = CircleShape,
interactionSource = unauthorizeInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = Icons.Filled.Block, imageVector = Icons.Filled.Block,
contentDescription = null, contentDescription = stringResource(R.string.batch_cancel_authorization),
modifier = Modifier.size(16.dp) modifier = Modifier.size(24.dp)
)
AnimatedVisibility(
visible = isUnauthorizePressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(R.string.batch_cancel_authorization),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
)
}
}
}
// 授权按钮
val authorizeInteractionSource = remember { MutableInteractionSource() }
val isAuthorizePressed by authorizeInteractionSource.collectIsPressedAsState()
FloatingActionButton(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(true)
}
},
modifier = Modifier.size(if (isAuthorizePressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
shape = CircleShape,
interactionSource = authorizeInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = stringResource(R.string.batch_authorization),
modifier = Modifier.size(24.dp)
)
AnimatedVisibility(
visible = isAuthorizePressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(R.string.batch_authorization),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
)
}
}
}
// 添加分隔
Spacer(modifier = Modifier.height(8.dp))
}
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
// 在批量操作按钮组中添加卸载模块的按钮
// 卸载模块启用按钮
val umountEnableInteractionSource = remember { MutableInteractionSource() }
val isUmountEnablePressed by umountEnableInteractionSource.collectIsPressedAsState()
FloatingActionButton(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(
allowSu = false, // 不改变ROOT权限状态
umountModules = true // 启用卸载模块
)
}
},
modifier = Modifier.size(if (isUmountEnablePressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
shape = CircleShape,
interactionSource = umountEnableInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.FolderOff,
contentDescription = stringResource(R.string.profile_umount_modules),
modifier = Modifier.size(24.dp)
)
AnimatedVisibility(
visible = isUmountEnablePressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(R.string.profile_umount_modules),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
)
}
}
}
// 卸载模块禁用按钮
val umountDisableInteractionSource = remember { MutableInteractionSource() }
val isUmountDisablePressed by umountDisableInteractionSource.collectIsPressedAsState()
FloatingActionButton(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(
allowSu = false, // 不改变ROOT权限状态
umountModules = false // 禁用卸载模块
)
}
},
modifier = Modifier.size(if (isUmountDisablePressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
shape = CircleShape,
interactionSource = umountDisableInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.Folder,
contentDescription = stringResource(R.string.profile_umount_modules_disable),
modifier = Modifier.size(24.dp)
)
AnimatedVisibility(
visible = isUmountDisablePressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(R.string.profile_umount_modules_disable),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
)
}
}
// 添加分隔
Spacer(modifier = Modifier.height(8.dp))
}
}
// 向上导航按钮
val topBtnInteractionSource = remember { MutableInteractionSource() }
val isTopBtnPressed by topBtnInteractionSource.collectIsPressedAsState()
FloatingActionButton(
onClick = {
scope.launch {
listState.animateScrollToItem(0)
}
},
modifier = Modifier.size(if (isTopBtnPressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 1f),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
shape = CircleShape,
interactionSource = topBtnInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.KeyboardArrowUp,
contentDescription = stringResource(R.string.scroll_to_top_description),
modifier = Modifier.size(24.dp)
)
AnimatedVisibility(
visible = isTopBtnPressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(R.string.scroll_to_top),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
)
}
}
}
// 向下导航按钮
val bottomBtnInteractionSource = remember { MutableInteractionSource() }
val isBottomBtnPressed by bottomBtnInteractionSource.collectIsPressedAsState()
FloatingActionButton(
onClick = {
scope.launch {
val lastIndex = viewModel.appList.size - 1
if (lastIndex >= 0) {
listState.animateScrollToItem(lastIndex)
}
}
},
modifier = Modifier.size(if (isBottomBtnPressed) 56.dp else 40.dp),
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 1f),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
shape = CircleShape,
interactionSource = bottomBtnInteractionSource,
elevation = FloatingActionButtonDefaults.elevation(4.dp, 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.KeyboardArrowDown,
contentDescription = stringResource(R.string.scroll_to_bottom_description),
modifier = Modifier.size(24.dp)
)
AnimatedVisibility(
visible = isBottomBtnPressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
stringResource(R.string.scroll_to_bottom),
modifier = Modifier.padding(end = 4.dp),
style = MaterialTheme.typography.labelMedium
) )
Spacer(modifier = Modifier.width(6.dp))
Text(stringResource(R.string.batch_cancel_authorization))
} }
} }
} }
@@ -256,7 +490,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = 8.dp, top = 8.dp,
bottom = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) 88.dp else 16.dp bottom = 16.dp
) )
) { ) {
// 获取分组后的应用列表 // 获取分组后的应用列表
@@ -280,7 +514,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
val profile = Natives.getAppProfile(app.packageName, app.uid) val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu) val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) { if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList() // 不重新获取应用列表,避免滚动位置重置
// viewModel.fetchAppList()
// 仅更新当前应用的配置
viewModel.updateAppProfileLocally(app.packageName, updatedProfile)
} }
} }
}, },
@@ -319,7 +556,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
val profile = Natives.getAppProfile(app.packageName, app.uid) val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu) val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) { if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList() // 不重新获取应用列表,避免滚动位置重置
// viewModel.fetchAppList()
// 仅更新当前应用的配置
viewModel.updateAppProfileLocally(app.packageName, updatedProfile)
} }
} }
}, },
@@ -358,7 +598,10 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
val profile = Natives.getAppProfile(app.packageName, app.uid) val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu) val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) { if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList() // 不重新获取应用列表,避免滚动位置重置
// viewModel.fetchAppList()
// 仅更新当前应用的配置
viewModel.updateAppProfileLocally(app.packageName, updatedProfile)
} }
} }
}, },
@@ -539,27 +782,73 @@ private fun AppItem(
} }
if (!viewModel.showBatchActions) { if (!viewModel.showBatchActions) {
Switch( // 开关交互源
checked = app.allowSu, val switchInteractionSource = remember { MutableInteractionSource() }
onCheckedChange = onSwitchChange, val isSwitchPressed by switchInteractionSource.collectIsPressedAsState()
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary, Row(
checkedTrackColor = MaterialTheme.colorScheme.primary, verticalAlignment = Alignment.CenterVertically,
checkedIconColor = MaterialTheme.colorScheme.primary, horizontalArrangement = Arrangement.End
uncheckedThumbColor = MaterialTheme.colorScheme.outline, ) {
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant, AnimatedVisibility(
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant visible = isSwitchPressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
text = if (app.allowSu) stringResource(R.string.authorized) else stringResource(R.string.unauthorized),
style = MaterialTheme.typography.labelMedium,
color = if (app.allowSu) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline,
modifier = Modifier.padding(end = 4.dp)
)
}
Switch(
checked = app.allowSu,
onCheckedChange = onSwitchChange,
interactionSource = switchInteractionSource,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
checkedTrackColor = MaterialTheme.colorScheme.primary,
checkedIconColor = MaterialTheme.colorScheme.primary,
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
uncheckedIconColor = MaterialTheme.colorScheme.surfaceVariant
)
) )
) }
} else { } else {
Checkbox( // 复选框交互源
checked = isSelected, val checkboxInteractionSource = remember { MutableInteractionSource() }
onCheckedChange = { onToggleSelection() }, val isCheckboxPressed by checkboxInteractionSource.collectIsPressedAsState()
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.primary, Row(
uncheckedColor = MaterialTheme.colorScheme.outline verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
AnimatedVisibility(
visible = isCheckboxPressed,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Text(
text = if (isSelected) stringResource(R.string.selected) else stringResource(R.string.select),
style = MaterialTheme.typography.labelMedium,
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline,
modifier = Modifier.padding(end = 4.dp)
)
}
Checkbox(
checked = isSelected,
onCheckedChange = { onToggleSelection() },
interactionSource = checkboxInteractionSource,
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.primary,
uncheckedColor = MaterialTheme.colorScheme.outline
)
) )
) }
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import LabelText
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.widget.Toast import android.widget.Toast

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -16,10 +17,15 @@ object CardConfig {
val settingElevation: Dp = 4.dp val settingElevation: Dp = 4.dp
val customBackgroundElevation: Dp = 0.dp val customBackgroundElevation: Dp = 0.dp
var cardAlpha by mutableStateOf(1f) // 卡片透明度
var cardAlpha by mutableFloatStateOf(1f)
// 卡片亮度
var cardDim by mutableFloatStateOf(0f)
// 卡片阴影
var cardElevation by mutableStateOf(settingElevation) var cardElevation by mutableStateOf(settingElevation)
var isShadowEnabled by mutableStateOf(true) var isShadowEnabled by mutableStateOf(true)
var isCustomAlphaSet by mutableStateOf(false) var isCustomAlphaSet by mutableStateOf(false)
var isCustomDimSet by mutableStateOf(false)
var isUserDarkModeEnabled by mutableStateOf(false) var isUserDarkModeEnabled by mutableStateOf(false)
var isUserLightModeEnabled by mutableStateOf(false) var isUserLightModeEnabled by mutableStateOf(false)
var isCustomBackgroundEnabled by mutableStateOf(false) var isCustomBackgroundEnabled by mutableStateOf(false)
@@ -31,9 +37,11 @@ object CardConfig {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
prefs.edit().apply { prefs.edit().apply {
putFloat("card_alpha", cardAlpha) putFloat("card_alpha", cardAlpha)
putFloat("card_dim", cardDim)
putBoolean("custom_background_enabled", isCustomBackgroundEnabled) putBoolean("custom_background_enabled", isCustomBackgroundEnabled)
putBoolean("is_shadow_enabled", isShadowEnabled) putBoolean("is_shadow_enabled", isShadowEnabled)
putBoolean("is_custom_alpha_set", isCustomAlphaSet) putBoolean("is_custom_alpha_set", isCustomAlphaSet)
putBoolean("is_custom_dim_set", isCustomDimSet)
putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled) putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled)
putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled) putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled)
apply() apply()
@@ -46,9 +54,11 @@ object CardConfig {
fun load(context: Context) { fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat("card_alpha", 1f) cardAlpha = prefs.getFloat("card_alpha", 1f)
cardDim = prefs.getFloat("card_dim", 0f)
isCustomBackgroundEnabled = prefs.getBoolean("custom_background_enabled", false) isCustomBackgroundEnabled = prefs.getBoolean("custom_background_enabled", false)
isShadowEnabled = prefs.getBoolean("is_shadow_enabled", true) isShadowEnabled = prefs.getBoolean("is_shadow_enabled", true)
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false) isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
isCustomDimSet = prefs.getBoolean("is_custom_dim_set", false)
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false) isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false) isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false)
updateShadowEnabled(isShadowEnabled) updateShadowEnabled(isShadowEnabled)
@@ -72,9 +82,25 @@ object CardConfig {
* 设置深色模式默认值 * 设置深色模式默认值
*/ */
fun setDarkModeDefaults() { fun setDarkModeDefaults() {
if (!isCustomAlphaSet) {
cardAlpha = 0.70f
}
if (!isCustomDimSet) {
cardDim = 0.5f
}
updateShadowEnabled(isShadowEnabled)
}
/**
* 设置浅色模式默认值
*/
fun setLightModeDefaults() {
if (!isCustomAlphaSet) { if (!isCustomAlphaSet) {
cardAlpha = 1f cardAlpha = 1f
} }
if (!isCustomDimSet) {
cardDim = 0f
}
updateShadowEnabled(isShadowEnabled) updateShadowEnabled(isShadowEnabled)
} }
} }
@@ -104,4 +130,4 @@ private fun determineContentColor(originalColor: Color): Color {
isDarkTheme -> Color.White isDarkTheme -> Color.White
else -> if (originalColor.luminance() > 0.5f) Color.Black else Color.White else -> if (originalColor.luminance() > 0.5f) Color.Black else Color.White
} }
} }

View File

@@ -44,6 +44,11 @@ import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import com.sukisu.ultra.ui.util.BackgroundTransformation import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground import com.sukisu.ultra.ui.util.saveTransformedBackground
import androidx.activity.SystemBarStyle
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
/** /**
* 主题配置对象,管理应用的主题相关状态 * 主题配置对象,管理应用的主题相关状态
@@ -105,11 +110,18 @@ fun KernelSUTheme(
if (!isCustomAlphaSet) { if (!isCustomAlphaSet) {
cardAlpha = if (systemIsDark) 0.50f else 1f cardAlpha = if (systemIsDark) 0.50f else 1f
} }
if (!isCustomDimSet) {
cardDim = if (systemIsDark) 0.5f else 0f
}
save(context) save(context)
} }
} }
} }
SystemBarStyle(
darkMode = darkTheme
)
// 初始加载配置 // 初始加载配置
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
context.loadThemeMode() context.loadThemeMode()
@@ -139,6 +151,8 @@ fun KernelSUTheme(
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
if (darkTheme && !dynamicColor) { if (darkTheme && !dynamicColor) {
CardConfig.setDarkModeDefaults() CardConfig.setDarkModeDefaults()
} else if (!darkTheme && !dynamicColor) {
CardConfig.setLightModeDefaults()
} }
CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground) CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground)
@@ -190,6 +204,9 @@ fun KernelSUTheme(
} }
} }
// 计算适用的暗化值
val dimFactor = CardConfig.cardDim
MaterialTheme( MaterialTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
typography = Typography typography = Typography
@@ -225,13 +242,13 @@ fun KernelSUTheme(
) )
} }
// 亮度调节层 // 亮度调节层 (根据cardDim调整)
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background( .background(
if (darkTheme) Color.Black.copy(alpha = 0.6f) if (darkTheme) Color.Black.copy(alpha = 0.6f + dimFactor * 0.3f)
else Color.White.copy(alpha = 0.1f) else Color.White.copy(alpha = 0.1f + dimFactor * 0.2f)
) )
) )
@@ -243,8 +260,8 @@ fun KernelSUTheme(
Brush.radialGradient( Brush.radialGradient(
colors = listOf( colors = listOf(
Color.Transparent, Color.Transparent,
if (darkTheme) Color.Black.copy(alpha = 0.5f) if (darkTheme) Color.Black.copy(alpha = 0.5f + dimFactor * 0.2f)
else Color.Black.copy(alpha = 0.2f) else Color.Black.copy(alpha = 0.2f + dimFactor * 0.1f)
), ),
radius = 1200f radius = 1200f
) )
@@ -535,4 +552,33 @@ fun Context.loadDynamicColorState() {
.getBoolean("use_dynamic_color", true) .getBoolean("use_dynamic_color", true)
ThemeConfig.useDynamicColor = enabled ThemeConfig.useDynamicColor = enabled
}
@Composable
private fun SystemBarStyle(
darkMode: Boolean,
statusBarScrim: Color = Color.Transparent,
navigationBarScrim: Color = Color.Transparent,
) {
val context = LocalContext.current
val activity = context as ComponentActivity
SideEffect {
activity.enableEdgeToEdge(
statusBarStyle = SystemBarStyle.auto(
statusBarScrim.toArgb(),
statusBarScrim.toArgb(),
) { darkMode },
navigationBarStyle = when {
darkMode -> SystemBarStyle.dark(
navigationBarScrim.toArgb()
)
else -> SystemBarStyle.light(
navigationBarScrim.toArgb(),
navigationBarScrim.toArgb(),
)
}
)
}
} }

View File

@@ -1,11 +1,7 @@
package com.sukisu.ultra.ui.viewmodel package com.sukisu.ultra.ui.viewmodel
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.IBinder
import android.os.Parcelable import android.os.Parcelable
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
@@ -14,22 +10,23 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.sukisu.zako.IKsuInterface
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.KsuService
import com.sukisu.ultra.ui.util.HanziToPinyin import com.sukisu.ultra.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.KsuCli
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
import kotlin.coroutines.resume import com.dergoogler.mmrl.platform.Platform
import kotlin.coroutines.suspendCoroutine import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
import com.sukisu.ultra.ui.webui.packageManager
import com.sukisu.ultra.ui.webui.userManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
class SuperUserViewModel : ViewModel() { class SuperUserViewModel : ViewModel() {
val isPlatformAlive get() = Platform.isAlive
companion object { companion object {
private const val TAG = "SuperUserViewModel" private const val TAG = "SuperUserViewModel"
private var apps by mutableStateOf<List<AppInfo>>(emptyList()) private var apps by mutableStateOf<List<AppInfo>>(emptyList())
@@ -142,55 +139,65 @@ class SuperUserViewModel : ViewModel() {
fetchAppList() // 刷新列表以显示最新状态 fetchAppList() // 刷新列表以显示最新状态
} }
private suspend fun connectKsuService( // 批量更新权限和umount模块设置
onDisconnect: () -> Unit = {} suspend fun updateBatchPermissions(allowSu: Boolean, umountModules: Boolean? = null) {
): Pair<IBinder, ServiceConnection> = suspendCoroutine { continuation -> selectedApps.forEach { packageName ->
val connection = object : ServiceConnection { val app = apps.find { it.packageName == packageName }
override fun onServiceDisconnected(name: ComponentName?) { app?.let {
onDisconnect() val profile = Natives.getAppProfile(packageName, it.uid)
} val updatedProfile = profile.copy(
allowSu = allowSu,
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { umountModules = umountModules ?: profile.umountModules,
continuation.resume(binder as IBinder to this) nonRootUseDefault = false
)
if (Natives.setAppProfile(updatedProfile)) {
apps = apps.map { app ->
if (app.packageName == packageName) {
app.copy(profile = updatedProfile)
} else {
app
}
}
}
} }
} }
clearSelection()
val intent = Intent(ksuApp, KsuService::class.java) showBatchActions = false // 批量操作完成后退出批量模式
fetchAppList() // 刷新列表以显示最新状态
val task = KsuService.bindOrTask(
intent,
Shell.EXECUTOR,
connection,
)
val shell = KsuCli.SHELL
task?.let { it1 -> shell.execTask(it1) }
} }
private fun stopKsuService() { // 仅更新本地应用配置,避免重新获取整个列表导致滚动位置重置
val intent = Intent(ksuApp, KsuService::class.java) fun updateAppProfileLocally(packageName: String, updatedProfile: Natives.Profile) {
KsuService.stop(intent) apps = apps.map { app ->
if (app.packageName == packageName) {
app.copy(profile = updatedProfile)
} else {
app
}
}
} }
suspend fun fetchAppList() { suspend fun fetchAppList() {
isRefreshing = true isRefreshing = true
val result = connectKsuService {
Log.w(TAG, "KsuService disconnected")
}
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
withTimeoutOrNull(TIMEOUT_MILLIS) {
while (!isPlatformAlive) {
delay(500)
}
} ?: return@withContext // Exit early if timeout
val pm = ksuApp.packageManager val pm = ksuApp.packageManager
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()
val binder = result.first val userInfos = Platform.userManager.getUsers()
val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0) val packages = mutableListOf<PackageInfo>()
val packageManager = Platform.packageManager
withContext(Dispatchers.Main) { for (userInfo in userInfos) {
stopKsuService() Log.i(TAG, "fetchAppList: ${userInfo.id}")
packages.addAll(packageManager.getInstalledPackages(0, userInfo.id))
} }
val packages = allPackages.list
apps = packages.map { apps = packages.map {
val appInfo = it.applicationInfo val appInfo = it.applicationInfo
val uid = appInfo!!.uid val uid = appInfo!!.uid

View File

@@ -0,0 +1,62 @@
package com.sukisu.ultra.ui.webui
import android.content.ServiceConnection
import android.util.Log
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.hiddenApi.HiddenPackageManager
import com.dergoogler.mmrl.platform.hiddenApi.HiddenUserManager
import com.dergoogler.mmrl.platform.model.IProvider
import com.dergoogler.mmrl.platform.model.PlatformIntent
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.Natives
import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
class KsuLibSuProvider : IProvider {
override val name = "KsuLibSu"
override fun isAvailable() = true
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName)
private val serviceIntent
get() = PlatformIntent(
ksuApp,
Platform.KsuNext,
SuService::class.java
)
override fun bind(connection: ServiceConnection) {
RootService.bind(serviceIntent.intent, connection)
}
override fun unbind(connection: ServiceConnection) {
RootService.stop(serviceIntent.intent)
}
}
// webui x
suspend fun initPlatform() = withContext(Dispatchers.IO) {
try {
val active = Platform.init {
this.context = ksuApp
this.platform = Platform.KsuNext
this.provider = from(KsuLibSuProvider())
}
while (!active) {
delay(1000)
}
return@withContext active
} catch (e: Exception) {
Log.e("KsuLibSu", "Failed to initialize platform", e)
return@withContext false
}
}
val Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService)
val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService)

View File

@@ -0,0 +1,14 @@
package com.sukisu.ultra.ui.webui
import android.content.Intent
import android.os.IBinder
import com.dergoogler.mmrl.platform.model.PlatformIntent.Companion.getPlatform
import com.dergoogler.mmrl.platform.service.ServiceManager
import com.topjohnwu.superuser.ipc.RootService
class SuService : RootService() {
override fun onBind(intent: Intent): IBinder {
val mode = intent.getPlatform()
return ServiceManager(mode)
}
}

View File

@@ -15,9 +15,11 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader
import com.dergoogler.mmrl.platform.model.ModId
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.ui.util.createRootShell import com.sukisu.ultra.ui.util.createRootShell
import java.io.File import java.io.File
import com.dergoogler.mmrl.webui.interfaces.WXOptions
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
class WebUIActivity : ComponentActivity() { class WebUIActivity : ComponentActivity() {
@@ -41,7 +43,8 @@ class WebUIActivity : ComponentActivity() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name")) setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
} else { } else {
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build() val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
setTaskDescription(taskDescription) setTaskDescription(taskDescription)
} }
@@ -82,7 +85,7 @@ class WebUIActivity : ComponentActivity() {
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
settings.domStorageEnabled = true settings.domStorageEnabled = true
settings.allowFileAccess = false settings.allowFileAccess = false
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir) webviewInterface = WebViewInterface(WXOptions(this@WebUIActivity, this, ModId(moduleId)))
addJavascriptInterface(webviewInterface, "ksu") addJavascriptInterface(webviewInterface, "ksu")
setWebViewClient(webViewClient) setWebViewClient(webViewClient)
loadUrl("https://mui.kernelsu.org/index.html") loadUrl("https://mui.kernelsu.org/index.html")

View File

@@ -0,0 +1,275 @@
package com.sukisu.ultra.ui.webui
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
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.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.paint
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.zIndex
import android.os.Build
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.updateTransition
import androidx.compose.ui.graphics.graphicsLayer
import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.Typography
import com.sukisu.ultra.ui.theme.loadCustomBackground
// 提供界面类型的本地组合
val LocalIsSecondaryScreen = staticCompositionLocalOf { false }
/**
* WebUI专用主题配置
*/
@Composable
fun WebUIXTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
isSecondaryScreen: Boolean = false,
content: @Composable () -> Unit
) {
val context = LocalContext.current
LaunchedEffect(Unit) {
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
context.loadCustomBackground()
ThemeConfig.backgroundImageLoaded = false
}
}
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) {
dynamicDarkColorScheme(context).let { scheme ->
if (isSecondaryScreen) {
scheme.copy(
background = scheme.surfaceContainerHighest,
surface = scheme.surfaceContainerHighest
)
} else {
scheme.copy(
background = Color.Transparent,
surface = Color.Transparent
)
}
}
} else {
dynamicLightColorScheme(context).let { scheme ->
if (isSecondaryScreen) {
scheme.copy(
background = scheme.surfaceContainerHighest,
surface = scheme.surfaceContainerHighest
)
} else {
scheme.copy(
background = Color.Transparent,
surface = Color.Transparent
)
}
}
}
}
darkTheme -> {
if (isSecondaryScreen) {
darkColorScheme().copy(
background = MaterialTheme.colorScheme.surfaceContainerHighest,
surface = MaterialTheme.colorScheme.surfaceContainerHighest
)
} else {
darkColorScheme().copy(
background = Color.Transparent,
surface = Color.Transparent
)
}
}
else -> {
if (isSecondaryScreen) {
lightColorScheme().copy(
background = MaterialTheme.colorScheme.surfaceContainerHighest,
surface = MaterialTheme.colorScheme.surfaceContainerHighest
)
} else {
lightColorScheme().copy(
background = Color.Transparent,
surface = Color.Transparent
)
}
}
}
ConfigureSystemBars(darkTheme)
val backgroundUri = remember { mutableStateOf(ThemeConfig.customBackgroundUri) }
LaunchedEffect(ThemeConfig.customBackgroundUri) {
backgroundUri.value = ThemeConfig.customBackgroundUri
}
val bgImagePainter = backgroundUri.value?.let {
rememberAsyncImagePainter(
model = it,
onError = {
ThemeConfig.backgroundImageLoaded = false
},
onSuccess = {
ThemeConfig.backgroundImageLoaded = true
ThemeConfig.isThemeChanging = false
}
)
}
// 背景透明度动画
val transition = updateTransition(
targetState = ThemeConfig.backgroundImageLoaded,
label = "bgTransition"
)
val bgAlpha by transition.animateFloat(
label = "bgAlpha",
transitionSpec = {
spring(
dampingRatio = 0.8f,
stiffness = 300f
)
}
) { loaded -> if (loaded) 1f else 0f }
CompositionLocalProvider(LocalIsSecondaryScreen provides isSecondaryScreen) {
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
) {
if (isSecondaryScreen) {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
) {
content()
}
} else {
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(-2f)
.background(if (darkTheme) Color.Black else Color.White)
)
backgroundUri.value?.let { uri ->
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(-1f)
.alpha(bgAlpha)
) {
bgImagePainter?.let { painter ->
Box(
modifier = Modifier
.fillMaxSize()
.paint(
painter = painter,
contentScale = ContentScale.Crop
)
.graphicsLayer {
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
}
)
}
Box(
modifier = Modifier
.fillMaxSize()
.background(
if (darkTheme) Color.Black.copy(alpha = 0.6f)
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()
}
}
}
}
}
}
/**
* 获取当前界面是否为二级界面
*/
@Composable
fun isSecondaryScreen(): Boolean {
return LocalIsSecondaryScreen.current
}
/**
* 配置WebUI的系统栏样式
*/
@Composable
private fun ConfigureSystemBars(
darkMode: Boolean,
statusBarScrim: Color = Color.Transparent,
navigationBarScrim: Color = Color.Transparent
) {
val context = LocalContext.current
val activity = context as ComponentActivity
SideEffect {
activity.enableEdgeToEdge(
statusBarStyle = SystemBarStyle.auto(
statusBarScrim.toArgb(),
statusBarScrim.toArgb()
) { darkMode },
navigationBarStyle = when {
darkMode -> SystemBarStyle.dark(
navigationBarScrim.toArgb()
)
else -> SystemBarStyle.light(
navigationBarScrim.toArgb(),
navigationBarScrim.toArgb()
)
}
)
}
}

View File

@@ -0,0 +1,111 @@
package com.sukisu.ultra.ui.webui
import android.app.ActivityManager
import android.os.Build
import android.os.Bundle
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.lifecycleScope
import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.model.ModId
import com.dergoogler.mmrl.ui.component.Loading
import com.dergoogler.mmrl.webui.screen.WebUIScreen
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
import com.sukisu.ultra.BuildConfig
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class WebUIXActivity : ComponentActivity() {
private lateinit var webView: WebView
private val userAgent
get(): String {
val ksuVersion = BuildConfig.VERSION_CODE
val platform = Platform.get("Unknown") {
platform.name
}
val platformVersion = Platform.get(-1) {
moduleManager.versionCode
}
val osVersion = Build.VERSION.RELEASE
val deviceModel = Build.MODEL
return "SukiSU /$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
webView = WebView(this)
lifecycleScope.launch {
initPlatform()
}
val moduleId = intent.getStringExtra("id")!!
val name = intent.getStringExtra("name")!!
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
} else {
val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
setTaskDescription(taskDescription)
}
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
setContent {
WebUIXTheme {
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(Platform.isAlive) {
while (!Platform.isAlive) {
delay(1000)
}
isLoading = false
}
if (isLoading) {
Loading()
return@WebUIXTheme
}
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
val dark = isSystemInDarkTheme()
val options = rememberWebUIOptions(
modId = ModId(moduleId),
debug = webDebugging,
appVersionCode = BuildConfig.VERSION_CODE,
isDarkMode = dark,
enableEruda = erudaInject,
cls = WebUIXActivity::class.java,
userAgentString = userAgent
)
WebUIScreen(
webView = webView,
options = options,
interfaces = listOf(
WebViewInterface.factory()
)
)
}
}
}
}

View File

@@ -1,16 +1,17 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.app.Activity import android.app.Activity
import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.TextUtils import android.text.TextUtils
import android.view.Window import android.view.Window
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.dergoogler.mmrl.webui.interfaces.WebUIInterface
import com.dergoogler.mmrl.webui.model.JavaScriptInterface
import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
@@ -23,12 +24,24 @@ import com.sukisu.ultra.ui.util.controlKpmModule
import com.sukisu.ultra.ui.util.listKpmModules import com.sukisu.ultra.ui.util.listKpmModules
import java.io.File import java.io.File
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import androidx.compose.runtime.Composable
class WebViewInterface( class WebViewInterface(
val context: Context, wxOptions: WXOptions,
private val webView: WebView, ) : WebUIInterface(wxOptions) {
private val modDir: String override var name: String = "ksu"
) {
companion object {
fun factory() = JavaScriptInterface(WebViewInterface::class.java)
}
private val modDir get() = "/data/adb/modules/${modId.id}"
@Composable
@JavascriptInterface
fun isSecondaryPage(): Boolean {
return isSecondaryScreen()
}
@JavascriptInterface @JavascriptInterface
fun exec(cmd: String): String { fun exec(cmd: String): String {
@@ -170,9 +183,9 @@ class WebViewInterface(
if (context is Activity) { if (context is Activity) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
if (enable) { if (enable) {
hideSystemUI(context.window) hideSystemUI(activity.window)
} else { } else {
showSystemUI(context.window) showSystemUI(activity.window)
} }
} }
} }

View File

@@ -109,14 +109,14 @@
<string name="app_profile_template_import_empty">クリップボードが空です!</string> <string name="app_profile_template_import_empty">クリップボードが空です!</string>
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string> <string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
<string name="settings_check_update">更新を確認する</string> <string name="settings_check_update">更新を確認する</string>
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string> <string name="settings_check_update_summary">アプリの起動時に更新を自動確認します</string>
<string name="grant_root_failed">root の付与に失敗しました!</string> <string name="grant_root_failed">root の付与に失敗しました!</string>
<string name="action">アクション</string> <string name="action">アクション</string>
<string name="open">開く</string> <string name="open">開く</string>
<string name="enable_web_debugging">WebView デバッグを有効化する</string> <string name="enable_web_debugging">WebView デバッグを有効化する</string>
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合でのみ有効化してください</string> <string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合でのみ有効化してください</string>
<string name="direct_install">直接インストール (推奨)</string> <string name="direct_install">直接インストール (推奨)</string>
<string name="select_file">パッチを適用する必要があるミラーを選択</string> <string name="select_file">パッチを行うイメージを選択してください</string>
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string> <string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
<string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。 <string name="install_inactive_slot_warning">再起動後、デバイスは**強制的に**、現在非アクティブなスロットから起動します。
\nこのオプションは、OTA が完了した後にのみ使用してください。 \nこのオプションは、OTA が完了した後にのみ使用してください。
@@ -170,7 +170,7 @@
<string name="allowlist_restore_failed">許可リストの復元に失敗: %1$s</string> <string name="allowlist_restore_failed">許可リストの復元に失敗: %1$s</string>
<string name="backup_allowlist">許可リストをバックアップ</string> <string name="backup_allowlist">許可リストをバックアップ</string>
<string name="restore_allowlist">許可リストを復元</string> <string name="restore_allowlist">許可リストを復元</string>
<string name="settings_custom_background">カスタムアプリ背景</string> <string name="settings_custom_background">アプリ背景を変更</string>
<string name="settings_custom_background_summary">背景にする画像を選択してください</string> <string name="settings_custom_background_summary">背景にする画像を選択してください</string>
<string name="settings_card_alpha">ナビゲーションバーの透過</string> <string name="settings_card_alpha">ナビゲーションバーの透過</string>
<string name="settings_restore_default">デフォルトに復元</string> <string name="settings_restore_default">デフォルトに復元</string>
@@ -188,14 +188,14 @@
<string name="selinux_disabled">無効</string> <string name="selinux_disabled">無効</string>
<string name="simple_mode">シンプルモード</string> <string name="simple_mode">シンプルモード</string>
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string> <string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string> <string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示</string>
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string> <string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
<string name="hide_other_info">その他の情報を非表示にする</string> <string name="hide_other_info">その他の情報を非表示</string>
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string> <string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
<string name="hide_susfs_status">SuSFS ステータスを非表示にする</string> <string name="hide_susfs_status">SuSFS ステータスを非表示</string>
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string> <string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
<string name="hide_link_card">リンクカードのステータスを隠す</string> <string name="hide_link_card">リンクカードのステータスを非表示</string>
<string name="hide_link_card_summary">ホームページのリンクカード情報を</string> <string name="hide_link_card_summary">ホームページのリンクカード情報を非表示にしま</string>
<string name="theme_mode">テーマ</string> <string name="theme_mode">テーマ</string>
<string name="theme_follow_system">システムに従う</string> <string name="theme_follow_system">システムに従う</string>
<string name="theme_light">ライト</string> <string name="theme_light">ライト</string>
@@ -222,12 +222,11 @@
<string name="yes">はい</string> <string name="yes">はい</string>
<string name="no">いいえ</string> <string name="no">いいえ</string>
<string name="failed_reboot">再起動に失敗しました</string> <string name="failed_reboot">再起動に失敗しました</string>
<string name="batch_authorization">Bulk ライセンス</string> <string name="batch_authorization">権限を付与</string>
<string name="batch_cancel_authorization">認証を一括でキャンセル</string> <string name="batch_cancel_authorization">撤回する</string>
<string name="backup">バックアップ</string> <string name="backup">バックアップ</string>
<string name="color_yellow">イエロー</string> <string name="color_yellow">イエロー</string>
<string name="kpm">カーネルモジュール</string> <string name="kpm_title">KPM</string>
<string name="kpm_title">カーネルモジュール</string>
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string> <string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
<string name="kpm_version">バージョン</string> <string name="kpm_version">バージョン</string>
<string name="kpm_author">作者</string> <string name="kpm_author">作者</string>
@@ -245,7 +244,7 @@
<string name="home_ContributionCard_kernelsu">SukiSU Ultra の今後にご期待ください</string> <string name="home_ContributionCard_kernelsu">SukiSU Ultra の今後にご期待ください</string>
<string name="kpm_control_success">成功</string> <string name="kpm_control_success">成功</string>
<string name="kpm_control_failed">失敗</string> <string name="kpm_control_failed">失敗</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra は将来的に KSU から比較的に独立したブランチになりますが、公式の KernelSU や MKSU などの貢献は引き続き感謝しています!</string> <string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra は将来的に KSU から比較的に独立したブランチになりますが、公式の KernelSU や MKSU などの貢献感謝しています!</string>
<string name="not_supported">非対応</string> <string name="not_supported">非対応</string>
<string name="supported">対応</string> <string name="supported">対応</string>
<string name="home_kpm_module">"KPM モジュールの数: %d "</string> <string name="home_kpm_module">"KPM モジュールの数: %d "</string>
@@ -299,7 +298,7 @@
<string name="flash_failed_message">フラッシュに失敗しました</string> <string name="flash_failed_message">フラッシュに失敗しました</string>
<!-- lkm/gki install --> <!-- lkm/gki install -->
<string name="Lkm_install_methods">LKM の修復またはインストール</string> <string name="Lkm_install_methods">LKM の修復またはインストール</string>
<string name="GKI_install_methods">GKI/non-GKI のインストール</string> <string name="GKI_install_methods">GKI または 非 GKI のインストール</string>
<string name="kernel_version_log">カーネルのバージョン: %1$s</string> <string name="kernel_version_log">カーネルのバージョン: %1$s</string>
<string name="tool_version_log">パッチ適用ツールの使用: %1$s</string> <string name="tool_version_log">パッチ適用ツールの使用: %1$s</string>
<string name="configuration">設定</string> <string name="configuration">設定</string>
@@ -318,11 +317,38 @@
<string name="advanced_settings">高度な設定</string> <string name="advanced_settings">高度な設定</string>
<string name="appearance_settings">ツールバーをカスタマイズ</string> <string name="appearance_settings">ツールバーをカスタマイズ</string>
<string name="back">戻る</string> <string name="back">戻る</string>
<string name="expand">最高の状態</string> <string name="expand">展開する</string>
<string name="collapse">設置</string> <string name="collapse">折りたたむ</string>
<string name="susfs_enabled">SuSFS 有効</string> <string name="susfs_enabled">SuSFS 有効</string>
<string name="susfs_disabled">SuSFS 無効</string> <string name="susfs_disabled">SuSFS 無効</string>
<string name="background_set_success">背景の設定が成功しました</string> <string name="background_set_success">背景の設定が成功しました</string>
<string name="background_removed">カスタム背景を削除しました</string> <string name="background_removed">カスタム背景を削除しました</string>
<string name="root_require_for_install">root 権限が必要</string> <string name="root_require_for_install">root 権限が必要です</string>
<!-- KPM display settings -->
<string name="show_kpm_info">KPM 機能を表示</string>
<string name="show_kpm_info_summary">KPM の情報と機能をホームとボトムバーに表示します (アプリを開き直す必要があります)</string>
<!-- Webui X settings -->
<string name="use_webuix">WebUI X を使用する</string>
<string name="use_webuix_summary">より多くの API をサポートする WebUI の代わりに WebUI X を使用します</string>
<string name="use_webuix_eruda">WebUI に Eruda をインジェクトする</string>
<string name="use_webuix_eruda_summary">デバッグを容易にするために WebUI X にデバッグコンソールを挿入します。Web デバッグが ON になっている必要があります。</string>
<!-- DPI setting related strings -->
<string name="dpi_settings">DPI の設定</string>
<string name="app_dpi_title">DPI の変更を適用</string>
<string name="app_dpi_summary">このアプリのみで画面表示密度を調整します</string>
<string name="dpi_size_small"></string>
<string name="dpi_size_medium"></string>
<string name="dpi_size_large"></string>
<string name="dpi_size_extra_large">特大</string>
<string name="dpi_size_custom">カスタマイズ</string>
<string name="dpi_apply_settings">DPI の設定を適用する</string>
<string name="dpi_confirm_title">DPI の変更を確認</string>
<string name="dpi_confirm_message">アプリの DPI を %1$d から %2$d に変更してもよろしいですか?</string>
<string name="dpi_confirm_summary">変更した DPI 設定を適用するにはアプリを再起動する必要がありますが、システムステータスバーや他のアプリには影響しません</string>
<string name="dpi_applied_success">DPI は %1$d に変更されました。アプリの再起動後に適用されます。</string>
<!-- Language settings related strings -->
<string name="language_setting">アプリの言語</string>
<string name="language_follow_system">システムに従う</string>
<string name="language_changed">言語の変更を適用するために再起動しています</string>
<string name="settings_card_dim">カードの暗さを調整</string>
</resources> </resources>

View File

@@ -1,32 +1,33 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="home">Home</string> <string name="app_name" translatable="false">SukiSU Ultra</string>
<string name="home_not_installed">Chưa cài đặt</string> <string name="home">Trang chủ</string>
<string name="home_click_to_install">Nhấn vào đây để cài đặt</string> <string name="home_not_installed">Hỗ trợ nhưng chưa cài đặt</string>
<string name="home_click_to_install">Bấm để cài đặt</string>
<string name="home_working">Đang hoạt động</string> <string name="home_working">Đang hoạt động</string>
<string name="home_working_version">Phiên bản: %d</string> <string name="home_working_version">Phiên bản: %d</string>
<string name="home_superuser_count">Superusers: %d</string> <string name="home_superuser_count">Ứng dụng đã cấp SU: %d</string>
<string name="home_module_count">Mô-đun: %d</string> <string name="home_module_count">Modules: %d</string>
<string name="home_unsupported">Không được hỗ trợ</string> <string name="home_unsupported">Không được hỗ trợ</string>
<string name="home_unsupported_reason">Không phát hiện được trình điều khiển KernelSU trên kernel của bạn.</string> <string name="home_unsupported_reason">Không phát hiện được trình điều khiển SukiSU Ultra trên Kernel của bạn, Kernel sai?</string>
<string name="home_kernel">Phiên bản Kernel</string> <string name="home_kernel">Phiên bản Kernel</string>
<string name="home_susfs">SuSFS: %s</string> <string name="home_susfs">SuSFS: %s</string>
<string name="home_susfs_version">Phiên bản SuSFS</string> <string name="home_susfs_version">Phiên bản SuSFS</string>
<string name="home_susfs_sus_su">SuS SU</string> <string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">Phiên bản quản lý</string> <string name="home_manager_version">Phiên bản trình quản lý</string>
<string name="home_fingerprint">Dấu vân tay</string> <string name="home_fingerprint">Dấu vân tay</string>
<string name="home_selinux_status">Trạng thái SELinux</string> <string name="home_selinux_status">Trạng thái SELinux</string>
<string name="selinux_status_disabled">Disabled</string> <string name="selinux_status_disabled">Vô hiệu hoá</string>
<string name="selinux_status_enforcing">Enforcing</string> <string name="selinux_status_enforcing">Enforcing</string>
<string name="selinux_status_permissive">Permissive</string> <string name="selinux_status_permissive">Permissive</string>
<string name="selinux_status_unknown">Không </string> <string name="selinux_status_unknown">Không xác định</string>
<string name="superuser">Superuser</string> <string name="superuser">Superuser</string>
<string name="module_failed_to_enable">Không thể bật mô-đun: %s</string> <string name="module_failed_to_enable">Không thể kích hoạt module: %s</string>
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string> <string name="module_failed_to_disable">Không thể vô hiệu hoá module: %s</string>
<string name="module_empty">Không có mô-đun nào được cài đặt</string> <string name="module_empty">Chưa cài module nào</string>
<string name="module">Mô-đun</string> <string name="module">Modules</string>
<string name="module_sort_action_first">Sắp xếp (theo hành động)</string> <string name="module_sort_action_first">Sắp xếp (Theo hành động)</string>
<string name="module_sort_enabled_first">Sắp xếp (theo trạng thái)</string> <string name="module_sort_enabled_first">Sắp xếp (Theo trạng thái)</string>
<string name="uninstall">Gỡ cài đặt</string> <string name="uninstall">Gỡ cài đặt</string>
<string name="restore">Khôi phục</string> <string name="restore">Khôi phục</string>
<string name="module_install">Cài đặt</string> <string name="module_install">Cài đặt</string>
@@ -36,63 +37,63 @@
<string name="reboot_userspace">Khởi động lại không gian người dùng</string> <string name="reboot_userspace">Khởi động lại không gian người dùng</string>
<string name="reboot_recovery">Khởi động lại vào Recovery</string> <string name="reboot_recovery">Khởi động lại vào Recovery</string>
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string> <string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
<string name="reboot_download">Khởi động lại vào Download mode</string> <string name="reboot_download">Khởi động lại vào Download Mode</string>
<string name="reboot_edl">Khởi động lại vào EDL</string> <string name="reboot_edl">Khởi động lại vào EDL</string>
<string name="about">Thông tin thêm</string> <string name="about">Thông tin</string>
<string name="module_uninstall_confirm">Bạn có chắc chắn muốn gỡ cài đặt mô-đun %s không?</string> <string name="module_uninstall_confirm">Bạn có THẬT SỰ muốn gỡ cài đặt module %s không?</string>
<string name="module_uninstall_success">%s đã gỡ cài đặt</string> <string name="module_uninstall_success">%s đã được gỡ cài đặt</string>
<string name="module_uninstall_failed">Không thể gỡ cài đặt: %s</string> <string name="module_uninstall_failed">Gỡ cài đặt thất bại: %s</string>
<string name="module_version">Phiên bản</string> <string name="module_version">Phiên bản</string>
<string name="module_author">Tác giả</string> <string name="module_author">Tác giả</string>
<string name="refresh">Làm mới</string> <string name="refresh">Làm mới</string>
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string> <string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string> <string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
<string name="send_log">Gửi nhật ký</string> <string name="send_log">Gửi logs</string>
<string name="safe_mode">Chế độ an toàn</string> <string name="safe_mode">CHẾ ĐỘ AN TOÀN</string>
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string> <string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
<string name="module_magisk_conflict">Các mô-đun không khả dụng do xung đột với Magisk!</string> <string name="module_magisk_conflict">Các module không khả dụng do xung đột với Magisk!</string>
<string name="home_learn_kernelsu">Tìm hiểu KernelSU</string> <string name="home_learn_kernelsu">Tìm hiểu về KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string> <string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun</string> <string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các Module!</string>
<string name="home_support_title">Hỗ trợ chúng tôi</string> <string name="home_support_title">Hỗ trợ chúng tôi</string>
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string> <string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp!</string>
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string> <string name="about_source_code"><![CDATA[Xem mã nguồn tại %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
<string name="profile" translatable="false">Hồ sơ ứng dụng</string>
<string name="profile_default">Mặc định</string> <string name="profile_default">Mặc định</string>
<string name="profile_template">Bản mẫu</string> <string name="profile_template">Bản mẫu</string>
<string name="profile_custom">Tùy chỉnh</string> <string name="profile_custom">Tuỳ chỉnh</string>
<string name="profile_name">Tên hồ sơ</string> <string name="profile_name">Tên hồ sơ</string>
<string name="profile_namespace">Tên không gian gắn kết</string> <string name="profile_namespace">Tên không gian gắn kết</string>
<string name="profile_namespace_inherited">Được thừa hưởng</string> <string name="profile_namespace_inherited">Thừa hưởng</string>
<string name="profile_namespace_global">Toàn cầu</string> <string name="profile_namespace_global">Chung</string>
<string name="profile_namespace_individual">Cá nhân</string> <string name="profile_namespace_individual">Riêng biệt</string>
<string name="profile_groups">Nhóm</string> <string name="profile_groups">Nhóm</string>
<string name="profile_capabilities">Khả năng</string> <string name="profile_capabilities">Tính tương thích</string>
<string name="profile_selinux_context">Bối cảnh SELinux</string> <string name="profile_selinux_context">Bối cảnh SELinux</string>
<string name="profile_umount_modules">Bỏ gắn kết mô-đun</string> <string name="profile_umount_modules">Bỏ gắn kết Modules</string>
<string name="failed_to_update_app_profile">Không cập nhật được Hồ sơ ứng dụng cho %s</string> <string name="failed_to_update_app_profile">Cập nhật hồ sơ ứng dụng thất bại cho %s</string>
<string name="require_kernel_version" formatted="false">Phiên bản KernelSU hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng nâng cấp lên phiên bản %d hoặc cao hơn!</string> <string name="require_kernel_version" formatted="false">Phiên bản SukiSU Ultra hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng cập nhật lên phiên bản %d hoặc cao hơn!</string>
<string name="settings_umount_modules_default">Bỏ gắn kết các mô-đun theo mặc định</string> <string name="settings_umount_modules_default">Bỏ gắn kết các Module cho toàn hệ thống</string>
<string name="settings_umount_modules_default_summary">Giá trị mặc định cho \"Bỏ gắn kết các mô-đun\" trong Hồ sơ ứng dụng. Nếu được bật, nó sẽ xóa tất cả các sửa đổi của mô-đun đối với hệ thống đối và các ứng dụng không có hồ sơ được đặt.</string> <string name="settings_umount_modules_default_summary">Giá trị mặc định chung cho \"Bỏ gắn kết các Module\" trong Hồ sơ ứng dụng. Nếu được bật, mọi thay đổi hệ thống do các Module gây ra sẽ bị gỡ bỏ khỏi hệ thống và các ứng dụng chưa thiết lập hồ sơ</string>
<string name="settings_susfs_toggle">Ẩn móc kprobe</string> <string name="settings_susfs_toggle">Ẩn hooks kprobe</string>
<string name="settings_susfs_toggle_summary">Vô hiệu hóa các móc kprobe do KSU tạo ra và kích hoạt các móc không phải kprobe tích hợp sẵn, triển khai sẽ được áp dụng cho hạt nhân không phải GKI, không hỗ trợ krp</string> <string name="profile_umount_modules_summary">Bật tùy chọn này sẽ cho phép SukiSU Ultra khôi phục mọi tệp đã được các Module sửa đổi cho ứng dụng này</string>
<string name="profile_umount_modules_summary">Bật tùy chọn này sẽ cho phép KernelSU khôi phục mọi tệp đã được các mô-đun sửa đổi cho ứng dụng này.</string> <string name="profile_selinux_domain">Tên miền</string>
<string name="profile_selinux_domain">Domain</string>
<string name="profile_selinux_rules">Quy tắc</string> <string name="profile_selinux_rules">Quy tắc</string>
<string name="module_update">Cập nhật</string> <string name="module_update">Cập nhật</string>
<string name="module_downloading">Đang tải xuống mô-đun: %s</string> <string name="module_downloading">Tải xuống module: %s</string>
<string name="module_start_downloading">Bắt đầu tải xuống: %s</string> <string name="module_start_downloading">Đang tải xuống module: %s</string>
<string name="new_version_available">Phiên bản mới %s đã có sẵn, hãy nhấp để cập nhật.</string> <string name="new_version_available">Phiên bản mới %s đã có sẵn, hãy nhấp để cập nhật</string>
<string name="launch_app">Khởi chạy</string> <string name="launch_app">Mở</string>
<string name="force_stop_app" formatted="false">Buộc dừng</string> <string name="force_stop_app" formatted="false">Buộc dừng</string>
<string name="restart_app">Khởi động lại</string> <string name="restart_app">Khởi động lại</string>
<string name="failed_to_update_sepolicy">Không cập nhật được quy tắc SELinux cho %s</string> <string name="failed_to_update_sepolicy">Không cập nhật được quy tắc SELinux cho %s</string>
<string name="module_changelog">Nhật ký thay đổi</string> <string name="module_changelog">Changelog</string>
<string name="settings_profile_template">Mẫu hồ sơ ứng dụng</string> <string name="settings_profile_template">Mẫu hồ sơ ứng dụng</string>
<string name="settings_profile_template_summary">Quản lý mẫu cục bộ và trực tuyến của Hồ sơ ứng dụng</string> <string name="settings_profile_template_summary">Quản lý mẫu cục bộ và trực tuyến của Hồ sơ ứng dụng</string>
<string name="app_profile_template_create">Tạo mẫu</string> <string name="app_profile_template_create">Tạo mẫu</string>
<string name="app_profile_template_edit">Chỉnh sửa mẫu</string> <string name="app_profile_template_edit">Chỉnh sửa mẫu</string>
<string name="app_profile_template_id">ID</string> <string name="app_profile_template_id">ID</string>
<string name="app_profile_template_id_invalid">ID mẫu không hợp lệ</string> <string name="app_profile_template_id_invalid">ID mẫu không hợp lệ/tồn tại</string>
<string name="app_profile_template_name">Tên</string> <string name="app_profile_template_name">Tên</string>
<string name="app_profile_template_description">Miêu tả</string> <string name="app_profile_template_description">Miêu tả</string>
<string name="app_profile_template_save">Lưu</string> <string name="app_profile_template_save">Lưu</string>
@@ -103,97 +104,99 @@
<string name="app_profile_import_export">Nhập/Xuất</string> <string name="app_profile_import_export">Nhập/Xuất</string>
<string name="app_profile_import_from_clipboard">Nhập từ bộ nhớ tạm clipboard</string> <string name="app_profile_import_from_clipboard">Nhập từ bộ nhớ tạm clipboard</string>
<string name="app_profile_export_to_clipboard">Xuất vào bộ nhớ tạm clipboard</string> <string name="app_profile_export_to_clipboard">Xuất vào bộ nhớ tạm clipboard</string>
<string name="app_profile_template_export_empty">Cannot find local template to export!</string> <string name="app_profile_template_export_empty">Không tìm thấy hồ sơ nội bộ để xuất!</string>
<string name="app_profile_template_import_success">Đã nhập thành công</string> <string name="app_profile_template_import_success">Nhập thành công</string>
<string name="app_profile_template_sync">Đồng bộ mẫu trực tuyến</string> <string name="app_profile_template_sync">Đồng bộ với hồ sơ ứng dụng trực tuyến</string>
<string name="app_profile_template_save_failed">Không lưu được mẫu</string> <string name="app_profile_template_save_failed">Lưu hồ sơ ứng dụng thất bại</string>
<string name="app_profile_template_import_empty">Bộ nhớ tạm đang trống!</string> <string name="app_profile_template_import_empty">Bộ nhớ tạm đang trống!</string>
<string name="module_changelog_failed">Lấy nhật ký thay đổi không thành công: %s</string> <string name="module_changelog_failed">Lấy changelog thất bại: %s</string>
<string name="settings_check_update">Kiểm tra cập nhật</string> <string name="settings_check_update">Kiểm tra cập nhật</string>
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string> <string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
<string name="grant_root_failed">Không cấp được quyền root!</string> <string name="grant_root_failed">Cấp quyền root thất bại!</string>
<string name="action">Khởi chạy</string> <string name="action">Khởi chạy</string>
<string name="open">Mở</string> <string name="open">Mở</string>
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string> <string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
<string name="enable_web_debugging_summary">Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết.</string> <string name="enable_web_debugging_summary">Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết</string>
<string name="direct_install">Cài đặt trực tiếp (Khuyến nghị)</string> <string name="direct_install">Cài đặt trực tiếp (Khuyến nghị)</string>
<string name="select_file">Chọn một tập tin</string> <string name="select_file">Chọn tệp .img cần vá</string>
<string name="install_inactive_slot">Cài đặt vào khe không hoạt động (Sau OTA)</string> <string name="install_inactive_slot">Cài đặt vào phân vùng chưa được sử dụng (Sau OTA)</string>
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào khe cắm hiện tại không hoạt động sau khi khởi động lại!\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\nTiếp tục?</string> <string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào phân vùng chưa được sử dụng!\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.\nTiếp tục?</string>
<string name="install_next">Kế tiếp</string> <string name="install_next">Kế tiếp</string>
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string> <string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
<string name="select_kmi">Chọn KMI</string> <string name="select_kmi">Chọn KMI</string>
<string name="settings_uninstall">Uninstall</string> <string name="settings_uninstall">Gỡ cài đặt</string>
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string> <string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
<string name="settings_uninstall_permanent">Gỡ cài đặt vĩnh viễn</string> <string name="settings_uninstall_permanent">Gỡ cài đặt sạch</string>
<string name="settings_restore_stock_image">Khôi phục img ban đầu</string> <string name="settings_restore_stock_image">Khôi phục phân vùng khởi động về mặc định</string>
<string name="settings_uninstall_temporary_message">Gỡ cài đặt tạm thời KernelSU, khôi phục lại trạng thái ban đầu sau lần khởi động lại tiếp theo.</string> <string name="settings_uninstall_temporary_message">Gỡ cài đặt tạm thời SukiSU Ultra, khôi phục lại trạng thái ban đầu sau lần khởi động lại tiếp theo</string>
<string name="settings_uninstall_permanent_message">Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn.</string> <string name="settings_uninstall_permanent_message">Gỡ cài đặt SukiSU Ultra (Root và tất cả các module) sạch hoàn toàn, trả về trạng thái ban đầu</string>
<string name="settings_restore_stock_image_message">Khôi phục ảnh gốc (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\".</string> <string name="settings_restore_stock_image_message">Khôi phục lại boot lúc đầu (Nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ hẳn SukiSU Ultra, sử dụng\"Gỡ cài đặt sạch\"</string>
<string name="flashing">Đang flash...</string> <string name="flashing">Đang flash...</string>
<string name="flash_success">Flash thành công</string> <string name="flash_success">Flash thành công</string>
<string name="flash_failed">Lỗi flash</string> <string name="flash_failed">Flash thất bại</string>
<string name="selected_lkm">LKM đã chọn: %s</string> <string name="selected_lkm">Chọn tệp LKM: %s</string>
<string name="save_log">Lưu nhật ký</string> <string name="save_log">Lưu logs</string>
<string name="log_saved">Nhật ký đã lưu</string> <string name="log_saved">Logs đã được lưu</string>
<string name="status_supported">Được hỗ trợ</string> <string name="status_supported">Được hỗ trợ</string>
<string name="status_not_supported">Không được hỗ trợ</string> <string name="status_not_supported">Không được hỗ trợ</string>
<string name="status_unknown">Không rõ</string> <string name="status_unknown">Không rõ</string>
<string name="sus_su_mode">Chế độ SuS: SU</string> <string name="sus_su_mode">Chế độ SU của SuS:</string>
<!-- Module related --> <!-- Module related -->
<string name="module_install_confirm">Xác nhận cài đặt mô-đun %1$s ?</string> <string name="module_install_confirm">Xác nhận cài đặt Module %1$s</string>
<string name="unknown_module">Mô-đun không xác định</string> <string name="unknown_module">Module không xác định</string>
<!-- Restore related --> <!-- Restore related -->
<string name="restore_confirm_title">Xác nhận khôi phục mô-đun</string> <string name="restore_confirm_title">Xác nhận khôi phục Modules</string>
<string name="restore_confirm_message">Hành động này sẽ ghi đè lên tất cả các mô-đun hiện có. Tiếp tục?</string> <string name="restore_confirm_message">Hành động này sẽ ghi đè lên tất cả các Module hiện có. Tiếp tục?</string>
<string name="confirm">Xác nhận</string> <string name="confirm">Xác nhận</string>
<string name="cancel">Hủy bỏ</string> <string name="cancel">Huỷ bỏ</string>
<!-- Backup related --> <!-- Backup related -->
<string name="backup_success">Sao lưu thành công (tar.gz)</string> <string name="backup_success">Sao lưu thành công (tar.gz)</string>
<string name="backup_failed">Sao lưu không thành công: %1$s</string> <string name="backup_failed">Sao lưu thất bại: %1$s</string>
<string name="backup_modules">Sao lưu mô-đun</string> <string name="backup_modules">Sao lưu Modules</string>
<string name="restore_modules">Khôi phục các mô-đun</string> <string name="restore_modules">Khôi phục Modules</string>
<!-- Restore related messages --> <!-- Restore related messages -->
<string name="restore_success">Các mô-đun đã được khôi phục thành công, cần khởi động lại</string> <string name="restore_success">Các Module đã được khôi phục thành công, cần khởi động lại</string>
<string name="restore_failed">Khôi phục không thành công: %1$s</string> <string name="restore_failed">Khôi phục thất bại: %1$s</string>
<string name="restart_now">Khởi động lại ngay</string> <string name="restart_now">Khởi động lại ngay</string>
<string name="unknown_error">Lỗi không xác định</string> <string name="unknown_error">Lỗi không xác định</string>
<!-- Command related --> <!-- Command related -->
<string name="command_execution_failed">Thực hiện lệnh không thành công: %1$s</string> <string name="command_execution_failed">Thực hiện lệnh thất bại: %1$s</string>
<!-- Allowlist related --> <!-- Allowlist related -->
<string name="allowlist_backup_success">Sao lưu danh sách cho phép thành công</string> <string name="allowlist_backup_success">Sao lưu danh sách cho phép thành công</string>
<string name="allowlist_backup_failed">Sao lưu danh sách cho phép không thành công: %1$s</string> <string name="allowlist_backup_failed">Sao lưu danh sách cho phép thất bại: %1$s</string>
<string name="allowlist_restore_confirm_title">Xác nhận khôi phục danh sách cho phép</string> <string name="allowlist_restore_confirm_title">Xác nhận khôi phục danh sách cho phép</string>
<string name="allowlist_restore_confirm_message">Hành động này sẽ ghi đè lên danh sách cho phép hiện tại. Tiếp tục?</string> <string name="allowlist_restore_confirm_message">Hành động này sẽ ghi đè lên danh sách cho phép hiện tại. Tiếp tục?</string>
<string name="allowlist_restore_success">Đã khôi phục danh sách cho phép thành công</string> <string name="allowlist_restore_success">Khôi phục danh sách cho phép thành công</string>
<string name="allowlist_restore_failed">Khôi phục danh sách cho phép không thành công: %1$s</string> <string name="allowlist_restore_failed">Khôi phục danh sách cho phép thất bại: %1$s</string>
<string name="backup_allowlist">Sao lưu danh sách cho phép</string> <string name="backup_allowlist">Sao lưu danh sách cho phép</string>
<string name="restore_allowlist">Khôi phục danh sách cho phép</string> <string name="restore_allowlist">Khôi phục danh sách cho phép</string>
<string name="settings_custom_background">Cài đặt nền tùy chỉnh</string> <string name="settings_custom_background">Tuỳ chỉnh nền ứng dụng</string>
<string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string> <string name="settings_custom_background_summary">Chọn một hình ảnh làm hình nền</string>
<string name="settings_card_alpha">Thẻ alpha</string> <string name="settings_card_alpha">Độ trong suốt của thanh điều hướng</string>
<string name="settings_restore_default">Khôi phục mặc định</string> <string name="settings_restore_default">Khôi phục mặc định</string>
<string name="home_android_version">Phiên bản Android</string> <string name="home_android_version">Phiên bản Android</string>
<string name="home_device_model">Model thiết bị</string> <string name="home_device_model">Model thiết bị</string>
<string name="su_not_allowed">Không được phép cấp siêu người dùng cho %s</string> <string name="su_not_allowed">Cấp quyền SU không được phép cho: %s</string>
<string name="settings_disable_su">Vô hiệu hóa khả năng tương thích su</string> <string name="settings_disable_su">Vô hiệu h khả năng của lệnh SU</string>
<string name="settings_disable_su_summary">Tạm thời vô hiệu hóa mọi ứng dụng khỏi quyền root thông qua lệnh su (các tiến trình root hiện có sẽ không bị ảnh hưởng).</string> <string name="settings_disable_su_summary">Vô hiệu hoá khả năng thực thi lệnh SU để lấy quyền root (Những app đã cấp trước đó không bị ảnh hưởng)</string>
<string name="using_mksu_manager">Bạn đang sử dụng trình quản lý SukiSU thử nghiệm</string> <string name="using_mksu_manager">Bạn đang sử dụng trình quản lý SukiSU Beta</string>
<string name="module_install_multiple_confirm">Bạn có chắc chắn muốn cài đặt các mô-đun %d đã chọn không?</string> <string name="module_install_multiple_confirm">Bạn có chắc muốn cài đặt các Module %d đã chọn không?</string>
<string name="module_install_multiple_confirm_with_names">Bạn có chắc chắn muốn cài đặt các mô-đun %1$d sau không? \n\n%2$s</string> <string name="module_install_multiple_confirm_with_names">Bạn có chắc muốn cài đặt các module %1$d sau không? \n\n%2$s</string>
<string name="more_settings">Nhiều thiết lập hơn</string> <string name="more_settings">Nhiều thiết lập hơn</string>
<string name="selinux">SELinux</string> <string name="selinux">SELinux</string>
<string name="selinux_enabled">Đã bật</string> <string name="selinux_enabled">Đã bật</string>
<string name="selinux_disabled">Đã tắt</string> <string name="selinux_disabled">Đã tắt</string>
<string name="simple_mode">Chế độ đơn giản</string> <string name="simple_mode">Chế độ đơn giản</string>
<string name="simple_mode_summary">Ẩn các thẻ không cần thiết khi bật</string> <string name="simple_mode_summary">Ẩn các thẻ không cần thiết ở Trang chủ</string>
<string name="hide_kernel_kernelsu_version">Ẩn phiên bản kernel</string> <string name="hide_kernel_kernelsu_version">Ẩn phiên bản kernel</string>
<string name="hide_kernel_kernelsu_version_summary">Ẩn phiên bản kernel hiện tại</string> <string name="hide_kernel_kernelsu_version_summary">Ẩn thông tin phiên bản kernel ở Trang chủ</string>
<string name="hide_other_info">Ẩn thông tin thêm</string> <string name="hide_other_info">Ẩn thông tin khác</string>
<string name="hide_other_info_summary">Ẩn thông tin về số lượng app đã được cấp root, mô-đun và mô-đun KPM trên trang chủ</string> <string name="hide_other_info_summary">Ẩn thông tin về số lượng app đã được cấp root, Modules và KPM Modules ở Trang chủ</string>
<string name="hide_susfs_status">Ẩn trạng thái SuSFS</string> <string name="hide_susfs_status">Ẩn trạng thái SuSFS</string>
<string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS trên trang chủ</string> <string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS ở Trang chủ</string>
<string name="theme_mode">Chế độ chủ đề</string> <string name="hide_link_card">Ẩn trạng thái thẻ liên kết</string>
<string name="hide_link_card_summary">Ẩn thông tin thẻ liên kết ở Trang chủ</string>
<string name="theme_mode">Chủ đề</string>
<string name="theme_follow_system">Theo hệ thống</string> <string name="theme_follow_system">Theo hệ thống</string>
<string name="theme_light">Sáng</string> <string name="theme_light">Sáng</string>
<string name="theme_dark">Tối</string> <string name="theme_dark">Tối</string>
@@ -207,56 +210,155 @@
<string name="color_orange">Cam</string> <string name="color_orange">Cam</string>
<string name="color_pink">Hồng</string> <string name="color_pink">Hồng</string>
<string name="color_gray">Xám</string> <string name="color_gray">Xám</string>
<string name="color_ivory">Ngà voi</string> <string name="color_ivory">Trắng ngà</string>
<string name="flash_option">Tùy chọn cọ</string> <string name="flash_option">Tuỳ chọn Flash</string>
<string name="flash_option_tip">Chọn tập tin cần flash</string> <string name="flash_option_tip">Chọn tập tin cần Flash</string>
<string name="horizon_kernel">File Anykernel3</string> <string name="horizon_kernel">Tệp Anykernel3</string>
<string name="horizon_kernel_summary">Flash tệp Kernel AnyKernel3</string>
<string name="root_required">Yêu cầu quyền root</string> <string name="root_required">Yêu cầu quyền root</string>
<string name="copy_failed">Sao chép tập tin không thành công</string> <string name="copy_failed">Sao chép tập tin thất bại</string>
<string name="reboot_complete_title">Hoàn tất</string> <string name="reboot_complete_title">Khởi động lại để hoàn tất</string>
<string name="reboot_complete_msg">khởi động lại ngay lập tức?</string> <string name="reboot_complete_msg">Khởi động lại ngay lập tức?</string>
<string name="yes"></string> <string name="yes"></string>
<string name="no">Không</string> <string name="no">Không</string>
<string name="failed_reboot">Khởi động lại không thành công</string> <string name="failed_reboot">Khởi động lại thất bại</string>
<string name="batch_authorization">Giấy phép số lượng lớn</string> <string name="batch_authorization">Uỷ quyền</string>
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string> <string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
<string name="backup">Sao lưu</string> <string name="backup">Sao lưu</string>
<string name="color_yellow">Vàng</string> <string name="color_yellow">Vàng</string>
<string name="kpm">Mô-đun kpm</string> <string name="kpm_title">KPM</string>
<string name="kpm_title">Mô-đun KPM</string> <string name="kpm_empty">Không có Kernel Modules nào được cài đặt tại thời điểm này</string>
<string name="kpm_empty">Không có mô-đun nào được cài đặt.</string> <string name="kpm_version">Phiên bản</string>
<string name="kpm_version">Phát hành</string>
<string name="kpm_author">Tác giả</string> <string name="kpm_author">Tác giả</string>
<string name="kpm_uninstall">Gỡ cài đặt</string> <string name="kpm_uninstall">Gỡ cài đặt</string>
<string name="kpm_uninstall_success">Đã gỡ cài đặt thành công</string> <string name="kpm_uninstall_success">Gỡ cài đặt thành công</string>
<string name="kpm_uninstall_failed">Không thể gỡ cài đặt</string> <string name="kpm_uninstall_failed">Gỡ cài đặt thất bại</string>
<string name="kpm_install">Cài đặt</string> <string name="kpm_install">Cài đặt</string>
<string name="kpm_install_success">Tải module kpm thành công</string> <string name="kpm_install_success">Tải Module KPM thành công</string>
<string name="kpm_install_failed">Tải module kpm không thành công</string> <string name="kpm_install_failed">Tải Module KPM thất bại</string>
<string name="kpm_args">thông số kpm</string> <string name="kpm_args">Thông số KPM</string>
<string name="kpm_control">Tùy chỉnh</string> <string name="kpm_control">Tuỳ chỉnh</string>
<string name="home_kpm_version">Phiên bản KPM</string> <string name="home_kpm_version">Phiên bản KPM</string>
<string name="close_notice">Đóng</string> <string name="close_notice">Đóng</string>
<string name="kernel_module_notice">Các chức năng mô-đun kernel sau đây được KernelPatch phát triển và sửa đổi để bao gồm các chức năng mô-đun hạt nhân của SukiSU Ultra</string> <string name="kernel_module_notice">Các chức năng Module Kernel sau đây được KernelPatch phát triển và sửa đổi để tương thích với các chức năng Module Kernel của SukiSU Ultra</string>
<string name="home_ContributionCard_kernelsu">SukiSU Ultra mong đợi:</string> <string name="home_ContributionCard_kernelsu">SukiSU Ultra mong đợi</string>
<string name="kpm_control_success">Thành công</string> <string name="kpm_control_success">Thành công</string>
<string name="kpm_control_failed">Thất bại</string> <string name="kpm_control_failed">Thất bại</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra sẽ là một nhánh tương đối độc lập của KSU trong tương lai, nhưng xin cảm ơn KernelSU và MKSU chính thức vì những đóng góp của họ!</string> <string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra sẽ là một nhánh tương đối độc lập của KSU trong tương lai, nhưng chúng tôi xin cảm ơn KernelSU và MKSU,... vì những đóng góp của họ!</string>
<string name="not_supported">Không được hỗ trợ</string> <string name="not_supported">Không được hỗ trợ</string>
<string name="supported">Được hỗ trợ</string> <string name="supported">Được hỗ trợ</string>
<string name="home_kpm_module">Mô-đun KPM%d </string> <string name="home_kpm_module">Module KPM: %d</string>
<string name="kpm_invalid_file">Tệp KPM không hợp lệ</string> <string name="kpm_invalid_file">Tệp KPM không hợp lệ</string>
<string name="kernel_patched">chưa được vá</string> <string name="kernel_patched">Kernel chưa được vá</string>
<string name="kernel_not_enabled">chưa được </string> <string name="kernel_not_enabled">Kernel chưa được cấu hình</string>
<string name="custom_settings">Cài đặt tùy chỉnh</string> <string name="custom_settings">Cài đặt tùy chỉnh</string>
<string name="kpm_install_mode">Cài đặt</string> <string name="kpm_install_mode">Cài đặt KPM</string>
<string name="kpm_install_mode_load">Tải</string> <string name="kpm_install_mode_load">Tải</string>
<string name="kpm_install_mode_embed">Nhúng</string> <string name="kpm_install_mode_embed">Nhúng</string>
<string name="kpm_install_mode_description">Vui lòng%1\$s chọn chế độ cài đặt mô-đun \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string> <string name="kpm_install_mode_description">Vui lòng%1\$s chọn chế độ cài đặt Module \n\nTải: Tải tạm thời Module \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
<string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string> <string name="log_failed_to_check_module_file">Kiểm tra tệp Module thất bại</string>
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string> <string name="snackbar_failed_to_check_module_file">Không thể kiểm tra tệp Module</string>
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string> <string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>
<string name="confirm_uninstall_confirm">loại bỏ</string> <string name="confirm_uninstall_confirm">Gỡ cài đặt</string>
<string name="confirm_uninstall_dismiss">bãi bỏ</string> <string name="confirm_uninstall_dismiss">Hủy bỏ</string>
<string name="theme_color">Màu chủ đề</string> <string name="theme_color">Màu chủ đề</string>
<string name="invalid_file_type">Loại tệp không đúng! Vui lòng chọn tệp .kpm</string>
<string name="confirm_uninstall_title_with_filename">Gỡ cài đặt</string>
<string name="confirm_uninstall_content">KPM sau đây sẽ được gỡ cài đặt: %s</string>
<string name="settings_susfs_toggle_summary">Vô hiệu hóa các hook kprobe được tạo bởi SukiSU Ultra, thay vào đó sử dụng các hook nội tuyến, tương tự như phương pháp hook kernel không phải GKI</string>
<string name="image_editor_title">Điều chỉnh hình nền</string>
<string name="image_editor_hint">Sử dụng hai ngón tay để phóng to hình ảnh và một ngón tay để kéo nó để điều chỉnh vị trí</string>
<string name="background_image_error">Không thể tải hình ảnh</string>
<string name="reprovision">Chọn lại</string>
<!-- Kernel Flash Progress Related -->
<string name="horizon_flash_title">Kernel Flashing</string>
<string name="horizon_logs_label">Logs:</string>
<string name="horizon_flash_complete">Flash Complete</string>
<!-- Flash Status Related -->
<string name="horizon_preparing">Preparing…</string>
<string name="horizon_cleaning_files">Cleaning files…</string>
<string name="horizon_copying_files">Copying files…</string>
<string name="horizon_extracting_tool">Extracting flash tool…</string>
<string name="horizon_patching_script">Patching flash script…</string>
<string name="horizon_flashing">Flashing kernel…</string>
<string name="horizon_flash_complete_status">Flash completed</string>
<!-- Slot selection related strings -->
<string name="select_slot_title">Chọn Slot Flash</string>
<string name="select_slot_description">Vui lòng chọn Slot để Flash Boot</string>
<string name="slot_a">Slot A</string>
<string name="slot_b">Slot B</string>
<string name="selected_slot">Chọn Slot: %1$s</string>
<string name="horizon_getting_original_slot">Lấy Slot ban đầu</string>
<string name="horizon_setting_target_slot">Cài đặt Slot được chỉ định</string>
<string name="horizon_restoring_original_slot">Khôi phục Slot mặc định</string>
<string name="current_slot">Slot hiện tại%1$s </string>
<!-- Error Messages -->
<string name="horizon_copy_failed">Copy thất bại</string>
<string name="horizon_unknown_error">Lỗi không xác định</string>
<string name="flash_failed_message">Flash thất bại</string>
<!-- lkm/gki install -->
<string name="Lkm_install_methods">LKM cài đặt</string>
<string name="GKI_install_methods">GKI/non-GKI cài đặt</string>
<string name="kernel_version_log">Phiên bản Kernel%1$s</string>
<string name="tool_version_log">Sử dụng công cụ vá lỗi%1$s</string>
<string name="configuration">Cấu hình</string>
<string name="app_settings">Cài đặt ứng dụng</string>
<string name="tools">Công cụ</string>
<string name="currently_selected">Hiện tại</string>
<!-- String resources used in SuperUser -->
<string name="clear">Loại bỏ</string>
<string name="apps_with_root">Quyền Root của ứng dụng</string>
<string name="apps_with_custom_profile">Cấu hình tuỳ chỉnh của ứng dụng</string>
<string name="other_apps">Các ứng dụng khác</string>
<string name="no_apps_found">Không tìm thấy ứng dụng</string>
<string name="selinux_enabled_toast">SELinux đã bật</string>
<string name="selinux_disabled_toast">SELinux đã tắt</string>
<string name="selinux_change_failed">Thay đổi trạng thái SELinux thất bại</string>
<string name="advanced_settings">Cài đặt nâng cao</string>
<string name="appearance_settings">Tùy chỉnh thanh công cụ</string>
<string name="back">Trở lại</string>
<string name="expand">Be in full swing</string>
<string name="collapse">put away</string>
<string name="susfs_enabled">SuSFS đã bật</string>
<string name="susfs_disabled">SuSFS đã tắt</string>
<string name="background_set_success">Đã cài đặt hình nền thành công</string>
<string name="background_removed">Đã xóa hình nền tùy chỉnh</string>
<string name="root_require_for_install">Yêu cầu quyền root</string>
<!-- KPM display settings -->
<string name="show_kpm_info">Hiển thị chức năng KPM</string>
<string name="show_kpm_info_summary">Hiển thị thông tin KPM, chức năng ở trang chủ và thanh dưới cùng (Cần khởi động lại ứng dụng)</string>
<!-- Webui X settings -->
<string name="use_webuix">Sử dụng WebUI X</string>
<string name="use_webuix_summary">Sử dụng WebUI X thay cho WebUI vì hỗ trợ nhiều API hơn</string>
<string name="use_webuix_eruda">Tiêm Eruda vào WebUI X</string>
<string name="use_webuix_eruda_summary">Chèn bảng điều khiển gỡ lỗi vào WebUI X để gỡ lỗi dễ dàng hơn. Yêu cầu bật gỡ lỗi web</string>
<!-- DPI setting related strings -->
<string name="dpi_settings">Cài đặt DPI</string>
<string name="app_dpi_title">Tuỳ chỉnh DPI</string>
<string name="app_dpi_summary">Điều chỉnh DPI hiển thị cho ứng dụng</string>
<string name="dpi_size_small">Nhỏ</string>
<string name="dpi_size_medium">Vừa</string>
<string name="dpi_size_large">Lớn</string>
<string name="dpi_size_extra_large">Cực lớn</string>
<string name="dpi_size_custom">DPI đang hiển thị</string>
<string name="dpi_apply_settings">Áp dụng</string>
<string name="dpi_confirm_title">Xác nhận thay đổi DPI</string>
<string name="dpi_confirm_message">Bạn có chắc chắn muốn thay đổi DPI của ứng dụng từ %1$d thành %2$d?</string>
<string name="dpi_confirm_summary">Ứng dụng cần được khởi động lại để áp dụng cài đặt DPI mới, không ảnh hưởng đến thanh trạng thái hệ thống hoặc các ứng dụng khác</string>
<string name="dpi_applied_success">DPI đã được đặt thành %1$d, có hiệu lực sau khi khởi động lại ứng dụng</string>
<!-- Language settings related strings -->
<string name="language_setting">Ngôn ngữ ứng dụng</string>
<string name="language_follow_system">Theo dõi hệ thống</string>
<string name="language_changed">Ngôn ngữ đã thay đổi, khởi động lại để áp dụng thay đổi</string>
<string name="settings_card_dim">Điều chỉnh độ tối của thẻ</string>
<!-- Super User Related -->
<string name="scroll_to_top">Lên trên</string>
<string name="scroll_to_bottom">Xuống dưới</string>
<string name="scroll_to_top_description">Cuộn lên trên</string>
<string name="scroll_to_bottom_description">Cuộn xuống dưới</string>
<string name="authorized">Uỷ quyền</string>
<string name="unauthorized">Huỷ uỷ quyền</string>
<string name="selected">Đã chọn</string>
<string name="select">Chọn</string>
<string name="profile_umount_modules_disable">Vô hiệu hóa tuỳ chỉnh của các module</string>
</resources> </resources>

View File

@@ -320,4 +320,41 @@
<string name="background_set_success">背景设置成功</string> <string name="background_set_success">背景设置成功</string>
<string name="background_removed">已移除自定义背景</string> <string name="background_removed">已移除自定义背景</string>
<string name="root_require_for_install">需要 root 权限</string> <string name="root_require_for_install">需要 root 权限</string>
<!-- KPM 显示设置相关 -->
<string name="show_kpm_info">显示 KPM 功能</string>
<string name="show_kpm_info_summary">在主页和底栏显示 KPM 相关功能和信息 (需要重新打开应用)</string>
<!-- Webui X 设置相关 -->
<string name="use_webuix">使用 WebUI X</string>
<string name="use_webuix_summary">使用支持更多 API 的 WebUI X 而不是 WebUI</string>
<string name="use_webuix_eruda">将 Eruda 注入 WebUI X</string>
<string name="use_webuix_eruda_summary">在 WebUI X 中注入调试控制台,使调试更容易,需要启用 WebView 调试</string>
<!-- DPI设置相关字符串 -->
<string name="dpi_settings">DPI 设置</string>
<string name="app_dpi_title">应用DPI</string>
<string name="app_dpi_summary">仅调整当前应用的屏幕显示密度</string>
<string name="dpi_size_small"></string>
<string name="dpi_size_medium"></string>
<string name="dpi_size_large"></string>
<string name="dpi_size_extra_large">超大</string>
<string name="dpi_size_custom">自定义</string>
<string name="dpi_apply_settings">应用DPI设置</string>
<string name="dpi_confirm_title">确认更改DPI</string>
<string name="dpi_confirm_message">你确定要将应用DPI从 %1$d 更改为 %2$d 吗?</string>
<string name="dpi_confirm_summary">应用需要重启以应用新的DPI设置不会影响系统状态栏或其他应用</string>
<string name="dpi_applied_success">DPI 已设置为 %1$d重启应用后生效</string>
<!-- 语言设置相关字符串 -->
<string name="language_setting">应用语言</string>
<string name="language_follow_system">跟随系统</string>
<string name="language_changed">语言已更改,重启应用以应用更改</string>
<string name="settings_card_dim">卡片暗度调节</string>
<!-- 超级用户相关 -->
<string name="scroll_to_top">顶部</string>
<string name="scroll_to_bottom">底部</string>
<string name="scroll_to_top_description">滚动到顶部</string>
<string name="scroll_to_bottom_description">滚动到底部</string>
<string name="authorized">已授权</string>
<string name="unauthorized">未授权</string>
<string name="selected">已选择</string>
<string name="select">选择</string>
<string name="profile_umount_modules_disable">禁用自定义卸载模块</string>
</resources> </resources>

View File

@@ -324,4 +324,41 @@
<string name="background_set_success">Background set successfully</string> <string name="background_set_success">Background set successfully</string>
<string name="background_removed">Removed custom backgrounds</string> <string name="background_removed">Removed custom backgrounds</string>
<string name="root_require_for_install">Requires root privileges</string> <string name="root_require_for_install">Requires root privileges</string>
<!-- KPM display settings -->
<string name="show_kpm_info">Display KPM Function</string>
<string name="show_kpm_info_summary">Display KPM information and Function in home and bottom bar (Need to reopen the app)</string>
<!-- Webui X settings -->
<string name="use_webuix">Use WebUI X</string>
<string name="use_webuix_summary">Use WebUI X instead of WebUI which supports more API\'s</string>
<string name="use_webuix_eruda">Inject Eruda into WebUI X</string>
<string name="use_webuix_eruda_summary">Inject a debug console into WebUI X to make debugging easier. Requires web debugging to be on.</string>
<!-- DPI setting related strings -->
<string name="dpi_settings">DPI setting</string>
<string name="app_dpi_title">Applied DPI</string>
<string name="app_dpi_summary">Adjust the screen display density for the current application only</string>
<string name="dpi_size_small">Small </string>
<string name="dpi_size_medium">Medium </string>
<string name="dpi_size_large">Big</string>
<string name="dpi_size_extra_large">oversize</string>
<string name="dpi_size_custom">customizable</string>
<string name="dpi_apply_settings">Applying DPI settings</string>
<string name="dpi_confirm_title">Confirm DPI change</string>
<string name="dpi_confirm_message">Are you sure you want to change the application DPI from %1$d to %2$d?</string>
<string name="dpi_confirm_summary">Application needs to be restarted to apply the new DPI settings, does not affect the system status bar or other applications</string>
<string name="dpi_applied_success">DPI has been set to %1$d, effective after restarting the application</string>
<!-- Language settings related strings -->
<string name="language_setting">App Language</string>
<string name="language_follow_system">Follow System</string>
<string name="language_changed">Language changed, restarting to apply changes</string>
<string name="settings_card_dim">Card Darkness Adjustment</string>
<!-- Super User Related -->
<string name="scroll_to_top">Top</string>
<string name="scroll_to_bottom">Bottom</string>
<string name="scroll_to_top_description">Scroll to top</string>
<string name="scroll_to_bottom_description">Scroll to the bottom</string>
<string name="authorized">authorized</string>
<string name="unauthorized">unauthorized</string>
<string name="selected">Selected</string>
<string name="select">option</string>
<string name="profile_umount_modules_disable">Disable custom uninstallation module</string>
</resources> </resources>

View File

@@ -1,7 +1,6 @@
import com.android.build.api.dsl.ApplicationDefaultConfig import com.android.build.api.dsl.ApplicationDefaultConfig
import com.android.build.api.dsl.CommonExtension import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.api.AndroidBasePlugin import com.android.build.gradle.api.AndroidBasePlugin
import java.io.ByteArrayOutputStream
plugins { plugins {
alias(libs.plugins.agp.app) apply false alias(libs.plugins.agp.app) apply false
@@ -18,7 +17,7 @@ cmaker {
"-DANDROID_STL=none", "-DANDROID_STL=none",
) )
) )
abiFilters("arm64-v8a", "x86_64", "riscv64") abiFilters("arm64-v8a", "x86_64", "armeabi-v7a")
} }
buildTypes { buildTypes {
if (it.name == "release") { if (it.name == "release") {
@@ -48,18 +47,6 @@ fun getGitDescribe(): String {
}.standardOutput.asText.get().trim() }.standardOutput.asText.get().trim()
} }
fun getVersionCode(): Int {
val commitCount = getGitCommitCount()
val major = 1
return major * 10000 + commitCount + 606
}
fun getVersionName(): String {
return getGitDescribe()
}
subprojects { subprojects {
plugins.withType(AndroidBasePlugin::class.java) { plugins.withType(AndroidBasePlugin::class.java) {
extensions.configure(CommonExtension::class.java) { extensions.configure(CommonExtension::class.java) {
@@ -74,7 +61,7 @@ subprojects {
versionName = managerVersionName versionName = managerVersionName
} }
ndk { ndk {
abiFilters += listOf("arm64-v8a", "x86_64", "riscv64") abiFilters += listOf("arm64-v8a", "x86_64", "armeabi-v7a")
} }
} }

View File

@@ -22,6 +22,7 @@ compose-material3 = "1.3.2"
compose-ui = "1.8.0" compose-ui = "1.8.0"
compose-foundation = "1.7.8" compose-foundation = "1.7.8"
documentfile = "1.0.1" documentfile = "1.0.1"
mmrl = "v33560"
[plugins] [plugins]
agp-app = { id = "com.android.application", version.ref = "agp" } agp-app = { id = "com.android.application", version.ref = "agp" }
@@ -81,4 +82,10 @@ sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs"
markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" } markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" }
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" }
mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" }
mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" }
mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" }

View File

@@ -25,9 +25,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.11" version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -55,9 +55,9 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]] [[package]]
name = "android_log-sys" name = "android_log-sys"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
[[package]] [[package]]
name = "android_logger" name = "android_logger"
@@ -131,9 +131,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.96" version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -146,9 +146,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.86" version = "0.1.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -163,9 +163,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.74" version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cfg-if", "cfg-if",
@@ -184,9 +184,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@@ -211,15 +211,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.15" version = "1.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -232,23 +232,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.39" version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
"num-traits", "num-traits",
"wasm-bindgen", "wasm-bindgen",
"windows-targets", "windows-link",
] ]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.30" version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -256,9 +256,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.30" version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -268,9 +268,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.28" version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -336,9 +336,9 @@ dependencies = [
[[package]] [[package]]
name = "crc" name = "crc"
version = "3.2.1" version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
dependencies = [ dependencies = [
"crc-catalog", "crc-catalog",
] ]
@@ -373,9 +373,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.14" version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
@@ -438,9 +438,9 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
] ]
@@ -477,22 +477,11 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.14.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@@ -520,9 +509,9 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.11.6" version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [ dependencies = [
"env_filter", "env_filter",
"log", "log",
@@ -547,9 +536,9 @@ dependencies = [
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@@ -584,24 +573,14 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.35" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "fs4"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be058769cf1633370c3d0dac6bb9b223b8f18900cf808abadf7843192e706238"
dependencies = [
"rustix 0.38.44",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -623,14 +602,14 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.1" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi",
"wasi", "wasi",
"windows-targets",
] ]
[[package]] [[package]]
@@ -651,9 +630,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
[[package]] [[package]]
name = "heck" name = "heck"
@@ -678,14 +657,15 @@ dependencies = [
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.61" version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"core-foundation-sys", "core-foundation-sys",
"iana-time-zone-haiku", "iana-time-zone-haiku",
"js-sys", "js-sys",
"log",
"wasm-bindgen", "wasm-bindgen",
"windows-core", "windows-core",
] ]
@@ -724,12 +704,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.1" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.2", "hashbrown 0.15.3",
] ]
[[package]] [[package]]
@@ -749,9 +729,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "java-properties" name = "java-properties"
@@ -784,7 +764,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "zakozako" name = "ksud"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"android-properties", "android-properties",
@@ -797,7 +777,6 @@ dependencies = [
"encoding_rs", "encoding_rs",
"env_logger", "env_logger",
"extattr", "extattr",
"fs4",
"getopts", "getopts",
"humansize", "humansize",
"is_executable", "is_executable",
@@ -805,7 +784,6 @@ dependencies = [
"jwalk", "jwalk",
"libc", "libc",
"log", "log",
"loopdev",
"nom", "nom",
"procfs", "procfs",
"regex-lite", "regex-lite",
@@ -828,9 +806,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]] [[package]]
name = "libflate" name = "libflate"
@@ -858,9 +836,9 @@ dependencies = [
[[package]] [[package]]
name = "libm" name = "libm"
version = "0.2.11" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@@ -869,25 +847,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "lockfree-object-pool" name = "linux-raw-sys"
version = "0.1.6" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "loopdev"
version = "0.5.0"
source = "git+https://github.com/Kernel-SU/loopdev#7a921f8d966477a645b1188732fac486c71a68ef"
dependencies = [
"errno 0.2.8",
"libc",
]
[[package]] [[package]]
name = "lzma-rs" name = "lzma-rs"
@@ -899,6 +868,17 @@ dependencies = [
"crc", "crc",
] ]
[[package]]
name = "lzma-sys"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@@ -907,9 +887,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.5" version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
@@ -949,9 +929,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.3" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
@@ -959,6 +939,12 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -967,9 +953,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -980,7 +966,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
"chrono", "chrono",
"flate2", "flate2",
"hex", "hex",
@@ -994,20 +980,26 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
"chrono", "chrono",
"hex", "hex",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.38" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.10.0" version = "1.10.0"
@@ -1042,9 +1034,9 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]] [[package]]
name = "rust-embed" name = "rust-embed"
version = "8.5.0" version = "8.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" checksum = "60e425e204264b144d4c929d126d0de524b40a961686414bab5040f7465c71be"
dependencies = [ dependencies = [
"include-flate", "include-flate",
"rust-embed-impl", "rust-embed-impl",
@@ -1054,9 +1046,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-impl" name = "rust-embed-impl"
version = "8.5.0" version = "8.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" checksum = "6bf418c9a2e3f6663ca38b8a7134cc2c2167c9d69688860e8961e3faa731702e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1067,9 +1059,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-utils" name = "rust-embed-utils"
version = "8.5.0" version = "8.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" checksum = "08d55b95147fe01265d06b3955db798bdaed52e60e2211c41137701b3aba8e21"
dependencies = [ dependencies = [
"sha2", "sha2",
"walkdir", "walkdir",
@@ -1086,11 +1078,11 @@ name = "rustix"
version = "0.38.34" version = "0.38.34"
source = "git+https://github.com/Kernel-SU/rustix.git?branch=main#4a53fbc7cb7a07cabe87125cc21dbc27db316259" source = "git+https://github.com/Kernel-SU/rustix.git?branch=main#4a53fbc7cb7a07cabe87125cc21dbc27db316259"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
"errno 0.3.10", "errno 0.3.11",
"itoa", "itoa",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys 0.4.15",
"once_cell", "once_cell",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@@ -1101,24 +1093,37 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
"errno 0.3.10", "errno 0.3.11",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.0",
"errno 0.3.11",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]] [[package]]
name = "same-file" name = "same-file"
@@ -1131,18 +1136,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.218" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.218" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1151,9 +1156,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.139" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -1174,9 +1179,9 @@ dependencies = [
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.8" version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -1185,9 +1190,9 @@ dependencies = [
[[package]] [[package]]
name = "sha256" name = "sha256"
version = "1.5.0" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -1216,9 +1221,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.98" version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1227,43 +1232,22 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.17.1" version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
dependencies = [ dependencies = [
"cfg-if",
"fastrand", "fastrand",
"getrandom", "getrandom",
"once_cell", "once_cell",
"rustix 0.38.44", "rustix 1.0.7",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "thiserror"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.37" version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [ dependencies = [
"deranged", "deranged",
"num-conv", "num-conv",
@@ -1274,15 +1258,15 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.2" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.43.0" version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -1297,9 +1281,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
@@ -1337,9 +1321,9 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.13.3+wasi-0.2.2" version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [ dependencies = [
"wit-bindgen-rt", "wit-bindgen-rt",
] ]
@@ -1404,13 +1388,13 @@ dependencies = [
[[package]] [[package]]
name = "which" name = "which"
version = "7.0.2" version = "7.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283" checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
dependencies = [ dependencies = [
"either", "either",
"env_home", "env_home",
"rustix 0.38.44", "rustix 1.0.7",
"winsafe", "winsafe",
] ]
@@ -1447,11 +1431,61 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.52.0" version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [ dependencies = [
"windows-targets", "windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
] ]
[[package]] [[package]]
@@ -1544,27 +1578,36 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen-rt"
version = "0.33.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.9.0",
]
[[package]]
name = "xz2"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
dependencies = [
"lzma-sys",
] ]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.35" version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.7.35" version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1573,43 +1616,40 @@ dependencies = [
[[package]] [[package]]
name = "zip" name = "zip"
version = "2.2.2" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
dependencies = [ dependencies = [
"arbitrary", "arbitrary",
"crc32fast", "crc32fast",
"crossbeam-utils", "crossbeam-utils",
"deflate64", "deflate64",
"displaydoc",
"flate2", "flate2",
"indexmap", "indexmap",
"lzma-rs", "lzma-rs",
"memchr", "memchr",
"thiserror",
"time", "time",
"xz2",
"zopfli", "zopfli",
] ]
[[package]] [[package]]
name = "zip-extensions" name = "zip-extensions"
version = "0.8.1" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "386508a00aae1d8218b9252a41f59bba739ccee3f8e420bb90bcb1c30d960d4a" checksum = "79cdbf826e5a6eec81fc5a0d33cd7c09c31fd8f9918f15434f74c42d39ef337a"
dependencies = [ dependencies = [
"zip", "zip",
] ]
[[package]] [[package]]
name = "zopfli" name = "zopfli"
version = "0.8.1" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"crc32fast", "crc32fast",
"lockfree-object-pool",
"log", "log",
"once_cell",
"simd-adler32", "simd-adler32",
] ]

View File

@@ -50,7 +50,6 @@ rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", fea
# some android specific dependencies which compiles under unix are also listed here for convenience of coding # some android specific dependencies which compiles under unix are also listed here for convenience of coding
android-properties = { version = "0.2", features = ["bionic-deprecated"] } android-properties = { version = "0.2", features = ["bionic-deprecated"] }
procfs = "0.17" procfs = "0.17"
loopdev = { git = "https://github.com/Kernel-SU/loopdev" }
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android_logger = { version = "0.14", default-features = false } android_logger = { version = "0.14", default-features = false }

Binary file not shown.

Binary file not shown.

View File

@@ -15,11 +15,16 @@ pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl");
struct Asset; struct Asset;
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64 // IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
#[cfg(not(all(target_arch = "x86_64", target_os = "android")))] #[cfg(all(target_arch = "aarch64", target_os = "android"))]
#[derive(RustEmbed)] #[derive(RustEmbed)]
#[folder = "bin/aarch64"] #[folder = "bin/aarch64"]
struct Asset; struct Asset;
#[cfg(all(target_arch = "arm", target_os = "android"))]
#[derive(RustEmbed)]
#[folder = "bin/arm"]
struct Asset;
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> { pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
for file in Asset::iter() { for file in Asset::iter() {
if file == "ksuinit" || file.ends_with(".ko") { if file == "ksuinit" || file.ends_with(".ko") {

View File

@@ -202,7 +202,7 @@ pub fn root_shell() -> Result<()> {
if free_idx < matches.free.len() { if free_idx < matches.free.len() {
let name = &matches.free[free_idx]; let name = &matches.free[free_idx];
uid = unsafe { uid = unsafe {
#[cfg(target_arch = "aarch64")] #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
let pw = libc::getpwnam(name.as_ptr()).as_ref(); let pw = libc::getpwnam(name.as_ptr()).as_ref();
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
let pw = libc::getpwnam(name.as_ptr() as *const i8).as_ref(); let pw = libc::getpwnam(name.as_ptr() as *const i8).as_ref();

View File

@@ -1,3 +1,3 @@
APP_ABI := arm64-v8a x86_64 APP_ABI := arm64-v8a x86_64 armeabi-v7a
APP_PLATFORM := android-24 APP_PLATFORM := android-24
APP_STL := none APP_STL := none