Compare commits
10 Commits
v0.8.88-pr
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
672eaccd35 | ||
|
|
2fbb96f5c1 | ||
|
|
243b3037d9 | ||
|
|
6e404ab19c | ||
|
|
2395a4b20c | ||
|
|
d3c3f04062 | ||
|
|
201062dc5d | ||
|
|
45b163184d | ||
|
|
2ab70f193a | ||
|
|
ed7868282a |
119
.github/workflows/build.yaml
vendored
@@ -16,20 +16,20 @@ jobs:
|
|||||||
- platform: android
|
- platform: android
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
- platform: windows
|
- platform: windows
|
||||||
os: windows-latest
|
os: Windows-2022
|
||||||
arch: amd64
|
arch: amd64
|
||||||
- platform: linux
|
- platform: linux
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
arch: amd64
|
arch: amd64
|
||||||
- platform: macos
|
- platform: macos
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
arch: amd64
|
arch: amd64
|
||||||
- platform: macos
|
- platform: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
arch: arm64
|
arch: arm64
|
||||||
- platform: windows
|
# - platform: windows
|
||||||
os: windows-11-arm
|
# os: windows-11-arm
|
||||||
arch: arm64
|
# arch: arm64
|
||||||
- platform: linux
|
- platform: linux
|
||||||
os: ubuntu-24.04-arm
|
os: ubuntu-24.04-arm
|
||||||
arch: arm64
|
arch: arm64
|
||||||
@@ -52,6 +52,7 @@ jobs:
|
|||||||
if: startsWith(matrix.platform,'android')
|
if: startsWith(matrix.platform,'android')
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
|
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
|
||||||
|
echo "${{ secrets.SERVICE_JSON }}" | base64 --decode > android/app/google-services.json
|
||||||
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
|
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
|
||||||
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
|
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
|
||||||
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
|
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
|
||||||
@@ -63,22 +64,25 @@ jobs:
|
|||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
core/go.sum
|
core/go.sum
|
||||||
|
|
||||||
- name: Setup Flutter Master
|
|
||||||
if: startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: 'master'
|
|
||||||
cache: true
|
|
||||||
- name: Setup Flutter
|
- name: Setup Flutter
|
||||||
if: ${{ !(startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) }}
|
if: ${{ !(startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) }}
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: stable
|
||||||
|
flutter-version: 3.35.7
|
||||||
|
cache: true
|
||||||
|
- name: Setup Flutter With Other
|
||||||
|
if: startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: master
|
||||||
|
flutter-version: 3.35.7
|
||||||
cache: true
|
cache: true
|
||||||
# flutter-version: 3.29.3
|
|
||||||
|
|
||||||
- name: Get Flutter Dependency
|
- name: Get Flutter Dependency
|
||||||
run: flutter pub get
|
run: |
|
||||||
|
flutter --version
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }}
|
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }}
|
||||||
@@ -103,34 +107,26 @@ jobs:
|
|||||||
- name: Generate
|
- name: Generate
|
||||||
if: ${{ env.IS_STABLE == 'true' }}
|
if: ${{ env.IS_STABLE == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
|
last_ver=$(grep -m1 '^## ' CHANGELOG.md 2>/dev/null | sed 's/^## //')
|
||||||
preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
|
|
||||||
currentTag=""
|
tags=($(git tag --merged HEAD --sort=-creatordate))
|
||||||
for ((i = 0; i <= ${#tags[@]}; i++)); do
|
|
||||||
if (( i < ${#tags[@]} )); then
|
temp="NEW_CHANGELOG.md" > "$temp"
|
||||||
tag=${tags[$i]}
|
|
||||||
else
|
for i in "${!tags[@]}"; do
|
||||||
tag=""
|
curr="${tags[i]}"
|
||||||
fi
|
[[ "$curr" == "$last_ver" ]] && break
|
||||||
if [ -n "$currentTag" ]; then
|
|
||||||
if [ "$(echo -e "$currentTag\n$preTag" | sort -V | head -n 1)" == "$currentTag" ]; then
|
prev="${tags[i+1]}"
|
||||||
break
|
range="${prev:+$prev..}$curr"
|
||||||
fi
|
|
||||||
fi
|
echo -e "## $curr\n" >> "$temp"
|
||||||
if [ -n "$currentTag" ]; then
|
git log --no-merges --pretty=format:"%B" "$range" | \
|
||||||
echo "## $currentTag" >> NEW_CHANGELOG.md
|
awk '!/Update changelog/ && NF {print "- " $0 "\n"}' >> "$temp"
|
||||||
echo "" >> NEW_CHANGELOG.md
|
|
||||||
if [ -n "$tag" ]; then
|
|
||||||
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
|
|
||||||
else
|
|
||||||
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> NEW_CHANGELOG.md
|
|
||||||
fi
|
|
||||||
echo "" >> NEW_CHANGELOG.md
|
|
||||||
fi
|
|
||||||
currentTag=$tag
|
|
||||||
done
|
done
|
||||||
cat CHANGELOG.md >> NEW_CHANGELOG.md
|
[ -f CHANGELOG.md ] && cat CHANGELOG.md >> "$temp"
|
||||||
cat NEW_CHANGELOG.md > CHANGELOG.md
|
|
||||||
|
mv "$temp" CHANGELOG.md
|
||||||
|
|
||||||
- name: Commit
|
- name: Commit
|
||||||
if: ${{ env.IS_STABLE == 'true' }}
|
if: ${{ env.IS_STABLE == 'true' }}
|
||||||
@@ -180,31 +176,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate release.md
|
- name: Generate release.md
|
||||||
run: |
|
run: |
|
||||||
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
|
tags=($(git tag --merged HEAD --sort=-creatordate))
|
||||||
preTag=$(curl --silent "https://api.github.com/repos/chen08209/FlClash/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' || echo "")
|
preTag=$(curl -s "https://api.github.com/repos/chen08209/FlClash/releases/latest" | \
|
||||||
currentTag=""
|
sed -nE 's/.*"tag_name": "([^"]+)".*/\1/p')
|
||||||
for ((i = 0; i <= ${#tags[@]}; i++)); do
|
|
||||||
if (( i < ${#tags[@]} )); then
|
[ -z "$preTag" ] && preTag=""
|
||||||
tag=${tags[$i]}
|
|
||||||
else
|
out="release.md" > "$out"
|
||||||
tag=""
|
|
||||||
fi
|
for i in "${!tags[@]}"; do
|
||||||
if [ -n "$currentTag" ]; then
|
curr="${tags[i]}"
|
||||||
if [ "$(echo -e "$currentTag\n$preTag" | sort -V | head -n 1)" == "$currentTag" ]; then
|
[[ "$curr" == "$preTag" ]] && break
|
||||||
break
|
|
||||||
fi
|
prev="${tags[i+1]}"
|
||||||
fi
|
range="${prev:+$prev..}$curr"
|
||||||
if [ -n "$currentTag" ]; then
|
|
||||||
if [ -n "$tag" ]; then
|
git log --no-merges --pretty=format:"%B" "$range" | \
|
||||||
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> release.md
|
awk '!/Update changelog/ && NF {print "- " $0 "\n"}' >> "$out"
|
||||||
else
|
|
||||||
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> release.md
|
|
||||||
fi
|
|
||||||
echo "" >> release.md
|
|
||||||
fi
|
|
||||||
currentTag=$tag
|
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Push to telegram
|
- name: Push to telegram
|
||||||
env:
|
env:
|
||||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
|||||||
27
.gitignore
vendored
@@ -21,7 +21,7 @@ migrate_working_dir/
|
|||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
# is commented out by default.
|
# is commented out by default.
|
||||||
#.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
@@ -41,15 +41,30 @@ app.*.symbols
|
|||||||
# Obfuscation related
|
# Obfuscation related
|
||||||
app.*.map.json
|
app.*.map.json
|
||||||
|
|
||||||
|
#AI generated
|
||||||
|
CLAUDE.md
|
||||||
|
/.claude
|
||||||
|
|
||||||
|
|
||||||
# Android Studio will place build artifacts here
|
# Android Studio will place build artifacts here
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
/android/**/.cxx
|
||||||
|
/android/**/build
|
||||||
|
/android/common/**/.**/
|
||||||
|
/android/common/local.*
|
||||||
|
/android/core/**/includes/
|
||||||
|
/android/core/**/cmake-build-*/
|
||||||
|
/android/core/**/jniLibs/
|
||||||
|
|
||||||
|
#FlClash
|
||||||
|
|
||||||
#libclash
|
|
||||||
/libclash/
|
/libclash/
|
||||||
|
|
||||||
#jniLibs
|
|
||||||
/android/app/src/main/jniLibs/
|
/android/app/src/main/jniLibs/
|
||||||
|
/services/helper/target
|
||||||
|
/macos/**/Package.resolved
|
||||||
|
devtools_options.yaml
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
||||||
|
.fvmrc
|
||||||
4
.gitmodules
vendored
@@ -6,5 +6,9 @@
|
|||||||
path = plugins/flutter_distributor
|
path = plugins/flutter_distributor
|
||||||
url = git@github.com:chen08209/flutter_distributor.git
|
url = git@github.com:chen08209/flutter_distributor.git
|
||||||
branch = FlClash
|
branch = FlClash
|
||||||
|
[submodule "plugins/tray_manager"]
|
||||||
|
path = plugins/tray_manager
|
||||||
|
url = git@github.com:chen08209/tray_manager.git
|
||||||
|
branch = main
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
29
.metadata
@@ -1,11 +1,11 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
|
||||||
channel: stable
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
@@ -13,26 +13,11 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||||
- platform: android
|
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
- platform: ios
|
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
- platform: linux
|
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
- platform: macos
|
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
- platform: web
|
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
|
||||||
- platform: windows
|
- platform: windows
|
||||||
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||||
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
|
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|||||||
7
.run/main.dart.run.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||||
|
<option name="additionalArgs" value="--dart-define-from-file env.json" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
56
CHANGELOG.md
@@ -1,3 +1,59 @@
|
|||||||
|
## v0.8.92
|
||||||
|
|
||||||
|
- Add sqlite store
|
||||||
|
|
||||||
|
- Optimize android quick action
|
||||||
|
|
||||||
|
- Optimize backup and restore
|
||||||
|
|
||||||
|
- Optimize more details
|
||||||
|
|
||||||
|
## v0.8.91
|
||||||
|
|
||||||
|
- Fix windows some issues
|
||||||
|
|
||||||
|
- Optimize overwrite handle
|
||||||
|
|
||||||
|
- Optimize access control page
|
||||||
|
|
||||||
|
- Optimize some details
|
||||||
|
|
||||||
|
## v0.8.90
|
||||||
|
|
||||||
|
- Fix android tile service
|
||||||
|
|
||||||
|
- Support append system DNS
|
||||||
|
|
||||||
|
- Fix some issues
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
|
## v0.8.89
|
||||||
|
|
||||||
|
- Fix some issues
|
||||||
|
|
||||||
|
- Optimize Windows service mode
|
||||||
|
|
||||||
|
- Update core
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
|
## v0.8.88
|
||||||
|
|
||||||
|
- Add android separates the core process
|
||||||
|
|
||||||
|
- Support core status check and force restart
|
||||||
|
|
||||||
|
- Optimize proxies page and access page
|
||||||
|
|
||||||
|
- Update flutter and pub dependencies
|
||||||
|
|
||||||
|
- Update go version
|
||||||
|
|
||||||
|
- Optimize more details
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
## v0.8.87
|
## v0.8.87
|
||||||
|
|
||||||
- Optimize desktop view
|
- Optimize desktop view
|
||||||
|
|||||||
10
Makefile
@@ -1,10 +0,0 @@
|
|||||||
android_arm64:
|
|
||||||
dart ./setup.dart android --arch arm64
|
|
||||||
macos_arm64:
|
|
||||||
dart ./setup.dart macos --arch arm64
|
|
||||||
android_app:
|
|
||||||
dart ./setup.dart android
|
|
||||||
android_arm64_core:
|
|
||||||
dart ./setup.dart android --arch arm64 --out core
|
|
||||||
macos_arm64_core:
|
|
||||||
dart ./setup.dart macos --arch arm64 --out core
|
|
||||||
@@ -5,6 +5,8 @@ plugins {
|
|||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
id("com.google.gms.google-services")
|
||||||
|
id("com.google.firebase.crashlytics")
|
||||||
}
|
}
|
||||||
|
|
||||||
val localPropertiesFile = rootProject.file("local.properties")
|
val localPropertiesFile = rootProject.file("local.properties")
|
||||||
@@ -18,10 +20,9 @@ val mStoreFile: File = file("keystore.jks")
|
|||||||
val mStorePassword: String? = localProperties.getProperty("storePassword")
|
val mStorePassword: String? = localProperties.getProperty("storePassword")
|
||||||
val mKeyAlias: String? = localProperties.getProperty("keyAlias")
|
val mKeyAlias: String? = localProperties.getProperty("keyAlias")
|
||||||
val mKeyPassword: String? = localProperties.getProperty("keyPassword")
|
val mKeyPassword: String? = localProperties.getProperty("keyPassword")
|
||||||
val isRelease = mStoreFile.exists()
|
val isRelease =
|
||||||
&& mStorePassword != null
|
mStoreFile.exists() && mStorePassword != null && mKeyAlias != null && mKeyPassword != null
|
||||||
&& mKeyAlias != null
|
|
||||||
&& mKeyPassword != null
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.follow.clash"
|
namespace = "com.follow.clash"
|
||||||
@@ -29,6 +30,7 @@ android {
|
|||||||
ndkVersion = libs.versions.ndkVersion.get()
|
ndkVersion = libs.versions.ndkVersion.get()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
@@ -53,24 +55,30 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
signingConfig = if (isRelease) {
|
if (isRelease) {
|
||||||
signingConfigs.getByName("release")
|
signingConfig = signingConfigs.getByName("release")
|
||||||
} else {
|
} else {
|
||||||
signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
applicationIdSuffix = ".dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
"proguard-rules.pro"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,6 +94,7 @@ flutter {
|
|||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":service"))
|
implementation(project(":service"))
|
||||||
implementation(project(":common"))
|
implementation(project(":common"))
|
||||||
@@ -94,4 +103,7 @@ dependencies {
|
|||||||
implementation(libs.smali.dexlib2) {
|
implementation(libs.smali.dexlib2) {
|
||||||
exclude(group = "com.google.guava", module = "guava")
|
exclude(group = "com.google.guava", module = "guava")
|
||||||
}
|
}
|
||||||
|
implementation(platform(libs.firebase.bom))
|
||||||
|
implementation(libs.firebase.crashlytics.ndk)
|
||||||
|
implementation(libs.firebase.analytics)
|
||||||
}
|
}
|
||||||
65
android/app/google-services.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "000000000000",
|
||||||
|
"project_id": "dev"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.follow.clash"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.follow.clash.debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.follow.clash.dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".TileService"
|
android:name=".TileService"
|
||||||
android:label="FlClash Debug"
|
android:label="FlClash Debug"
|
||||||
tools:replace="android:label"
|
tools:replace="android:label" />
|
||||||
tools:targetApi="24" />
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<permission
|
|
||||||
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
|
|
||||||
android:protectionLevel="signature" />
|
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.touchscreen"
|
android:name="android.hardware.touchscreen"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
@@ -24,28 +20,23 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Application"
|
android:name=".Application"
|
||||||
android:banner="@mipmap/ic_banner"
|
android:banner="@mipmap/ic_banner"
|
||||||
android:extractNativeLibs="true"
|
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="FlClash">
|
android:label="FlClash">
|
||||||
<activity
|
<activity
|
||||||
android:name="com.follow.clash.MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
|
||||||
the Android process has started. This theme is visible to the user
|
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
|
||||||
to determine the Window background behind the Flutter UI. -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme" />
|
android:resource="@style/NormalTheme" />
|
||||||
@@ -112,35 +103,11 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="${applicationId}.permission.RECEIVE_BROADCASTS">
|
android:permission="${applicationId}.permission.RECEIVE_BROADCASTS">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="${applicationId}.intent.action.START" />
|
<action android:name="${applicationId}.intent.action.SERVICE_CREATED" />
|
||||||
<action android:name="${applicationId}.intent.action.STOP" />
|
<action android:name="${applicationId}.intent.action.SERVICE_DESTROYED" />
|
||||||
<action android:name="${applicationId}.intent.action.TOGGLE" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name=".FilesProvider"
|
|
||||||
android:authorities="${applicationId}.files"
|
|
||||||
android:exported="true"
|
|
||||||
android:grantUriPermissions="true"
|
|
||||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
|
||||||
android:process=":background">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
|
||||||
</intent-filter>
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.fileProvider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|||||||
@@ -4,29 +4,24 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.follow.clash.common.BroadcastAction
|
import com.follow.clash.common.BroadcastAction
|
||||||
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.common.action
|
import com.follow.clash.common.action
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class BroadcastReceiver : BroadcastReceiver(),
|
class BroadcastReceiver : BroadcastReceiver() {
|
||||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
BroadcastAction.START.action -> {
|
BroadcastAction.SERVICE_CREATED.action -> {
|
||||||
launch {
|
GlobalState.log("Receiver service created")
|
||||||
|
GlobalState.launch {
|
||||||
State.handleStartServiceAction()
|
State.handleStartServiceAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BroadcastAction.STOP.action -> {
|
BroadcastAction.SERVICE_DESTROYED.action -> {
|
||||||
State.handleStopServiceAction()
|
GlobalState.log("Receiver service destroyed")
|
||||||
}
|
GlobalState.launch {
|
||||||
|
State.handleStopServiceAction()
|
||||||
BroadcastAction.TOGGLE.action -> {
|
|
||||||
launch {
|
|
||||||
State.handleToggleAction()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context.MODE_PRIVATE
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import com.follow.clash.common.GlobalState
|
import com.follow.clash.common.GlobalState
|
||||||
|
import com.follow.clash.models.SharedState
|
||||||
|
import com.google.gson.Gson
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
@@ -21,20 +26,43 @@ import kotlin.coroutines.resume
|
|||||||
|
|
||||||
private const val ICON_TTL_DAYS = 1L
|
private const val ICON_TTL_DAYS = 1L
|
||||||
|
|
||||||
|
val Application.sharedState: SharedState
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
|
||||||
|
val res = sp.getString("flutter.sharedState", "")
|
||||||
|
return Gson().fromJson(res, SharedState::class.java)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
return SharedState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var lastToast: Toast? = null
|
||||||
|
|
||||||
|
fun Application.showToast(text: String?) {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
lastToast?.cancel()
|
||||||
|
lastToast = Toast.makeText(this, text, Toast.LENGTH_LONG).apply {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val cacheDir = GlobalState.application.cacheDir
|
val cacheDir = GlobalState.application.cacheDir
|
||||||
val iconDir = File(cacheDir, "icons").apply { mkdirs() }
|
val iconDir = File(cacheDir, "icons").apply { mkdirs() }
|
||||||
val pkgInfo = getPackageInfo(packageName, 0)
|
|
||||||
val lastUpdateTime = pkgInfo.lastUpdateTime
|
|
||||||
val iconFile = File(iconDir, "${packageName}_${lastUpdateTime}.webp")
|
|
||||||
if (iconFile.exists() && !isExpired(iconFile)) {
|
|
||||||
return@withContext iconFile.absolutePath
|
|
||||||
}
|
|
||||||
iconDir.listFiles()?.forEach { file ->
|
|
||||||
if (file.name.startsWith(packageName + "_")) file.delete()
|
|
||||||
}
|
|
||||||
return@withContext try {
|
return@withContext try {
|
||||||
|
val pkgInfo = getPackageInfo(packageName, 0)
|
||||||
|
val lastUpdateTime = pkgInfo.lastUpdateTime
|
||||||
|
val iconFile = File(iconDir, "${packageName}_${lastUpdateTime}.webp")
|
||||||
|
if (iconFile.exists() && !isExpired(iconFile)) {
|
||||||
|
return@withContext iconFile.absolutePath
|
||||||
|
}
|
||||||
|
iconDir.listFiles { f -> f.name.startsWith("${packageName}_") }?.forEach(File::delete)
|
||||||
|
|
||||||
val icon = getApplicationIcon(packageName)
|
val icon = getApplicationIcon(packageName)
|
||||||
saveDrawableToFile(icon, iconFile)
|
saveDrawableToFile(icon, iconFile)
|
||||||
iconFile.absolutePath
|
iconFile.absolutePath
|
||||||
@@ -47,8 +75,10 @@ suspend fun PackageManager.getPackageIconPath(packageName: String): String =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveDrawableToFile(drawable: Drawable, file: File) {
|
private suspend fun saveDrawableToFile(drawable: Drawable, file: File) {
|
||||||
val bitmap = drawable.toBitmap()
|
val bitmap = withContext(Dispatchers.Default) {
|
||||||
|
drawable.toBitmap(width = 128, height = 128)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val format = when {
|
val format = when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||||
@@ -97,17 +127,13 @@ inline fun <reified T : FlutterPlugin> FlutterEngine.plugin(): T? {
|
|||||||
return plugins.get(T::class.java) as T?
|
return plugins.get(T::class.java) as T?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun <T> MethodChannel.invokeMethodOnMainThread(
|
fun <T> MethodChannel.invokeMethodOnMainThread(
|
||||||
method: String,
|
method: String, arguments: Any? = null, callback: ((Result<T>) -> Unit)? = null
|
||||||
arguments: Any? = null,
|
|
||||||
callback: ((Result<T>) -> Unit)? = null
|
|
||||||
) {
|
) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
invokeMethod(method, arguments, object : MethodChannel.Result {
|
invokeMethod(method, arguments, object : MethodChannel.Result {
|
||||||
override fun success(result: Any?) {
|
override fun success(result: Any?) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST") callback?.invoke(Result.success(result as T))
|
||||||
callback?.invoke(Result.success(result as T))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||||
@@ -121,4 +147,4 @@ fun <T> MethodChannel.invokeMethodOnMainThread(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.plugins.AppPlugin
|
import com.follow.clash.plugins.AppPlugin
|
||||||
import com.follow.clash.plugins.ServicePlugin
|
import com.follow.clash.plugins.ServicePlugin
|
||||||
import com.follow.clash.plugins.TilePlugin
|
import com.follow.clash.plugins.TilePlugin
|
||||||
@@ -17,9 +17,6 @@ class MainActivity : FlutterActivity(),
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
lifecycleScope.launch {
|
|
||||||
State.destroyServiceEngine()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
@@ -31,6 +28,9 @@ class MainActivity : FlutterActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
GlobalState.launch {
|
||||||
|
Service.setEventListener(null)
|
||||||
|
}
|
||||||
State.flutterEngine = null
|
State.flutterEngine = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,187 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash
|
||||||
|
|
||||||
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.common.ServiceDelegate
|
import com.follow.clash.common.ServiceDelegate
|
||||||
|
import com.follow.clash.common.formatString
|
||||||
import com.follow.clash.common.intent
|
import com.follow.clash.common.intent
|
||||||
|
import com.follow.clash.service.IAckInterface
|
||||||
import com.follow.clash.service.ICallbackInterface
|
import com.follow.clash.service.ICallbackInterface
|
||||||
import com.follow.clash.service.IMessageInterface
|
import com.follow.clash.service.IEventInterface
|
||||||
import com.follow.clash.service.IRemoteInterface
|
import com.follow.clash.service.IRemoteInterface
|
||||||
|
import com.follow.clash.service.IResultInterface
|
||||||
|
import com.follow.clash.service.IVoidInterface
|
||||||
import com.follow.clash.service.RemoteService
|
import com.follow.clash.service.RemoteService
|
||||||
import com.follow.clash.service.models.NotificationParams
|
import com.follow.clash.service.models.NotificationParams
|
||||||
import com.follow.clash.service.models.VpnOptions
|
import com.follow.clash.service.models.VpnOptions
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
object Service {
|
object Service {
|
||||||
private val delegate by lazy {
|
private val delegate by lazy {
|
||||||
ServiceDelegate<IRemoteInterface>(
|
ServiceDelegate<IRemoteInterface>(
|
||||||
RemoteService::class.intent, ::handleOnServiceCrash
|
RemoteService::class.intent, ::handleServiceDisconnected
|
||||||
) {
|
) {
|
||||||
IRemoteInterface.Stub.asInterface(it)
|
IRemoteInterface.Stub.asInterface(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var onServiceCrash: (() -> Unit)? = null
|
var onServiceDisconnected: ((String) -> Unit)? = null
|
||||||
|
|
||||||
private fun handleOnServiceCrash() {
|
private fun handleServiceDisconnected(message: String) {
|
||||||
bindingState.set(false)
|
onServiceDisconnected?.let {
|
||||||
onServiceCrash?.let {
|
it(message)
|
||||||
it()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val bindingState = AtomicBoolean(false)
|
|
||||||
|
|
||||||
fun bind() {
|
fun bind() {
|
||||||
if (bindingState.compareAndSet(false, true)) {
|
delegate.bind()
|
||||||
delegate.bind()
|
}
|
||||||
|
|
||||||
|
fun unbind() {
|
||||||
|
delegate.unbind()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun invokeAction(data: String, cb: ((result: String) -> Unit)?): Result<Unit> {
|
||||||
|
val res = mutableListOf<ByteArray>()
|
||||||
|
return delegate.useService {
|
||||||
|
it.invokeAction(
|
||||||
|
data, object : ICallbackInterface.Stub() {
|
||||||
|
override fun onResult(
|
||||||
|
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||||
|
) {
|
||||||
|
res.add(result ?: byteArrayOf())
|
||||||
|
ack?.onAck()
|
||||||
|
if (isSuccess) {
|
||||||
|
cb?.let { cb ->
|
||||||
|
cb(res.formatString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invokeAction(
|
suspend fun quickSetup(
|
||||||
data: String, cb: (result: ByteArray?, isSuccess: Boolean) -> Unit
|
initParamsString: String,
|
||||||
) {
|
setupParamsString: String,
|
||||||
delegate.useService {
|
onStarted: (() -> Unit)?,
|
||||||
it.invokeAction(data, object : ICallbackInterface.Stub() {
|
onResult: ((result: String) -> Unit)?,
|
||||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
): Result<Unit> {
|
||||||
cb(result, isSuccess)
|
val res = mutableListOf<ByteArray>()
|
||||||
|
return delegate.useService {
|
||||||
|
it.quickSetup(
|
||||||
|
initParamsString,
|
||||||
|
setupParamsString,
|
||||||
|
object : ICallbackInterface.Stub() {
|
||||||
|
override fun onResult(
|
||||||
|
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||||
|
) {
|
||||||
|
res.add(result ?: byteArrayOf())
|
||||||
|
ack?.onAck()
|
||||||
|
if (isSuccess) {
|
||||||
|
onResult?.let { cb ->
|
||||||
|
cb(res.formatString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
object : IVoidInterface.Stub() {
|
||||||
|
override fun invoke() {
|
||||||
|
onStarted?.let { onStarted ->
|
||||||
|
onStarted()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setEventListener(
|
||||||
|
cb: ((result: String?) -> Unit)?
|
||||||
|
): Result<Unit> {
|
||||||
|
val results = HashMap<String, MutableList<ByteArray>>()
|
||||||
|
return delegate.useService {
|
||||||
|
it.setEventListener(
|
||||||
|
when (cb != null) {
|
||||||
|
true -> object : IEventInterface.Stub() {
|
||||||
|
override fun onEvent(
|
||||||
|
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||||
|
) {
|
||||||
|
if (results[id] == null) {
|
||||||
|
results[id] = mutableListOf()
|
||||||
|
}
|
||||||
|
results[id]?.add(data ?: byteArrayOf())
|
||||||
|
ack?.onAck()
|
||||||
|
if (isSuccess) {
|
||||||
|
cb(results[id]?.formatString())
|
||||||
|
results.remove(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false -> null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateNotificationParams(
|
suspend fun updateNotificationParams(
|
||||||
params: NotificationParams
|
params: NotificationParams
|
||||||
) {
|
): Result<Unit> {
|
||||||
delegate.useService {
|
return delegate.useService {
|
||||||
it.updateNotificationParams(params)
|
it.updateNotificationParams(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setMessageCallback(
|
suspend fun setCrashlytics(
|
||||||
cb: (result: String?) -> Unit
|
enable: Boolean
|
||||||
) {
|
): Result<Unit> {
|
||||||
delegate.useService {
|
return delegate.useService {
|
||||||
it.setMessageCallback(object : IMessageInterface.Stub() {
|
it.setCrashlytics(enable)
|
||||||
override fun onResult(result: String?) {
|
|
||||||
cb(result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startService(options: VpnOptions, inApp: Boolean) {
|
private suspend fun awaitIResultInterface(
|
||||||
delegate.useService { it.startService(options, inApp) }
|
block: (IResultInterface) -> Unit
|
||||||
|
): Long = suspendCancellableCoroutine { continuation ->
|
||||||
|
val callback = object : IResultInterface.Stub() {
|
||||||
|
override fun onResult(time: Long) {
|
||||||
|
if (continuation.isActive) {
|
||||||
|
continuation.resume(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
block(callback)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GlobalState.log("awaitIResultInterface $e")
|
||||||
|
if (continuation.isActive) {
|
||||||
|
continuation.resumeWithException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stopService() {
|
|
||||||
delegate.useService { it.stopService() }
|
suspend fun startService(options: VpnOptions, runTime: Long): Long {
|
||||||
|
return delegate.useService {
|
||||||
|
awaitIResultInterface { callback ->
|
||||||
|
it.startService(options, runTime, callback)
|
||||||
|
}
|
||||||
|
}.getOrNull() ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun stopService(): Long {
|
||||||
|
return delegate.useService {
|
||||||
|
awaitIResultInterface { callback ->
|
||||||
|
it.stopService(callback)
|
||||||
|
}
|
||||||
|
}.getOrNull() ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRunTime(): Long {
|
||||||
|
return delegate.useService {
|
||||||
|
it.runTime
|
||||||
|
}.getOrNull() ?: 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash
|
||||||
|
|
||||||
|
import android.net.VpnService
|
||||||
import com.follow.clash.common.GlobalState
|
import com.follow.clash.common.GlobalState
|
||||||
|
import com.follow.clash.models.SharedState
|
||||||
import com.follow.clash.plugins.AppPlugin
|
import com.follow.clash.plugins.AppPlugin
|
||||||
import com.follow.clash.plugins.ServicePlugin
|
|
||||||
import com.follow.clash.plugins.TilePlugin
|
import com.follow.clash.plugins.TilePlugin
|
||||||
import io.flutter.FlutterInjector
|
import com.follow.clash.service.models.NotificationParams
|
||||||
|
import com.google.gson.Gson
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.embedding.engine.dart.DartExecutor
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
enum class RunState {
|
enum class RunState {
|
||||||
START, PENDING, STOP
|
START, PENDING, STOP
|
||||||
@@ -25,19 +24,17 @@ object State {
|
|||||||
|
|
||||||
var runTime: Long = 0
|
var runTime: Long = 0
|
||||||
|
|
||||||
|
var sharedState: SharedState = SharedState()
|
||||||
|
|
||||||
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
|
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
|
||||||
|
|
||||||
var flutterEngine: FlutterEngine? = null
|
var flutterEngine: FlutterEngine? = null
|
||||||
var serviceFlutterEngine: FlutterEngine? = null
|
|
||||||
|
|
||||||
val appPlugin: AppPlugin?
|
val appPlugin: AppPlugin?
|
||||||
get() = flutterEngine?.plugin<AppPlugin>() ?: serviceFlutterEngine?.plugin<AppPlugin>()
|
get() = flutterEngine?.plugin<AppPlugin>()
|
||||||
|
|
||||||
val servicePlugin: ServicePlugin?
|
|
||||||
get() = flutterEngine?.plugin<ServicePlugin>()
|
|
||||||
?: serviceFlutterEngine?.plugin<ServicePlugin>()
|
|
||||||
|
|
||||||
val tilePlugin: TilePlugin?
|
val tilePlugin: TilePlugin?
|
||||||
get() = flutterEngine?.plugin<TilePlugin>() ?: serviceFlutterEngine?.plugin<TilePlugin>()
|
get() = flutterEngine?.plugin<TilePlugin>()
|
||||||
|
|
||||||
suspend fun handleToggleAction() {
|
suspend fun handleToggleAction() {
|
||||||
var action: (suspend () -> Unit)?
|
var action: (suspend () -> Unit)?
|
||||||
@@ -51,25 +48,54 @@ object State {
|
|||||||
action?.invoke()
|
action?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun handleStartServiceAction() {
|
suspend fun handleSyncState() {
|
||||||
tilePlugin?.handleStart()
|
runLock.withLock {
|
||||||
if (flutterEngine != null) {
|
try {
|
||||||
return
|
Service.bind()
|
||||||
|
runTime = Service.getRunTime()
|
||||||
|
val runState = when (runTime == 0L) {
|
||||||
|
true -> RunState.STOP
|
||||||
|
false -> RunState.START
|
||||||
|
}
|
||||||
|
runStateFlow.tryEmit(runState)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
runStateFlow.tryEmit(RunState.STOP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
startServiceWithEngine()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleStopServiceAction() {
|
suspend fun handleStartServiceAction() {
|
||||||
tilePlugin?.handleStop()
|
runLock.withLock {
|
||||||
if (flutterEngine != null || serviceFlutterEngine != null) {
|
if (runStateFlow.value != RunState.STOP) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
tilePlugin?.handleStart()
|
||||||
|
if (flutterEngine != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startServiceWithPref()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun handleStopServiceAction() {
|
||||||
|
runLock.withLock {
|
||||||
|
if (runStateFlow.value != RunState.START) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tilePlugin?.handleStop()
|
||||||
|
if (flutterEngine != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GlobalState.application.showToast(sharedState.stopTip)
|
||||||
|
handleStopService()
|
||||||
}
|
}
|
||||||
handleStopService()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleStartService() {
|
fun handleStartService() {
|
||||||
|
val appPlugin = flutterEngine?.plugin<AppPlugin>()
|
||||||
if (appPlugin != null) {
|
if (appPlugin != null) {
|
||||||
appPlugin?.requestNotificationsPermission {
|
appPlugin.requestNotificationsPermission {
|
||||||
startService()
|
startService()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -77,72 +103,103 @@ object State {
|
|||||||
startService()
|
startService()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun destroyServiceEngine() {
|
private fun startServiceWithPref() {
|
||||||
runLock.withLock {
|
GlobalState.launch {
|
||||||
withContext(Dispatchers.Main) {
|
runLock.withLock {
|
||||||
runCatching {
|
if (runStateFlow.value != RunState.STOP) {
|
||||||
serviceFlutterEngine?.destroy()
|
return@launch
|
||||||
serviceFlutterEngine = null
|
|
||||||
}
|
}
|
||||||
|
sharedState = GlobalState.application.sharedState
|
||||||
|
setupAndStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startServiceWithEngine() {
|
suspend fun syncState() {
|
||||||
runLock.withLock {
|
GlobalState.setCrashlytics(sharedState.crashlytics)
|
||||||
withContext(Dispatchers.Main) {
|
Service.updateNotificationParams(
|
||||||
serviceFlutterEngine = FlutterEngine(GlobalState.application)
|
NotificationParams(
|
||||||
serviceFlutterEngine?.plugins?.add(ServicePlugin())
|
title = sharedState.currentProfileName,
|
||||||
serviceFlutterEngine?.plugins?.add(AppPlugin())
|
stopText = sharedState.stopText,
|
||||||
serviceFlutterEngine?.plugins?.add(TilePlugin())
|
onlyStatisticsProxy = sharedState.onlyStatisticsProxy
|
||||||
val dartEntrypoint = DartExecutor.DartEntrypoint(
|
)
|
||||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
|
)
|
||||||
)
|
Service.setCrashlytics(sharedState.crashlytics)
|
||||||
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
private suspend fun setupAndStart() {
|
||||||
|
Service.bind()
|
||||||
|
syncState()
|
||||||
|
GlobalState.application.showToast(sharedState.startTip)
|
||||||
|
val initParams = mutableMapOf<String, Any>()
|
||||||
|
initParams["home-dir"] = GlobalState.application.filesDir.path
|
||||||
|
initParams["version"] = android.os.Build.VERSION.SDK_INT
|
||||||
|
val initParamsString = Gson().toJson(initParams)
|
||||||
|
val setupParamsString = Gson().toJson(sharedState.setupParams)
|
||||||
|
Service.quickSetup(
|
||||||
|
initParamsString,
|
||||||
|
setupParamsString,
|
||||||
|
onStarted = {
|
||||||
|
startService()
|
||||||
|
},
|
||||||
|
onResult = {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
GlobalState.application.showToast(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startService() {
|
private fun startService() {
|
||||||
GlobalState.launch {
|
GlobalState.launch {
|
||||||
runLock.withLock {
|
runLock.withLock {
|
||||||
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.START) {
|
if (runStateFlow.value != RunState.STOP) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
runStateFlow.tryEmit(RunState.PENDING)
|
try {
|
||||||
if (servicePlugin == null) {
|
runStateFlow.tryEmit(RunState.PENDING)
|
||||||
return@launch
|
val options = sharedState.vpnOptions ?: return@launch
|
||||||
}
|
appPlugin?.let {
|
||||||
val options = servicePlugin?.handleGetVpnOptions()
|
it.prepare(options.enable) {
|
||||||
if (options == null) {
|
runTime = Service.startService(options, runTime)
|
||||||
return@launch
|
runStateFlow.tryEmit(RunState.START)
|
||||||
}
|
}
|
||||||
appPlugin?.prepare(options.enable) {
|
} ?: run {
|
||||||
Service.startService(options, true)
|
val intent = VpnService.prepare(GlobalState.application)
|
||||||
runStateFlow.tryEmit(RunState.START)
|
if (intent != null) {
|
||||||
runTime = System.currentTimeMillis()
|
return@launch
|
||||||
|
}
|
||||||
|
runTime = Service.startService(options, runTime)
|
||||||
|
runStateFlow.tryEmit(RunState.START)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (runStateFlow.value == RunState.PENDING) {
|
||||||
|
runStateFlow.tryEmit(RunState.STOP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleStopService() {
|
fun handleStopService() {
|
||||||
GlobalState.launch {
|
GlobalState.launch {
|
||||||
runLock.withLock {
|
runLock.withLock {
|
||||||
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.STOP) {
|
if (runStateFlow.value != RunState.START) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
runStateFlow.tryEmit(RunState.PENDING)
|
try {
|
||||||
Service.stopService()
|
runStateFlow.tryEmit(RunState.PENDING)
|
||||||
runStateFlow.tryEmit(RunState.STOP)
|
runTime = Service.stopService()
|
||||||
runTime = 0
|
runStateFlow.tryEmit(RunState.STOP)
|
||||||
|
} finally {
|
||||||
|
if (runStateFlow.value == RunState.PENDING) {
|
||||||
|
runStateFlow.tryEmit(RunState.START)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
destroyServiceEngine()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ class TempActivity : Activity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
QuickAction.STOP.action -> {
|
QuickAction.STOP.action -> {
|
||||||
State.handleStopServiceAction()
|
launch {
|
||||||
|
State.handleStopServiceAction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QuickAction.TOGGLE.action -> {
|
QuickAction.TOGGLE.action -> {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class TileService : TileService() {
|
|||||||
scope?.cancel()
|
scope?.cancel()
|
||||||
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
scope?.launch {
|
scope?.launch {
|
||||||
|
State.handleSyncState()
|
||||||
State.runStateFlow.collect {
|
State.runStateFlow.collect {
|
||||||
updateTile(it)
|
updateTile(it)
|
||||||
}
|
}
|
||||||
@@ -44,8 +45,7 @@ class TileService : TileService() {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
startActivityAndCollapse(pendingIntent)
|
startActivityAndCollapse(pendingIntent)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION") startActivityAndCollapse(intent)
|
||||||
startActivityAndCollapse(intent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
package com.follow.clash.models
|
package com.follow.clash.models
|
||||||
|
|
||||||
|
import com.follow.clash.service.models.VpnOptions
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class AppState(
|
data class SharedState(
|
||||||
val currentProfileName: String,
|
val startTip: String = "Starting VPN...",
|
||||||
val stopText: String,
|
val stopTip: String = "Stopping VPN...",
|
||||||
val onlyStatisticsProxy: Boolean,
|
val crashlytics: Boolean = true,
|
||||||
|
val currentProfileName: String = "FlClash",
|
||||||
|
val stopText: String = "Stop",
|
||||||
|
val onlyStatisticsProxy: Boolean = false,
|
||||||
|
val vpnOptions: VpnOptions? = null,
|
||||||
|
val setupParams: SetupParams? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetupParams(
|
||||||
|
@SerializedName("test-url")
|
||||||
|
val testUrl: String,
|
||||||
|
@SerializedName("selected-map")
|
||||||
|
val selectedMap: Map<String, String>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.content.pm.ComponentInfo
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
@@ -24,6 +23,7 @@ import com.follow.clash.common.QuickAction
|
|||||||
import com.follow.clash.common.quickIntent
|
import com.follow.clash.common.quickIntent
|
||||||
import com.follow.clash.getPackageIconPath
|
import com.follow.clash.getPackageIconPath
|
||||||
import com.follow.clash.models.Package
|
import com.follow.clash.models.Package
|
||||||
|
import com.follow.clash.showToast
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
@@ -58,8 +58,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
|||||||
|
|
||||||
private var requestNotificationCallback: (() -> Unit)? = null
|
private var requestNotificationCallback: (() -> Unit)? = null
|
||||||
|
|
||||||
private val iconMap = mutableMapOf<String, String?>()
|
|
||||||
|
|
||||||
private val packages = mutableListOf<Package>()
|
private val packages = mutableListOf<Package>()
|
||||||
|
|
||||||
private val skipPrefixList = listOf(
|
private val skipPrefixList = listOf(
|
||||||
@@ -172,8 +170,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
|||||||
result.success("")
|
result.success("")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val path =
|
val path = GlobalState.application.packageManager.getPackageIconPath(packageName)
|
||||||
GlobalState.application.packageManager.getPackageIconPath(packageName)
|
|
||||||
result.success(path)
|
result.success(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun tip(message: String?) {
|
private fun tip(message: String?) {
|
||||||
Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show()
|
GlobalState.application.showToast(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@@ -223,13 +220,12 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
|||||||
if (packages.isNotEmpty()) return packages
|
if (packages.isNotEmpty()) return packages
|
||||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
|
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
|
||||||
?.filter {
|
?.filter {
|
||||||
it.packageName != GlobalState.application.packageName || it.packageName == "android"
|
it.packageName != GlobalState.application.packageName && it.packageName != "android"
|
||||||
|
|
||||||
}?.map {
|
}?.map {
|
||||||
Package(
|
Package(
|
||||||
packageName = it.packageName,
|
packageName = it.packageName,
|
||||||
label = it.applicationInfo?.loadLabel(packageManager).toString(),
|
label = it.applicationInfo?.loadLabel(packageManager).toString(),
|
||||||
system = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
|
system = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) != 0,
|
||||||
lastUpdateTime = it.lastUpdateTime,
|
lastUpdateTime = it.lastUpdateTime,
|
||||||
internet = it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
internet = it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,13 +3,9 @@ package com.follow.clash.plugins
|
|||||||
import com.follow.clash.RunState
|
import com.follow.clash.RunState
|
||||||
import com.follow.clash.Service
|
import com.follow.clash.Service
|
||||||
import com.follow.clash.State
|
import com.follow.clash.State
|
||||||
import com.follow.clash.awaitResult
|
|
||||||
import com.follow.clash.common.Components
|
import com.follow.clash.common.Components
|
||||||
import com.follow.clash.common.formatString
|
|
||||||
import com.follow.clash.invokeMethodOnMainThread
|
import com.follow.clash.invokeMethodOnMainThread
|
||||||
import com.follow.clash.models.AppState
|
import com.follow.clash.models.SharedState
|
||||||
import com.follow.clash.service.models.NotificationParams
|
|
||||||
import com.follow.clash.service.models.VpnOptions
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
@@ -41,6 +37,10 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
|||||||
handleInit(result)
|
handleInit(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"shutdown" -> {
|
||||||
|
handleShutdown(result)
|
||||||
|
}
|
||||||
|
|
||||||
"invokeAction" -> {
|
"invokeAction" -> {
|
||||||
handleInvokeAction(call, result)
|
handleInvokeAction(call, result)
|
||||||
}
|
}
|
||||||
@@ -69,16 +69,17 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
|||||||
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
|
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
|
||||||
launch {
|
launch {
|
||||||
val data = call.arguments<String>()!!
|
val data = call.arguments<String>()!!
|
||||||
val res = mutableListOf<ByteArray>()
|
Service.invokeAction(data) {
|
||||||
Service.invokeAction(data) { byteArray, isSuccess ->
|
result.success(it)
|
||||||
res.add(byteArray ?: byteArrayOf())
|
|
||||||
if (isSuccess) {
|
|
||||||
result.success(res.formatString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleShutdown(result: MethodChannel.Result) {
|
||||||
|
Service.unbind()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleStart(result: MethodChannel.Result) {
|
private fun handleStart(result: MethodChannel.Result) {
|
||||||
State.handleStartService()
|
State.handleStartService()
|
||||||
result.success(true)
|
result.success(true)
|
||||||
@@ -89,11 +90,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
|||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun handleGetVpnOptions(): VpnOptions? {
|
|
||||||
val res = flutterMethodChannel.awaitResult<String>("getVpnOptions", null)
|
|
||||||
return Gson().fromJson(res, VpnOptions::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
val semaphore = Semaphore(10)
|
val semaphore = Semaphore(10)
|
||||||
|
|
||||||
fun handleSendEvent(value: String?) {
|
fun handleSendEvent(value: String?) {
|
||||||
@@ -104,38 +100,40 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onServiceCrash() {
|
private fun onServiceDisconnected(message: String) {
|
||||||
State.runStateFlow.tryEmit(RunState.STOP)
|
State.runStateFlow.tryEmit(RunState.STOP)
|
||||||
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", null)
|
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
|
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
val data = call.arguments<String>()!!
|
||||||
|
State.sharedState = Gson().fromJson(data, SharedState::class.java)
|
||||||
launch {
|
launch {
|
||||||
val data = call.arguments<String>()!!
|
State.syncState()
|
||||||
val params = Gson().fromJson(data, AppState::class.java)
|
result.success("")
|
||||||
Service.updateNotificationParams(
|
|
||||||
NotificationParams(
|
|
||||||
title = params.currentProfileName,
|
|
||||||
stopText = params.stopText,
|
|
||||||
onlyStatisticsProxy = params.onlyStatisticsProxy
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result.success(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun handleInit(result: MethodChannel.Result) {
|
fun handleInit(result: MethodChannel.Result) {
|
||||||
Service.bind()
|
Service.bind()
|
||||||
launch {
|
launch {
|
||||||
Service.setMessageCallback {
|
Service.setEventListener {
|
||||||
handleSendEvent(it)
|
handleSendEvent(it)
|
||||||
|
}.onSuccess {
|
||||||
|
result.success("")
|
||||||
|
}.onFailure {
|
||||||
|
result.success(it.message)
|
||||||
}
|
}
|
||||||
result.success(true)
|
|
||||||
}
|
}
|
||||||
Service.onServiceCrash = ::onServiceCrash
|
Service.onServiceDisconnected = ::onServiceDisconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGetRunTime(result: MethodChannel.Result) {
|
private fun handleGetRunTime(result: MethodChannel.Result) {
|
||||||
return result.success(State.runTime)
|
launch {
|
||||||
|
State.handleSyncState()
|
||||||
|
result.success(State.runTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="240"
|
android:viewportWidth="240"
|
||||||
android:viewportHeight="240">
|
android:viewportHeight="240">
|
||||||
<group android:scaleX="0.924"
|
<group android:scaleX="0.924"
|
||||||
android:scaleY="0.924"
|
android:scaleY="0.924"
|
||||||
android:translateX="9.12"
|
android:translateX="9.12"
|
||||||
android:translateY="9.12">
|
android:translateY="9.12">
|
||||||
<group android:scaleX="0.63461536"
|
<group android:scaleX="0.63461536"
|
||||||
android:scaleY="0.63461536"
|
android:scaleY="0.63461536"
|
||||||
android:translateX="45.96154"
|
android:translateX="45.96154"
|
||||||
android:translateY="43.846153">
|
android:translateY="43.846153">
|
||||||
<path
|
<path
|
||||||
android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z"
|
android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z"
|
||||||
android:fillColor="#6666FB"/>
|
android:fillColor="#6666FB"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z"
|
android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z"
|
||||||
android:fillColor="#336AB6"/>
|
android:fillColor="#336AB6"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z"
|
android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z"
|
||||||
android:fillColor="#5CA8E9"/>
|
android:fillColor="#5CA8E9"/>
|
||||||
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath(libs.build.kotlin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("com.android.library") apply false
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val newBuildDir: Directory =
|
val newBuildDir: Directory =
|
||||||
rootProject.layout.buildDirectory
|
rootProject.layout.buildDirectory
|
||||||
.dir("../../build")
|
.dir("../../build")
|
||||||
@@ -31,4 +22,3 @@ subprojects {
|
|||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
delete(rootProject.layout.buildDirectory)
|
delete(rootProject.layout.buildDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
|
implementation(platform(libs.firebase.bom))
|
||||||
|
implementation(libs.firebase.crashlytics.ndk)
|
||||||
|
implementation(libs.firebase.analytics)
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<permission
|
||||||
|
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
|
||||||
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
<uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" />
|
<uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" />
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -10,9 +10,8 @@ enum class QuickAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class BroadcastAction {
|
enum class BroadcastAction {
|
||||||
START,
|
SERVICE_CREATED,
|
||||||
STOP,
|
SERVICE_DESTROYED,
|
||||||
TOGGLE,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class AccessControlMode {
|
enum class AccessControlMode {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.follow.clash.common
|
package com.follow.clash.common
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
@@ -13,14 +14,21 @@ import android.content.Context.RECEIVER_NOT_EXPORTED
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.Looper
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.retryWhen
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@@ -37,19 +45,23 @@ val KClass<*>.intent: Intent
|
|||||||
|
|
||||||
fun Service.startForegroundCompat(id: Int, notification: Notification) {
|
fun Service.startForegroundCompat(id: Int, notification: Notification) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
startForeground(id, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
startForeground(id, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||||
} else {
|
} else {
|
||||||
startForeground(id, notification)
|
startForeground(id, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ComponentName.intent: Intent
|
||||||
|
get() = Intent().apply {
|
||||||
|
setComponent(this@intent)
|
||||||
|
setPackage(GlobalState.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
val QuickAction.action: String
|
val QuickAction.action: String
|
||||||
get() = "${GlobalState.application.packageName}.action.${this.name}"
|
get() = "${GlobalState.application.packageName}.action.${this.name}"
|
||||||
|
|
||||||
val QuickAction.quickIntent: Intent
|
val QuickAction.quickIntent: Intent
|
||||||
get() = Intent().apply {
|
get() = Components.TEMP_ACTIVITY.intent.apply {
|
||||||
setComponent(Components.TEMP_ACTIVITY)
|
|
||||||
setPackage(GlobalState.packageName)
|
|
||||||
action = this@quickIntent.action
|
action = this@quickIntent.action
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||||
}
|
}
|
||||||
@@ -57,10 +69,18 @@ val QuickAction.quickIntent: Intent
|
|||||||
val BroadcastAction.action: String
|
val BroadcastAction.action: String
|
||||||
get() = "${GlobalState.application.packageName}.intent.action.${this.name}"
|
get() = "${GlobalState.application.packageName}.intent.action.${this.name}"
|
||||||
|
|
||||||
|
val Context.processName: String?
|
||||||
|
get() {
|
||||||
|
val pid = android.os.Process.myPid()
|
||||||
|
val activityManager = getSystemService<ActivityManager>()
|
||||||
|
activityManager?.runningAppProcesses?.find { it.pid == pid }?.let {
|
||||||
|
return it.processName
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val BroadcastAction.quickIntent: Intent
|
val BroadcastAction.quickIntent: Intent
|
||||||
get() = Intent().apply {
|
get() = Components.BROADCAST_RECEIVER.intent.apply {
|
||||||
setComponent(Components.BROADCAST_RECEIVER)
|
|
||||||
setPackage(GlobalState.packageName)
|
|
||||||
action = this@quickIntent.action
|
action = this@quickIntent.action
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,62 +146,55 @@ fun Context.receiveBroadcastFlow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed class BindServiceEvent<out T : IBinder> {
|
|
||||||
data class Connected<T : IBinder>(val binder: T) : BindServiceEvent<T>()
|
|
||||||
object Disconnected : BindServiceEvent<Nothing>()
|
|
||||||
object Crashed : BindServiceEvent<Nothing>()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T : IBinder> Context.bindServiceFlow(
|
inline fun <reified T : IBinder> Context.bindServiceFlow(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
flags: Int = Context.BIND_AUTO_CREATE,
|
flags: Int = Context.BIND_AUTO_CREATE,
|
||||||
): Flow<BindServiceEvent<T>> = callbackFlow {
|
maxRetries: Int = 10,
|
||||||
var currentBinder: IBinder? = null
|
retryDelayMillis: Long = 200L
|
||||||
val deathRecipient = IBinder.DeathRecipient {
|
): Flow<Pair<IBinder?, String>> = callbackFlow {
|
||||||
trySend(BindServiceEvent.Crashed)
|
|
||||||
}
|
|
||||||
|
|
||||||
val connection = object : ServiceConnection {
|
val connection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
||||||
if (binder != null) {
|
if (binder != null) {
|
||||||
try {
|
try {
|
||||||
binder.linkToDeath(deathRecipient, 0)
|
|
||||||
currentBinder = binder
|
|
||||||
@Suppress("UNCHECKED_CAST") val casted = binder as? T
|
@Suppress("UNCHECKED_CAST") val casted = binder as? T
|
||||||
if (casted != null) {
|
if (casted != null) {
|
||||||
trySend(BindServiceEvent.Connected(casted))
|
trySend(Pair(casted, ""))
|
||||||
} else {
|
} else {
|
||||||
GlobalState.log("Binder is not of type ${T::class.java}")
|
trySend(Pair(null, "Binder is not of type ${T::class.java}"))
|
||||||
trySend(BindServiceEvent.Disconnected)
|
|
||||||
}
|
}
|
||||||
} catch (e: RemoteException) {
|
} catch (e: RemoteException) {
|
||||||
GlobalState.log("Failed to link to death: ${e.message}")
|
trySend(Pair(null, "Failed to link to death: ${e.message}"))
|
||||||
binder.unlinkToDeath(deathRecipient, 0)
|
|
||||||
trySend(BindServiceEvent.Disconnected)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trySend(BindServiceEvent.Disconnected)
|
trySend(Pair(null, "Binder empty"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
GlobalState.log("Service disconnected")
|
trySend(Pair(null, "Service disconnected"))
|
||||||
currentBinder?.unlinkToDeath(deathRecipient, 0)
|
|
||||||
currentBinder = null
|
|
||||||
trySend(BindServiceEvent.Disconnected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bindService(intent, connection, flags)) {
|
val success = withContext(Dispatchers.Main) {
|
||||||
GlobalState.log("Failed to bind service")
|
bindService(intent, connection, flags)
|
||||||
trySend(BindServiceEvent.Disconnected)
|
}
|
||||||
close()
|
|
||||||
return@callbackFlow
|
if (!success) {
|
||||||
|
throw IllegalStateException("bindService() failed, will retry")
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitClose {
|
awaitClose {
|
||||||
currentBinder?.unlinkToDeath(deathRecipient, 0)
|
Handler(Looper.getMainLooper()).post {
|
||||||
unbindService(connection)
|
unbindService(connection)
|
||||||
|
trySend(Pair(null, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.retryWhen { cause, attempt ->
|
||||||
|
if (attempt < maxRetries && cause is Exception) {
|
||||||
|
delay(retryDelayMillis)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +220,6 @@ val Long.formatBytes: String
|
|||||||
fun String.chunkedForAidl(charset: Charset = Charsets.UTF_8): List<ByteArray> {
|
fun String.chunkedForAidl(charset: Charset = Charsets.UTF_8): List<ByteArray> {
|
||||||
val allBytes = toByteArray(charset)
|
val allBytes = toByteArray(charset)
|
||||||
val total = allBytes.size
|
val total = allBytes.size
|
||||||
|
|
||||||
val maxBytes = when {
|
val maxBytes = when {
|
||||||
total <= 100 * 1024 -> total
|
total <= 100 * 1024 -> total
|
||||||
total <= 1024 * 1024 -> 64 * 1024
|
total <= 1024 * 1024 -> 64 * 1024
|
||||||
@@ -230,7 +242,7 @@ fun <T : List<ByteArray>> T.formatString(charset: Charset = Charsets.UTF_8): Str
|
|||||||
val totalSize = this.sumOf { it.size }
|
val totalSize = this.sumOf { it.size }
|
||||||
val combined = ByteArray(totalSize)
|
val combined = ByteArray(totalSize)
|
||||||
var offset = 0
|
var offset = 0
|
||||||
this.forEach { byteArray ->
|
forEach { byteArray ->
|
||||||
byteArray.copyInto(combined, offset)
|
byteArray.copyInto(combined, offset)
|
||||||
offset += byteArray.size
|
offset += byteArray.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.follow.clash.common
|
package com.follow.clash.common
|
||||||
|
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.google.firebase.FirebaseApp
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
@@ -12,16 +15,16 @@ object GlobalState : CoroutineScope by CoroutineScope(Dispatchers.Default) {
|
|||||||
const val NOTIFICATION_ID = 1
|
const val NOTIFICATION_ID = 1
|
||||||
|
|
||||||
val packageName: String
|
val packageName: String
|
||||||
get() = _application.packageName
|
get() = application.packageName
|
||||||
|
|
||||||
val RECEIVE_BROADCASTS_PERMISSIONS: String
|
val RECEIVE_BROADCASTS_PERMISSIONS: String
|
||||||
get() = "${packageName}.permission.RECEIVE_BROADCASTS"
|
get() = "${packageName}.permission.RECEIVE_BROADCASTS"
|
||||||
|
|
||||||
|
|
||||||
private lateinit var _application: Application
|
private var _application: Application? = null
|
||||||
|
|
||||||
val application: Application
|
val application: Application
|
||||||
get() = _application
|
get() = _application!!
|
||||||
|
|
||||||
|
|
||||||
fun log(text: String) {
|
fun log(text: String) {
|
||||||
@@ -31,4 +34,14 @@ object GlobalState : CoroutineScope by CoroutineScope(Dispatchers.Default) {
|
|||||||
fun init(application: Application) {
|
fun init(application: Application) {
|
||||||
_application = application
|
_application = application
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCrashlytics(enable: Boolean) {
|
||||||
|
_application?.let {
|
||||||
|
FirebaseApp.initializeApp(it)
|
||||||
|
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = enable
|
||||||
|
if (enable) {
|
||||||
|
log("init crashlytics ${it.processName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,73 +6,73 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.retryWhen
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class ServiceDelegate<T>(
|
class ServiceDelegate<T>(
|
||||||
private val intent: Intent,
|
private val intent: Intent,
|
||||||
private val onServiceDisconnected: (() -> Unit)? = null,
|
private val onServiceDisconnected: ((String) -> Unit)? = null,
|
||||||
private val onServiceCrash: (() -> Unit)? = null,
|
|
||||||
private val interfaceCreator: (IBinder) -> T,
|
private val interfaceCreator: (IBinder) -> T,
|
||||||
) : CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
) : CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||||
|
|
||||||
private val _service = MutableStateFlow<T?>(null)
|
private val _bindingState = AtomicBoolean(false)
|
||||||
|
|
||||||
val service: StateFlow<T?> = _service
|
private var _serviceState = MutableStateFlow<Pair<T?, String>?>(null)
|
||||||
|
|
||||||
private var bindJob: Job? = null
|
val serviceState: StateFlow<Pair<T?, String>?> = _serviceState
|
||||||
private fun handleBindEvent(event: BindServiceEvent<IBinder>) {
|
private var job: Job? = null
|
||||||
when (event) {
|
|
||||||
is BindServiceEvent.Connected -> {
|
|
||||||
_service.value = event.binder.let(interfaceCreator)
|
|
||||||
}
|
|
||||||
|
|
||||||
is BindServiceEvent.Disconnected -> {
|
private fun handleBind(data: Pair<IBinder?, String>) {
|
||||||
_service.value = null
|
data.first?.let {
|
||||||
onServiceDisconnected?.invoke()
|
_serviceState.value = Pair(interfaceCreator(it), data.second)
|
||||||
}
|
} ?: run {
|
||||||
|
_serviceState.value = Pair(null, data.second)
|
||||||
is BindServiceEvent.Crashed -> {
|
unbind()
|
||||||
_service.value = null
|
onServiceDisconnected?.invoke(data.second)
|
||||||
onServiceCrash?.invoke()
|
_bindingState.set(false)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind() {
|
fun bind() {
|
||||||
unbind()
|
if (_bindingState.compareAndSet(false, true)) {
|
||||||
bindJob = launch {
|
job?.cancel()
|
||||||
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { it ->
|
job = null
|
||||||
handleBindEvent(it)
|
_serviceState.value = null
|
||||||
|
job = launch {
|
||||||
|
runCatching {
|
||||||
|
GlobalState.application.bindServiceFlow<IBinder>(intent)
|
||||||
|
.collect { handleBind(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <R> useService(
|
suspend inline fun <R> useService(
|
||||||
retries: Int = 10,
|
timeoutMillis: Long = 5000, crossinline block: suspend (T) -> R
|
||||||
delayMillis: Long = 200,
|
|
||||||
crossinline block: (T) -> R
|
|
||||||
): Result<R> {
|
): Result<R> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
service.filterNotNull()
|
withTimeout(timeoutMillis) {
|
||||||
.retryWhen { _, attempt ->
|
val state = serviceState.filterNotNull().first()
|
||||||
(attempt < retries).also {
|
state.first?.let {
|
||||||
if (it) delay(delayMillis)
|
withContext(Dispatchers.Default) {
|
||||||
|
block(it)
|
||||||
}
|
}
|
||||||
}.first().let {
|
} ?: throw Exception(state.second)
|
||||||
block(it)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unbind() {
|
fun unbind() {
|
||||||
_service.value = null
|
if (_bindingState.compareAndSet(true, false)) {
|
||||||
bindJob?.cancel()
|
job?.cancel()
|
||||||
bindJob = null
|
job = null
|
||||||
|
_serviceState.value = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,25 +8,9 @@ message("CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}")
|
|||||||
|
|
||||||
|
|
||||||
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
# set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
||||||
add_compile_options(-O3)
|
add_compile_options(-O3 -flto -g0 -fno-exceptions -fno-rtti)
|
||||||
|
add_link_options(-flto -Wl,--gc-sections,--strip-all)
|
||||||
add_compile_options(-flto)
|
|
||||||
|
|
||||||
add_compile_options(-g0)
|
|
||||||
|
|
||||||
add_compile_options(-ffunction-sections -fdata-sections)
|
|
||||||
|
|
||||||
add_compile_options(-fno-exceptions -fno-rtti)
|
|
||||||
|
|
||||||
add_link_options(
|
|
||||||
-flto
|
|
||||||
-Wl,--gc-sections
|
|
||||||
-Wl,--strip-all
|
|
||||||
-Wl,--exclude-libs=ALL
|
|
||||||
)
|
|
||||||
|
|
||||||
add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
|
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ JNIEXPORT void JNICALL
|
|||||||
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
|
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
|
||||||
jstring stack, jstring address, jstring dns) {
|
jstring stack, jstring address, jstring dns) {
|
||||||
const auto interface = new_global(cb);
|
const auto interface = new_global(cb);
|
||||||
scoped_string stackChar = get_string(stack);
|
startTUN(interface, fd, get_string(stack), get_string(address), get_string(dns));
|
||||||
scoped_string addressChar = get_string(address);
|
|
||||||
scoped_string dnsChar = get_string(dns);
|
|
||||||
startTUN(interface, fd, stackChar, addressChar, dnsChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@@ -32,39 +29,39 @@ Java_com_follow_clash_core_Core_forceGC(JNIEnv *env, jobject thiz) {
|
|||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns) {
|
Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns) {
|
||||||
scoped_string dnsChar = get_string(dns);
|
updateDns(get_string(dns));
|
||||||
updateDns(dnsChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring data, jobject cb) {
|
Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring data, jobject cb) {
|
||||||
const auto interface = new_global(cb);
|
const auto interface = new_global(cb);
|
||||||
scoped_string dataChar = get_string(data);
|
invokeAction(interface, get_string(data));
|
||||||
invokeAction(interface, dataChar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
|
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
|
||||||
const auto interface = new_global(cb);
|
if (cb != nullptr) {
|
||||||
setMessageCallback(interface);
|
const auto interface = new_global(cb);
|
||||||
|
setEventListener(interface);
|
||||||
|
} else {
|
||||||
|
setEventListener(nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_com_follow_clash_core_Core_getTraffic(JNIEnv *env, jobject thiz,
|
Java_com_follow_clash_core_Core_getTraffic(JNIEnv *env, jobject thiz,
|
||||||
const jboolean only_statistics_proxy) {
|
const jboolean only_statistics_proxy) {
|
||||||
scoped_string res = getTraffic(only_statistics_proxy);
|
return new_string(getTraffic(only_statistics_proxy));
|
||||||
return new_string(res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_com_follow_clash_core_Core_getTotalTraffic(JNIEnv *env, jobject thiz,
|
Java_com_follow_clash_core_Core_getTotalTraffic(JNIEnv *env, jobject thiz,
|
||||||
const jboolean only_statistics_proxy) {
|
const jboolean only_statistics_proxy) {
|
||||||
scoped_string res = getTotalTraffic(only_statistics_proxy);
|
return new_string(getTotalTraffic(only_statistics_proxy));
|
||||||
return new_string(res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@@ -73,6 +70,14 @@ Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean su
|
|||||||
suspend(suspended);
|
suspend(suspended);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
|
||||||
|
jstring setup_params_string, jobject cb) {
|
||||||
|
const auto interface = new_global(cb);
|
||||||
|
quickSetup(interface, get_string(init_params_string), get_string(setup_params_string));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static jmethodID m_tun_interface_protect;
|
static jmethodID m_tun_interface_protect;
|
||||||
static jmethodID m_tun_interface_resolve_process;
|
static jmethodID m_tun_interface_resolve_process;
|
||||||
@@ -84,6 +89,10 @@ static void release_jni_object_impl(void *obj) {
|
|||||||
del_global(static_cast<jobject>(obj));
|
del_global(static_cast<jobject>(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void free_string_impl(char *str) {
|
||||||
|
free(str);
|
||||||
|
}
|
||||||
|
|
||||||
static void call_tun_interface_protect_impl(void *tun_interface, const int fd) {
|
static void call_tun_interface_protect_impl(void *tun_interface, const int fd) {
|
||||||
ATTACH_JNI();
|
ATTACH_JNI();
|
||||||
env->CallVoidMethod(static_cast<jobject>(tun_interface),
|
env->CallVoidMethod(static_cast<jobject>(tun_interface),
|
||||||
@@ -104,8 +113,7 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
|
|||||||
new_string(source),
|
new_string(source),
|
||||||
new_string(target),
|
new_string(target),
|
||||||
uid));
|
uid));
|
||||||
scoped_string packageNameChar = get_string(packageName);
|
return get_string(packageName);
|
||||||
return packageNameChar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void call_invoke_interface_result_impl(void *invoke_interface, const char *data) {
|
static void call_invoke_interface_result_impl(void *invoke_interface, const char *data) {
|
||||||
@@ -140,6 +148,7 @@ JNI_OnLoad(JavaVM *vm, void *) {
|
|||||||
resolve_process_func = &call_tun_interface_resolve_process_impl;
|
resolve_process_func = &call_tun_interface_resolve_process_impl;
|
||||||
result_func = &call_invoke_interface_result_impl;
|
result_func = &call_invoke_interface_result_impl;
|
||||||
release_object_func = &release_jni_object_impl;
|
release_object_func = &release_jni_object_impl;
|
||||||
|
free_string_func = &free_string_impl;
|
||||||
|
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
@@ -172,7 +181,7 @@ Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_setMessageCallback(JNIEnv *env, jobject thiz, jobject cb) {
|
Java_com_follow_clash_core_Core_setEventListener(JNIEnv *env, jobject thiz, jobject cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@@ -190,4 +199,10 @@ extern "C"
|
|||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
|
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
|
||||||
|
jstring setup_params_string, jobject cb) {
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -84,12 +84,32 @@ data object Core {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun setMessageCallback(cb: InvokeInterface)
|
private external fun setEventListener(cb: InvokeInterface?)
|
||||||
|
|
||||||
fun setMessageCallback(
|
fun callSetEventListener(
|
||||||
cb: (result: String?) -> Unit
|
cb: ((result: String?) -> Unit)?
|
||||||
) {
|
) {
|
||||||
setMessageCallback(
|
when (cb != null) {
|
||||||
|
true -> setEventListener(
|
||||||
|
object : InvokeInterface {
|
||||||
|
override fun onResult(result: String?) {
|
||||||
|
cb(result)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
false -> setEventListener(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun quickSetup(
|
||||||
|
initParamsString: String,
|
||||||
|
setupParamsString: String,
|
||||||
|
cb: (result: String?) -> Unit,
|
||||||
|
) {
|
||||||
|
quickSetup(
|
||||||
|
initParamsString,
|
||||||
|
setupParamsString,
|
||||||
object : InvokeInterface {
|
object : InvokeInterface {
|
||||||
override fun onResult(result: String?) {
|
override fun onResult(result: String?) {
|
||||||
cb(result)
|
cb(result)
|
||||||
@@ -98,6 +118,12 @@ data object Core {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private external fun quickSetup(
|
||||||
|
initParamsString: String,
|
||||||
|
setupParamsString: String,
|
||||||
|
cb: InvokeInterface
|
||||||
|
)
|
||||||
|
|
||||||
external fun stopTun()
|
external fun stopTun()
|
||||||
|
|
||||||
external fun getTraffic(onlyStatisticsProxy: Boolean): String
|
external fun getTraffic(onlyStatisticsProxy: Boolean): String
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#agp = "8.10.1"
|
#agp = "8.10.1"
|
||||||
|
firebaseBom = "34.2.0"
|
||||||
minSdk = "23"
|
minSdk = "23"
|
||||||
targetSdk = "36"
|
targetSdk = "36"
|
||||||
compileSdk = "36"
|
compileSdk = "36"
|
||||||
@@ -10,11 +11,18 @@ coreSplashscreen = "1.0.1"
|
|||||||
gson = "2.13.1"
|
gson = "2.13.1"
|
||||||
kotlin = "2.2.10"
|
kotlin = "2.2.10"
|
||||||
smaliDexlib2 = "3.0.9"
|
smaliDexlib2 = "3.0.9"
|
||||||
|
firebaseCrashlyticsKtx = "20.0.1"
|
||||||
|
firebaseCommonKtx = "22.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||||
annotation-jvm = { module = "androidx.annotation:annotation-jvm", version.ref = "annotationJvm" }
|
annotation-jvm = { module = "androidx.annotation:annotation-jvm", version.ref = "annotationJvm" }
|
||||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||||
|
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
|
||||||
|
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||||
|
firebase-crashlytics-ndk = { module = "com.google.firebase:firebase-crashlytics-ndk" }
|
||||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||||
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
|
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
|
||||||
|
firebase-crashlytics-ktx = { group = "com.google.firebase", name = "firebase-crashlytics-ktx", version.ref = "firebaseCrashlyticsKtx" }
|
||||||
|
firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
|
||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
<application>
|
<application>
|
||||||
<service
|
<service
|
||||||
android:name="com.follow.clash.service.VpnService"
|
android:name=".VpnService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync"
|
android:foregroundServiceType="specialUse"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:process=":background">
|
android:process=":remote">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.net.VpnService" />
|
<action android:name="android.net.VpnService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -19,18 +19,31 @@
|
|||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.follow.clash.service.CommonService"
|
android:name=".CommonService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync"
|
android:foregroundServiceType="specialUse"
|
||||||
android:process=":background">
|
android:process=":remote">
|
||||||
<property
|
<property
|
||||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
android:value="service" />
|
android:value="proxy" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.follow.clash.service.RemoteService"
|
android:name=".RemoteService"
|
||||||
|
android:enabled="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":background" />
|
android:process=":remote" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name=".FilesProvider"
|
||||||
|
android:authorities="${applicationId}.files"
|
||||||
|
android:exported="true"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||||
|
android:process=":remote">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||||
|
</intent-filter>
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// IAckInterface.aidl
|
||||||
|
package com.follow.clash.service;
|
||||||
|
|
||||||
|
import com.follow.clash.service.IAckInterface;
|
||||||
|
|
||||||
|
interface IAckInterface {
|
||||||
|
oneway void onAck();
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// ICallbackInterface.aidl
|
// ICallbackInterface.aidl
|
||||||
package com.follow.clash.service;
|
package com.follow.clash.service;
|
||||||
|
|
||||||
|
import com.follow.clash.service.IAckInterface;
|
||||||
|
|
||||||
interface ICallbackInterface {
|
interface ICallbackInterface {
|
||||||
void onResult(in byte[] result, boolean isSuccess);
|
oneway void onResult(in byte[] data,in boolean isSuccess, in IAckInterface ack);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// IEventInterface.aidl
|
||||||
|
package com.follow.clash.service;
|
||||||
|
|
||||||
|
import com.follow.clash.service.IAckInterface;
|
||||||
|
|
||||||
|
interface IEventInterface {
|
||||||
|
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess, in IAckInterface ack);
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// IMessageInterface.aidl
|
|
||||||
package com.follow.clash.service;
|
|
||||||
|
|
||||||
interface IMessageInterface {
|
|
||||||
void onResult(String result);
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,19 @@
|
|||||||
package com.follow.clash.service;
|
package com.follow.clash.service;
|
||||||
|
|
||||||
import com.follow.clash.service.ICallbackInterface;
|
import com.follow.clash.service.ICallbackInterface;
|
||||||
import com.follow.clash.service.IMessageInterface;
|
import com.follow.clash.service.IEventInterface;
|
||||||
|
import com.follow.clash.service.IResultInterface;
|
||||||
|
import com.follow.clash.service.IVoidInterface;
|
||||||
import com.follow.clash.service.models.VpnOptions;
|
import com.follow.clash.service.models.VpnOptions;
|
||||||
import com.follow.clash.service.models.NotificationParams;
|
import com.follow.clash.service.models.NotificationParams;
|
||||||
|
|
||||||
interface IRemoteInterface {
|
interface IRemoteInterface {
|
||||||
void invokeAction(in String data, in ICallbackInterface callback);
|
void invokeAction(in String data, in ICallbackInterface callback);
|
||||||
|
void quickSetup(in String initParamsString, in String setupParamsString, in ICallbackInterface callback, in IVoidInterface onStarted);
|
||||||
void updateNotificationParams(in NotificationParams params);
|
void updateNotificationParams(in NotificationParams params);
|
||||||
void startService(in VpnOptions options,in boolean inApp);
|
void startService(in VpnOptions options, in long runTime, in IResultInterface result);
|
||||||
void stopService();
|
void stopService(in IResultInterface result);
|
||||||
void setMessageCallback(in IMessageInterface messageCallback);
|
void setEventListener(in IEventInterface event);
|
||||||
|
void setCrashlytics(in boolean enable);
|
||||||
|
long getRunTime();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// IResultInterface.aidl
|
||||||
|
package com.follow.clash.service;
|
||||||
|
|
||||||
|
interface IResultInterface {
|
||||||
|
oneway void onResult(in long runTime);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// IVoidInterface.aidl
|
||||||
|
package com.follow.clash.service;
|
||||||
|
|
||||||
|
interface IVoidInterface {
|
||||||
|
oneway void invoke();
|
||||||
|
}
|
||||||
@@ -29,6 +29,11 @@ class CommonService : Service(), IBaseService,
|
|||||||
handleCreate()
|
handleCreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
handleDestroy()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLowMemory() {
|
override fun onLowMemory() {
|
||||||
Core.forceGC()
|
Core.forceGC()
|
||||||
super.onLowMemory()
|
super.onLowMemory()
|
||||||
@@ -45,7 +50,11 @@ class CommonService : Service(), IBaseService,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
loader.load()
|
try {
|
||||||
|
loader.load()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
|
|||||||
@@ -1,35 +1,33 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash.service
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.MatrixCursor
|
import android.database.MatrixCursor
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.provider.DocumentsContract.Document
|
import android.provider.DocumentsContract
|
||||||
import android.provider.DocumentsContract.Root
|
|
||||||
import android.provider.DocumentsProvider
|
import android.provider.DocumentsProvider
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
|
|
||||||
class FilesProvider : DocumentsProvider() {
|
class FilesProvider : DocumentsProvider() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DEFAULT_ROOT_ID = "0"
|
private const val DEFAULT_ROOT_ID = "0"
|
||||||
|
|
||||||
private val DEFAULT_DOCUMENT_COLUMNS = arrayOf(
|
private val DEFAULT_DOCUMENT_COLUMNS = arrayOf(
|
||||||
Document.COLUMN_DOCUMENT_ID,
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
Document.COLUMN_DISPLAY_NAME,
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
Document.COLUMN_MIME_TYPE,
|
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||||
Document.COLUMN_FLAGS,
|
DocumentsContract.Document.COLUMN_FLAGS,
|
||||||
Document.COLUMN_SIZE,
|
DocumentsContract.Document.COLUMN_SIZE,
|
||||||
)
|
)
|
||||||
private val DEFAULT_ROOT_COLUMNS = arrayOf(
|
private val DEFAULT_ROOT_COLUMNS = arrayOf(
|
||||||
Root.COLUMN_ROOT_ID,
|
DocumentsContract.Root.COLUMN_ROOT_ID,
|
||||||
Root.COLUMN_FLAGS,
|
DocumentsContract.Root.COLUMN_FLAGS,
|
||||||
Root.COLUMN_ICON,
|
DocumentsContract.Root.COLUMN_ICON,
|
||||||
Root.COLUMN_TITLE,
|
DocumentsContract.Root.COLUMN_TITLE,
|
||||||
Root.COLUMN_SUMMARY,
|
DocumentsContract.Root.COLUMN_SUMMARY,
|
||||||
Root.COLUMN_DOCUMENT_ID
|
DocumentsContract.Root.COLUMN_DOCUMENT_ID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,12 +38,12 @@ class FilesProvider : DocumentsProvider() {
|
|||||||
override fun queryRoots(projection: Array<String>?): Cursor {
|
override fun queryRoots(projection: Array<String>?): Cursor {
|
||||||
return MatrixCursor(projection ?: DEFAULT_ROOT_COLUMNS).apply {
|
return MatrixCursor(projection ?: DEFAULT_ROOT_COLUMNS).apply {
|
||||||
newRow().apply {
|
newRow().apply {
|
||||||
add(Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
|
add(DocumentsContract.Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
|
||||||
add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY)
|
add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_LOCAL_ONLY)
|
||||||
add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
|
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_service)
|
||||||
add(Root.COLUMN_TITLE, "FlClash")
|
add(DocumentsContract.Root.COLUMN_TITLE, "FlClash")
|
||||||
add(Root.COLUMN_SUMMARY, "Data")
|
add(DocumentsContract.Root.COLUMN_SUMMARY, "Data")
|
||||||
add(Root.COLUMN_DOCUMENT_ID, "/")
|
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, "/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,20 +85,20 @@ class FilesProvider : DocumentsProvider() {
|
|||||||
|
|
||||||
private fun includeFile(result: MatrixCursor, file: File) {
|
private fun includeFile(result: MatrixCursor, file: File) {
|
||||||
result.newRow().apply {
|
result.newRow().apply {
|
||||||
add(Document.COLUMN_DOCUMENT_ID, file.absolutePath)
|
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, file.absolutePath)
|
||||||
add(Document.COLUMN_DISPLAY_NAME, file.name)
|
add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.name)
|
||||||
add(Document.COLUMN_SIZE, file.length())
|
add(DocumentsContract.Document.COLUMN_SIZE, file.length())
|
||||||
add(
|
add(
|
||||||
Document.COLUMN_FLAGS,
|
DocumentsContract.Document.COLUMN_FLAGS,
|
||||||
Document.FLAG_SUPPORTS_WRITE or Document.FLAG_SUPPORTS_DELETE
|
DocumentsContract.Document.FLAG_SUPPORTS_WRITE or DocumentsContract.Document.FLAG_SUPPORTS_DELETE
|
||||||
)
|
)
|
||||||
add(Document.COLUMN_MIME_TYPE, getDocumentType(file))
|
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getDocumentType(file))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDocumentType(file: File): String {
|
private fun getDocumentType(file: File): String {
|
||||||
return if (file.isDirectory) {
|
return if (file.isDirectory) {
|
||||||
Document.MIME_TYPE_DIR
|
DocumentsContract.Document.MIME_TYPE_DIR
|
||||||
} else {
|
} else {
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
package com.follow.clash.service
|
package com.follow.clash.service
|
||||||
|
|
||||||
import com.follow.clash.common.BroadcastAction
|
import com.follow.clash.common.BroadcastAction
|
||||||
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.common.sendBroadcast
|
import com.follow.clash.common.sendBroadcast
|
||||||
|
|
||||||
interface IBaseService {
|
interface IBaseService {
|
||||||
fun handleCreate() {
|
fun handleCreate() {
|
||||||
if (!State.inApp) {
|
GlobalState.log("Service create")
|
||||||
BroadcastAction.START.sendBroadcast()
|
BroadcastAction.SERVICE_CREATED.sendBroadcast()
|
||||||
} else {
|
}
|
||||||
State.inApp = false
|
|
||||||
}
|
fun handleDestroy() {
|
||||||
|
GlobalState.log("Service destroy")
|
||||||
|
BroadcastAction.SERVICE_DESTROYED.sendBroadcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start()
|
fun start()
|
||||||
|
|||||||
@@ -3,92 +3,194 @@ package com.follow.clash.service
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.common.ServiceDelegate
|
import com.follow.clash.common.ServiceDelegate
|
||||||
import com.follow.clash.common.chunkedForAidl
|
import com.follow.clash.common.chunkedForAidl
|
||||||
import com.follow.clash.common.intent
|
import com.follow.clash.common.intent
|
||||||
import com.follow.clash.core.Core
|
import com.follow.clash.core.Core
|
||||||
|
import com.follow.clash.service.State.delegate
|
||||||
|
import com.follow.clash.service.State.intent
|
||||||
|
import com.follow.clash.service.State.runLock
|
||||||
import com.follow.clash.service.models.NotificationParams
|
import com.follow.clash.service.models.NotificationParams
|
||||||
import com.follow.clash.service.models.VpnOptions
|
import com.follow.clash.service.models.VpnOptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
class RemoteService : Service(),
|
class RemoteService : Service(),
|
||||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||||
private var delegate: ServiceDelegate<IBaseService>? = null
|
private fun handleStopService(result: IResultInterface) {
|
||||||
private var intent: Intent? = null
|
|
||||||
|
|
||||||
private fun handleStopService() {
|
|
||||||
launch {
|
launch {
|
||||||
delegate?.useService { service ->
|
runLock.withLock {
|
||||||
service.stop()
|
delegate?.useService { service ->
|
||||||
delegate?.unbind()
|
service.stop()
|
||||||
}
|
delegate?.unbind()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleStartService() {
|
|
||||||
launch {
|
|
||||||
val nextIntent = when (State.options?.enable == true) {
|
|
||||||
true -> VpnService::class.intent
|
|
||||||
false -> CommonService::class.intent
|
|
||||||
}
|
|
||||||
if (intent != nextIntent) {
|
|
||||||
delegate?.unbind()
|
|
||||||
delegate = ServiceDelegate(nextIntent) { binder ->
|
|
||||||
when (binder) {
|
|
||||||
is VpnService.LocalBinder -> binder.getService()
|
|
||||||
is CommonService.LocalBinder -> binder.getService()
|
|
||||||
else -> throw IllegalArgumentException("Invalid binder type")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
intent = nextIntent
|
State.runTime = 0
|
||||||
delegate?.bind()
|
result.onResult(0)
|
||||||
}
|
|
||||||
delegate?.useService { service ->
|
|
||||||
service.start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val binder: IRemoteInterface.Stub = object : IRemoteInterface.Stub() {
|
private fun handleServiceDisconnected(message: String) {
|
||||||
|
GlobalState.log("Background service disconnected: $message")
|
||||||
|
intent = null
|
||||||
|
delegate = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleStartService(runTime: Long, result: IResultInterface) {
|
||||||
|
launch {
|
||||||
|
runLock.withLock {
|
||||||
|
val nextIntent = when (State.options?.enable == true) {
|
||||||
|
true -> VpnService::class.intent
|
||||||
|
false -> CommonService::class.intent
|
||||||
|
}
|
||||||
|
if (intent != nextIntent) {
|
||||||
|
delegate?.unbind()
|
||||||
|
delegate = ServiceDelegate(nextIntent, ::handleServiceDisconnected) { binder ->
|
||||||
|
when (binder) {
|
||||||
|
is VpnService.LocalBinder -> binder.getService()
|
||||||
|
is CommonService.LocalBinder -> binder.getService()
|
||||||
|
else -> throw IllegalArgumentException("Invalid binder type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intent = nextIntent
|
||||||
|
delegate?.bind()
|
||||||
|
}
|
||||||
|
delegate?.useService { service ->
|
||||||
|
service.start()
|
||||||
|
}
|
||||||
|
State.runTime = when (runTime != 0L) {
|
||||||
|
true -> runTime
|
||||||
|
false -> System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
result.onResult(State.runTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val binder = object : IRemoteInterface.Stub() {
|
||||||
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
||||||
Core.invokeAction(data) {
|
Core.invokeAction(data) {
|
||||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
launch {
|
||||||
val totalSize = chunks.size
|
runCatching {
|
||||||
chunks.forEachIndexed { index, chunk ->
|
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||||
callback.onResult(chunk, totalSize - 1 == index)
|
for ((index, chunk) in chunks.withIndex()) {
|
||||||
|
suspendCancellableCoroutine { cont ->
|
||||||
|
callback.onResult(
|
||||||
|
chunk,
|
||||||
|
index == chunks.lastIndex,
|
||||||
|
object : IAckInterface.Stub() {
|
||||||
|
override fun onAck() {
|
||||||
|
cont.resume(Unit)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun quickSetup(
|
||||||
|
initParamsString: String,
|
||||||
|
setupParamsString: String,
|
||||||
|
callback: ICallbackInterface,
|
||||||
|
onStarted: IVoidInterface
|
||||||
|
) {
|
||||||
|
Core.quickSetup(initParamsString, setupParamsString) {
|
||||||
|
launch {
|
||||||
|
runCatching {
|
||||||
|
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||||
|
for ((index, chunk) in chunks.withIndex()) {
|
||||||
|
suspendCancellableCoroutine { cont ->
|
||||||
|
callback.onResult(
|
||||||
|
chunk,
|
||||||
|
index == chunks.lastIndex,
|
||||||
|
object : IAckInterface.Stub() {
|
||||||
|
override fun onAck() {
|
||||||
|
cont.resume(Unit)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onStarted()
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateNotificationParams(params: NotificationParams?) {
|
override fun updateNotificationParams(params: NotificationParams?) {
|
||||||
State.notificationParamsFlow.tryEmit(params)
|
State.notificationParamsFlow.tryEmit(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun startService(
|
override fun startService(
|
||||||
options: VpnOptions, inApp: Boolean
|
options: VpnOptions,
|
||||||
|
runtime: Long,
|
||||||
|
result: IResultInterface,
|
||||||
) {
|
) {
|
||||||
|
GlobalState.log("remote startService")
|
||||||
State.options = options
|
State.options = options
|
||||||
State.inApp = inApp
|
handleStartService(runtime, result)
|
||||||
handleStartService()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopService() {
|
override fun stopService(result: IResultInterface) {
|
||||||
handleStopService()
|
handleStopService(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMessageCallback(messageCallback: IMessageInterface) {
|
override fun setEventListener(eventListener: IEventInterface?) {
|
||||||
setMessageCallback(messageCallback::onResult)
|
GlobalState.log("RemoveEventListener ${eventListener == null}")
|
||||||
}
|
when (eventListener != null) {
|
||||||
}
|
true -> Core.callSetEventListener {
|
||||||
|
launch {
|
||||||
|
runCatching {
|
||||||
|
val id = UUID.randomUUID().toString()
|
||||||
|
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||||
|
for ((index, chunk) in chunks.withIndex()) {
|
||||||
|
suspendCancellableCoroutine { cont ->
|
||||||
|
eventListener.onEvent(
|
||||||
|
id,
|
||||||
|
chunk,
|
||||||
|
index == chunks.lastIndex,
|
||||||
|
object : IAckInterface.Stub() {
|
||||||
|
override fun onAck() {
|
||||||
|
cont.resume(Unit)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setMessageCallback(cb: (result: String?) -> Unit) {
|
false -> Core.callSetEventListener(null)
|
||||||
Core.setMessageCallback(cb)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCrashlytics(enable: Boolean) {
|
||||||
|
GlobalState.setCrashlytics(enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRunTime(): Long {
|
||||||
|
return State.runTime
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder {
|
override fun onBind(intent: Intent?): IBinder {
|
||||||
return binder
|
return binder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
GlobalState.log("Remote service destroy")
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,22 @@
|
|||||||
package com.follow.clash.service
|
package com.follow.clash.service
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import com.follow.clash.common.ServiceDelegate
|
||||||
import com.follow.clash.service.models.NotificationParams
|
import com.follow.clash.service.models.NotificationParams
|
||||||
import com.follow.clash.service.models.VpnOptions
|
import com.follow.clash.service.models.VpnOptions
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
|
||||||
object State {
|
object State {
|
||||||
var options: VpnOptions? = null
|
var options: VpnOptions? = null
|
||||||
var inApp: Boolean = false
|
|
||||||
var notificationParamsFlow: MutableStateFlow<NotificationParams?> = MutableStateFlow(
|
var notificationParamsFlow: MutableStateFlow<NotificationParams?> = MutableStateFlow(
|
||||||
NotificationParams()
|
NotificationParams()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val runLock = Mutex()
|
||||||
|
var runTime: Long = 0L
|
||||||
|
|
||||||
|
var delegate: ServiceDelegate<IBaseService>? = null
|
||||||
|
|
||||||
|
var intent: Intent? = null
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,7 @@ import android.os.RemoteException
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import com.follow.clash.common.AccessControlMode
|
import com.follow.clash.common.AccessControlMode
|
||||||
import com.follow.clash.common.BroadcastAction
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.common.sendBroadcast
|
|
||||||
import com.follow.clash.core.Core
|
import com.follow.clash.core.Core
|
||||||
import com.follow.clash.service.models.VpnOptions
|
import com.follow.clash.service.models.VpnOptions
|
||||||
import com.follow.clash.service.models.getIpv4RouteAddress
|
import com.follow.clash.service.models.getIpv4RouteAddress
|
||||||
@@ -44,6 +43,11 @@ class VpnService : SystemVpnService(), IBaseService,
|
|||||||
handleCreate()
|
handleCreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
handleDestroy()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
private val connectivity by lazy {
|
private val connectivity by lazy {
|
||||||
getSystemService<ConnectivityManager>()
|
getSystemService<ConnectivityManager>()
|
||||||
}
|
}
|
||||||
@@ -107,11 +111,13 @@ class VpnService : SystemVpnService(), IBaseService,
|
|||||||
try {
|
try {
|
||||||
val isSuccess = super.onTransact(code, data, reply, flags)
|
val isSuccess = super.onTransact(code, data, reply, flags)
|
||||||
if (!isSuccess) {
|
if (!isSuccess) {
|
||||||
BroadcastAction.STOP.sendBroadcast()
|
GlobalState.log("VpnService disconnected")
|
||||||
|
handleDestroy()
|
||||||
}
|
}
|
||||||
return isSuccess
|
return isSuccess
|
||||||
} catch (e: RemoteException) {
|
} catch (e: RemoteException) {
|
||||||
throw e
|
GlobalState.log("VpnService onTransact $e")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +187,7 @@ class VpnService : SystemVpnService(), IBaseService,
|
|||||||
addDnsServer(DNS6)
|
addDnsServer(DNS6)
|
||||||
}
|
}
|
||||||
setMtu(9000)
|
setMtu(9000)
|
||||||
options.accessControl.let { accessControl ->
|
options.accessControlProps.let { accessControl ->
|
||||||
if (accessControl.enable) {
|
if (accessControl.enable) {
|
||||||
when (accessControl.mode) {
|
when (accessControl.mode) {
|
||||||
AccessControlMode.ACCEPT_SELECTED -> {
|
AccessControlMode.ACCEPT_SELECTED -> {
|
||||||
@@ -207,6 +213,7 @@ class VpnService : SystemVpnService(), IBaseService,
|
|||||||
allowBypass()
|
allowBypass()
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.systemProxy) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.systemProxy) {
|
||||||
|
GlobalState.log("Open http proxy")
|
||||||
setHttpProxy(
|
setHttpProxy(
|
||||||
ProxyInfo.buildDirectProxy(
|
ProxyInfo.buildDirectProxy(
|
||||||
"127.0.0.1", options.port, options.bypassDomain
|
"127.0.0.1", options.port, options.bypassDomain
|
||||||
@@ -227,9 +234,13 @@ class VpnService : SystemVpnService(), IBaseService,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
loader.load()
|
try {
|
||||||
State.options?.let {
|
loader.load()
|
||||||
handleStart(it)
|
State.options?.let {
|
||||||
|
handleStart(it)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.follow.clash.service.models
|
package com.follow.clash.service.models
|
||||||
|
|
||||||
|
import com.follow.clash.common.GlobalState
|
||||||
import com.follow.clash.common.formatBytes
|
import com.follow.clash.common.formatBytes
|
||||||
import com.follow.clash.core.Core
|
import com.follow.clash.core.Core
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
@@ -13,7 +14,12 @@ val Traffic.speedText: String
|
|||||||
get() = "${up.formatBytes}/s↑ ${down.formatBytes}/s↓"
|
get() = "${up.formatBytes}/s↑ ${down.formatBytes}/s↓"
|
||||||
|
|
||||||
fun Core.getSpeedTrafficText(onlyStatisticsProxy: Boolean): String {
|
fun Core.getSpeedTrafficText(onlyStatisticsProxy: Boolean): String {
|
||||||
val res = getTraffic(onlyStatisticsProxy)
|
try {
|
||||||
val traffic = Gson().fromJson(res, Traffic::class.java)
|
val res = getTraffic(onlyStatisticsProxy)
|
||||||
return traffic.speedText
|
val traffic = Gson().fromJson(res, Traffic::class.java)
|
||||||
|
return traffic.speedText
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GlobalState.log(e.message + "")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class AccessControl(
|
data class AccessControlProps(
|
||||||
val enable: Boolean,
|
val enable: Boolean,
|
||||||
val mode: AccessControlMode,
|
val mode: AccessControlMode,
|
||||||
val acceptList: List<String>,
|
val acceptList: List<String>,
|
||||||
@@ -19,7 +19,7 @@ data class VpnOptions(
|
|||||||
val port: Int,
|
val port: Int,
|
||||||
val ipv6: Boolean,
|
val ipv6: Boolean,
|
||||||
val dnsHijacking: Boolean,
|
val dnsHijacking: Boolean,
|
||||||
val accessControl: AccessControl,
|
val accessControlProps: AccessControlProps,
|
||||||
val allowBypass: Boolean,
|
val allowBypass: Boolean,
|
||||||
val systemProxy: Boolean,
|
val systemProxy: Boolean,
|
||||||
val bypassDomain: List<String>,
|
val bypassDomain: List<String>,
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class NetworkObserveModule(private val service: Service) : Module() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
preDnsList = dnsList
|
preDnsList = dnsList
|
||||||
Core.updateDNS(dnsList.joinToString { "," })
|
Core.updateDNS(dnsList.toSet().joinToString(","))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setUnderlyingNetworks(network: Network) {
|
fun setUnderlyingNetworks(network: Network) {
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ class NotificationModule(private val service: Service) : Module() {
|
|||||||
private val scope = CoroutineScope(Dispatchers.Default)
|
private val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
override fun onInstall() {
|
override fun onInstall() {
|
||||||
State.notificationParamsFlow.value?.let {
|
|
||||||
update(it.extended)
|
|
||||||
}
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val screenFlow = service.receiveBroadcastFlow {
|
val screenFlow = service.receiveBroadcastFlow {
|
||||||
addAction(Intent.ACTION_SCREEN_ON)
|
addAction(Intent.ACTION_SCREEN_ON)
|
||||||
@@ -69,6 +66,12 @@ class NotificationModule(private val service: Service) : Module() {
|
|||||||
.collect { (params, _) ->
|
.collect { (params, _) ->
|
||||||
update(params!!)
|
update(params!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
State.notificationParamsFlow.value?.let {
|
||||||
|
update(it.extended)
|
||||||
|
} ?: run {
|
||||||
|
update(NotificationParams().extended)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +93,13 @@ class NotificationModule(private val service: Service) : Module() {
|
|||||||
setSmallIcon(R.drawable.ic)
|
setSmallIcon(R.drawable.ic)
|
||||||
setContentTitle("FlClash")
|
setContentTitle("FlClash")
|
||||||
setContentIntent(intent.toPendingIntent)
|
setContentIntent(intent.toPendingIntent)
|
||||||
|
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||||
}
|
}
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
setShowWhen(false)
|
setShowWhen(true)
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,7 +109,6 @@ class NotificationModule(private val service: Service) : Module() {
|
|||||||
with(notificationBuilder) {
|
with(notificationBuilder) {
|
||||||
setContentTitle(params.title)
|
setContentTitle(params.title)
|
||||||
setContentText(params.contentText)
|
setContentText(params.contentText)
|
||||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
clearActions()
|
clearActions()
|
||||||
addAction(
|
addAction(
|
||||||
0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
|
0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
|
||||||
|
|||||||
17
android/service/src/main/res/drawable/ic_service.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:width="240dp"
|
||||||
|
android:height="240dp"
|
||||||
|
android:viewportWidth="240"
|
||||||
|
android:viewportHeight="240"
|
||||||
|
tools:ignore="VectorRaster">
|
||||||
|
<path
|
||||||
|
android:pathData="M48.1,80.89L168.44,11.41c11.08,-6.4 25.24,-2.6 31.64,8.48 0,0 0,0 0,0h0c6.4,11.08 2.6,25.24 -8.48,31.64 0,0 0,0 0,0l-120.34,69.48c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64 0,0 0,0 0,0Z"
|
||||||
|
android:fillColor="#6666FB"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M78.98,134.37l60.18,-34.74c11.07,-6.39 25.23,-2.59 31.63,8.48h0c6.4,11.07 2.61,25.24 -8.47,31.64l-60.18,34.74c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64h0Z"
|
||||||
|
android:fillColor="#336AB6"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M109.86,187.86h0c11.08,-6.4 25.24,-2.6 31.64,8.48 0,0 0,0 0,0h0c6.4,11.08 2.6,25.24 -8.48,31.64 0,0 0,0 0,0h0c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64 0,0 0,0 0,0Z"
|
||||||
|
android:fillColor="#5CA8E9"/>
|
||||||
|
</vector>
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
val flutterSdkPath = run {
|
val flutterSdkPath =
|
||||||
val properties = java.util.Properties()
|
run {
|
||||||
file("local.properties").inputStream().use { properties.load(it) }
|
val properties = java.util.Properties()
|
||||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
flutterSdkPath
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
}
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
@@ -18,8 +19,10 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.12.1" apply false
|
id("com.android.application") version "8.12.2" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.2.10" apply false
|
id("org.jetbrains.kotlin.android") version "2.2.10" apply false
|
||||||
|
id("com.google.gms.google-services") version ("4.3.15") apply false
|
||||||
|
id("com.google.firebase.crashlytics") version ("2.8.1") apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -129,14 +129,8 @@
|
|||||||
"compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.",
|
"compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.",
|
||||||
"notSelectedTip": "The current proxy group cannot be selected.",
|
"notSelectedTip": "The current proxy group cannot be selected.",
|
||||||
"tip": "tip",
|
"tip": "tip",
|
||||||
"backupAndRecovery": "Backup and Recovery",
|
|
||||||
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
|
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"backup": "Backup",
|
"backup": "Backup",
|
||||||
"recovery": "Recovery",
|
|
||||||
"recoveryProfiles": "Only recovery profiles",
|
|
||||||
"recoveryAll": "Recovery all data",
|
|
||||||
"recoverySuccess": "Recovery success",
|
|
||||||
"backupSuccess": "Backup success",
|
"backupSuccess": "Backup success",
|
||||||
"noInfo": "No info",
|
"noInfo": "No info",
|
||||||
"pleaseBindWebDAV": "Please bind WebDAV",
|
"pleaseBindWebDAV": "Please bind WebDAV",
|
||||||
@@ -219,9 +213,7 @@
|
|||||||
"local": "Local",
|
"local": "Local",
|
||||||
"remote": "Remote",
|
"remote": "Remote",
|
||||||
"remoteBackupDesc": "Backup local data to WebDAV",
|
"remoteBackupDesc": "Backup local data to WebDAV",
|
||||||
"remoteRecoveryDesc": "Recovery data from WebDAV",
|
|
||||||
"localBackupDesc": "Backup local data to local",
|
"localBackupDesc": "Backup local data to local",
|
||||||
"localRecoveryDesc": "Recovery data from file",
|
|
||||||
"mode": "Mode",
|
"mode": "Mode",
|
||||||
"time": "Time",
|
"time": "Time",
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
@@ -340,6 +332,8 @@
|
|||||||
"none": "none",
|
"none": "none",
|
||||||
"basicConfig": "Basic configuration",
|
"basicConfig": "Basic configuration",
|
||||||
"basicConfigDesc": "Modify the basic configuration globally",
|
"basicConfigDesc": "Modify the basic configuration globally",
|
||||||
|
"advancedConfig": "Advanced configuration",
|
||||||
|
"advancedConfigDesc": "Provide diverse configuration options",
|
||||||
"selectedCountTitle": "{count} items have been selected",
|
"selectedCountTitle": "{count} items have been selected",
|
||||||
"addRule": "Add rule",
|
"addRule": "Add rule",
|
||||||
"ruleName": "Rule name",
|
"ruleName": "Rule name",
|
||||||
@@ -379,9 +373,9 @@
|
|||||||
"systemApp": "System APP",
|
"systemApp": "System APP",
|
||||||
"noNetworkApp": "No network APP",
|
"noNetworkApp": "No network APP",
|
||||||
"contactMe": "Contact me",
|
"contactMe": "Contact me",
|
||||||
"recoveryStrategy": "Recovery strategy",
|
"restoreStrategy": "Restore strategy",
|
||||||
"recoveryStrategy_override": "Override",
|
"restoreStrategy_override": "Override",
|
||||||
"recoveryStrategy_compatible": "Compatible",
|
"restoreStrategy_compatible": "Compatible",
|
||||||
"logsTest": "Logs test",
|
"logsTest": "Logs test",
|
||||||
"emptyTip": "{label} cannot be empty",
|
"emptyTip": "{label} cannot be empty",
|
||||||
"urlTip": "{label} must be a url",
|
"urlTip": "{label} must be a url",
|
||||||
@@ -390,7 +384,7 @@
|
|||||||
"existsTip": "Current {label} already exists",
|
"existsTip": "Current {label} already exists",
|
||||||
"deleteTip": "Are you sure you want to delete the current {label}?",
|
"deleteTip": "Are you sure you want to delete the current {label}?",
|
||||||
"deleteMultipTip": "Are you sure you want to delete the selected {label}?",
|
"deleteMultipTip": "Are you sure you want to delete the selected {label}?",
|
||||||
"nullTip": "No {label} at the moment",
|
"nullTip": "No {label} yet",
|
||||||
"script": "Script",
|
"script": "Script",
|
||||||
"color": "Color",
|
"color": "Color",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
@@ -409,7 +403,7 @@
|
|||||||
"autoSetSystemDns": "Auto set system DNS",
|
"autoSetSystemDns": "Auto set system DNS",
|
||||||
"details": "{label} details",
|
"details": "{label} details",
|
||||||
"creationTime": "Creation time",
|
"creationTime": "Creation time",
|
||||||
"progress": "Progress",
|
"process": "Process",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"destination": "Destination",
|
"destination": "Destination",
|
||||||
"destinationGeoIP": "Destination GeoIP",
|
"destinationGeoIP": "Destination GeoIP",
|
||||||
@@ -428,5 +422,59 @@
|
|||||||
"restartCoreTip": "Are you sure you want to restart the core?",
|
"restartCoreTip": "Are you sure you want to restart the core?",
|
||||||
"forceRestartCoreTip": "Are you sure you want to force restart the core?",
|
"forceRestartCoreTip": "Are you sure you want to force restart the core?",
|
||||||
"dnsHijacking": "DNS hijacking",
|
"dnsHijacking": "DNS hijacking",
|
||||||
"coreStatus": "Core status"
|
"coreStatus": "Core status",
|
||||||
|
"dataCollectionTip": "Data Collection Notice",
|
||||||
|
"dataCollectionContent": "This app uses Firebase Crashlytics to collect crash information to improve app stability.\nThe collected data includes device information and crash details, but does not contain personal sensitive data.\nYou can disable this feature in settings.",
|
||||||
|
"crashlytics": "Crash Analysis",
|
||||||
|
"crashlyticsTip": "When enabled, automatically uploads crash logs without sensitive information when the app crashes",
|
||||||
|
"appendSystemDns": "Append System DNS",
|
||||||
|
"appendSystemDnsTip": "Forcefully append system DNS to the configuration",
|
||||||
|
"editRule": "Edit rule",
|
||||||
|
"overrideMode": "Override mode",
|
||||||
|
"standardModeDesc": "Standard mode, override basic configuration, provide simple rule addition capability",
|
||||||
|
"scriptModeDesc": "Script mode, use external extension scripts, provide one-click override configuration capability",
|
||||||
|
"addedRules": "Added rules",
|
||||||
|
"controlGlobalAddedRules": "Control global added rules",
|
||||||
|
"overrideScript": "Override script",
|
||||||
|
"goToConfigureScript": "Go to configure script",
|
||||||
|
"editGlobalRules": "Edit global rules",
|
||||||
|
"externalFetch": "External fetch",
|
||||||
|
"confirmForceCrashCore": "Are you sure you want to force crash the core?",
|
||||||
|
"confirmClearAllData": "Are you sure you want to clear all data?",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"loadTest": "Load test",
|
||||||
|
"yearsAgo": "{count, plural, =1{1 year ago} other{{count} years ago}}",
|
||||||
|
"monthsAgo": "{count, plural, =1{1 month ago} other{{count} months ago}}",
|
||||||
|
"daysAgo": "{count, plural, =1{1 day ago} other{{count} days ago}}",
|
||||||
|
"hoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}",
|
||||||
|
"minutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}",
|
||||||
|
"justNow": "Just now",
|
||||||
|
"noLongerRemind": "Don't remind again",
|
||||||
|
"accessControlSettings": "Access Control Settings",
|
||||||
|
"turnOn": "Turn On",
|
||||||
|
"turnOff": "Turn Off",
|
||||||
|
"coreConfigChangeDetected": "Core configuration change detected",
|
||||||
|
"reload": "Reload",
|
||||||
|
"vpnConfigChangeDetected": "VPN configuration change detected",
|
||||||
|
"restart": "Restart",
|
||||||
|
"speedStatistics": "Speed statistics",
|
||||||
|
"resetPageChangesTip": "The current page has changes. Are you sure you want to reset?",
|
||||||
|
"overwriteTypeCustom": "Custom",
|
||||||
|
"overwriteTypeCustomDesc": "Custom mode, fully customize proxy groups and rules",
|
||||||
|
"unknownNetworkError": "Unknown network error",
|
||||||
|
"networkRequestException": "Network request exception, please try again later.",
|
||||||
|
"restoreException": "Recovery exception",
|
||||||
|
"networkException": "Network exception, please check your connection and try again",
|
||||||
|
"invalidBackupFile": "Invalid backup file",
|
||||||
|
"pruneCache": "Prune cache",
|
||||||
|
"backupAndRestore": "Backup and Restore",
|
||||||
|
"backupAndRestoreDesc": "Sync data via WebDAV or files",
|
||||||
|
"restore": "Restore",
|
||||||
|
"restoreSuccess": "Restore success",
|
||||||
|
"restoreFromWebDAVDesc": "Restore data via WebDAV",
|
||||||
|
"restoreFromFileDesc": "Restore data via file",
|
||||||
|
"restoreOnlyConfig": "Restore configuration files only",
|
||||||
|
"restoreAllData": "Restore all data",
|
||||||
|
"addProfile": "Add Profile",
|
||||||
|
"delayTest": "Delay Test"
|
||||||
}
|
}
|
||||||
@@ -129,14 +129,8 @@
|
|||||||
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
|
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
|
||||||
"notSelectedTip": "現在のプロキシグループは選択できません",
|
"notSelectedTip": "現在のプロキシグループは選択できません",
|
||||||
"tip": "ヒント",
|
"tip": "ヒント",
|
||||||
"backupAndRecovery": "バックアップと復元",
|
|
||||||
"backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
|
|
||||||
"account": "アカウント",
|
"account": "アカウント",
|
||||||
"backup": "バックアップ",
|
"backup": "バックアップ",
|
||||||
"recovery": "復元",
|
|
||||||
"recoveryProfiles": "プロファイルのみ復元",
|
|
||||||
"recoveryAll": "全データ復元",
|
|
||||||
"recoverySuccess": "復元成功",
|
|
||||||
"backupSuccess": "バックアップ成功",
|
"backupSuccess": "バックアップ成功",
|
||||||
"noInfo": "情報なし",
|
"noInfo": "情報なし",
|
||||||
"pleaseBindWebDAV": "WebDAVをバインドしてください",
|
"pleaseBindWebDAV": "WebDAVをバインドしてください",
|
||||||
@@ -219,9 +213,7 @@
|
|||||||
"local": "ローカル",
|
"local": "ローカル",
|
||||||
"remote": "リモート",
|
"remote": "リモート",
|
||||||
"remoteBackupDesc": "WebDAVにデータをバックアップ",
|
"remoteBackupDesc": "WebDAVにデータをバックアップ",
|
||||||
"remoteRecoveryDesc": "WebDAVからデータを復元",
|
|
||||||
"localBackupDesc": "ローカルにデータをバックアップ",
|
"localBackupDesc": "ローカルにデータをバックアップ",
|
||||||
"localRecoveryDesc": "ファイルからデータを復元",
|
|
||||||
"mode": "モード",
|
"mode": "モード",
|
||||||
"time": "時間",
|
"time": "時間",
|
||||||
"source": "ソース",
|
"source": "ソース",
|
||||||
@@ -340,6 +332,8 @@
|
|||||||
"none": "なし",
|
"none": "なし",
|
||||||
"basicConfig": "基本設定",
|
"basicConfig": "基本設定",
|
||||||
"basicConfigDesc": "基本設定をグローバルに変更",
|
"basicConfigDesc": "基本設定をグローバルに変更",
|
||||||
|
"advancedConfig": "高度な設定",
|
||||||
|
"advancedConfigDesc": "多様な設定を提供",
|
||||||
"selectedCountTitle": "{count} 項目が選択されています",
|
"selectedCountTitle": "{count} 項目が選択されています",
|
||||||
"addRule": "ルールを追加",
|
"addRule": "ルールを追加",
|
||||||
"ruleName": "ルール名",
|
"ruleName": "ルール名",
|
||||||
@@ -380,9 +374,9 @@
|
|||||||
"systemApp": "システムアプリ",
|
"systemApp": "システムアプリ",
|
||||||
"noNetworkApp": "ネットワークなしアプリ",
|
"noNetworkApp": "ネットワークなしアプリ",
|
||||||
"contactMe": "連絡する",
|
"contactMe": "連絡する",
|
||||||
"recoveryStrategy": "リカバリー戦略",
|
"restoreStrategy": "復元ストラテジー",
|
||||||
"recoveryStrategy_override": "オーバーライド",
|
"restoreStrategy_override": "上書き",
|
||||||
"recoveryStrategy_compatible": "互換性",
|
"restoreStrategy_compatible": "互換",
|
||||||
"logsTest": "ログテスト",
|
"logsTest": "ログテスト",
|
||||||
"emptyTip": "{label}は空欄にできません",
|
"emptyTip": "{label}は空欄にできません",
|
||||||
"urlTip": "{label}はURLである必要があります",
|
"urlTip": "{label}はURLである必要があります",
|
||||||
@@ -391,7 +385,7 @@
|
|||||||
"existsTip": "現在の{label}は既に存在しています",
|
"existsTip": "現在の{label}は既に存在しています",
|
||||||
"deleteTip": "現在の{label}を削除してもよろしいですか?",
|
"deleteTip": "現在の{label}を削除してもよろしいですか?",
|
||||||
"deleteMultipTip": "選択された{label}を削除してもよろしいですか?",
|
"deleteMultipTip": "選択された{label}を削除してもよろしいですか?",
|
||||||
"nullTip": "現在{label}はありません",
|
"nullTip": "まだ{label}はありません",
|
||||||
"script": "スクリプト",
|
"script": "スクリプト",
|
||||||
"color": "カラー",
|
"color": "カラー",
|
||||||
"rename": "リネーム",
|
"rename": "リネーム",
|
||||||
@@ -410,7 +404,7 @@
|
|||||||
"autoSetSystemDns": "オートセットシステムDNS",
|
"autoSetSystemDns": "オートセットシステムDNS",
|
||||||
"details": "{label}詳細",
|
"details": "{label}詳細",
|
||||||
"creationTime": "作成時間",
|
"creationTime": "作成時間",
|
||||||
"progress": "進捗",
|
"process": "プロセス",
|
||||||
"host": "ホスト",
|
"host": "ホスト",
|
||||||
"destination": "宛先",
|
"destination": "宛先",
|
||||||
"destinationGeoIP": "宛先地理情報",
|
"destinationGeoIP": "宛先地理情報",
|
||||||
@@ -429,5 +423,59 @@
|
|||||||
"restartCoreTip": "コアを再起動してもよろしいですか?",
|
"restartCoreTip": "コアを再起動してもよろしいですか?",
|
||||||
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?",
|
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?",
|
||||||
"dnsHijacking": "DNSハイジャッキング",
|
"dnsHijacking": "DNSハイジャッキング",
|
||||||
"coreStatus": "コアステータス"
|
"coreStatus": "コアステータス",
|
||||||
|
"dataCollectionTip": "データ収集説明",
|
||||||
|
"dataCollectionContent": "本アプリはFirebase Crashlyticsを使用してクラッシュ情報を収集し、アプリの安定性を向上させます。\n収集されるデータにはデバイス情報とクラッシュ詳細が含まれますが、個人の機密データは含まれません。\n設定でこの機能を無効にすることができます。",
|
||||||
|
"crashlytics": "クラッシュ分析",
|
||||||
|
"crashlyticsTip": "有効にすると、アプリがクラッシュした際に機密情報を含まないクラッシュログを自動的にアップロードします",
|
||||||
|
"appendSystemDns": "システムDNSを追加",
|
||||||
|
"appendSystemDnsTip": "設定にシステムDNSを強制的に追加します",
|
||||||
|
"editRule": "ルールを編集",
|
||||||
|
"overrideMode": "上書きモード",
|
||||||
|
"standardModeDesc": "標準モード、基本設定を上書きし、シンプルなルール追加機能を提供",
|
||||||
|
"scriptModeDesc": "スクリプトモード、外部拡張スクリプトを使用し、ワンクリックで設定を上書きする機能を提供",
|
||||||
|
"addedRules": "追加ルール",
|
||||||
|
"controlGlobalAddedRules": "グローバル追加ルールを制御",
|
||||||
|
"overrideScript": "上書きスクリプト",
|
||||||
|
"goToConfigureScript": "スクリプト設定に移動",
|
||||||
|
"editGlobalRules": "グローバルルールを編集",
|
||||||
|
"externalFetch": "外部取得",
|
||||||
|
"confirmForceCrashCore": "コアを強制的にクラッシュさせてもよろしいですか?",
|
||||||
|
"confirmClearAllData": "すべてのデータをクリアしてもよろしいですか?",
|
||||||
|
"loading": "読み込み中...",
|
||||||
|
"loadTest": "読み込みテスト",
|
||||||
|
"yearsAgo": "{count}年前",
|
||||||
|
"monthsAgo": "{count}ヶ月前",
|
||||||
|
"daysAgo": "{count}日前",
|
||||||
|
"hoursAgo": "{count}時間前",
|
||||||
|
"minutesAgo": "{count}分前",
|
||||||
|
"justNow": "たった今",
|
||||||
|
"noLongerRemind": "今後表示しない",
|
||||||
|
"accessControlSettings": "アクセス制御設定",
|
||||||
|
"turnOn": "オン",
|
||||||
|
"turnOff": "オフ",
|
||||||
|
"coreConfigChangeDetected": "コア設定の変更が検出されました",
|
||||||
|
"reload": "リロード",
|
||||||
|
"vpnConfigChangeDetected": "VPN設定の変更が検出されました",
|
||||||
|
"restart": "再起動",
|
||||||
|
"speedStatistics": "速度統計",
|
||||||
|
"resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?",
|
||||||
|
"overwriteTypeCustom": "カスタム",
|
||||||
|
"overwriteTypeCustomDesc": "カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
|
||||||
|
"unknownNetworkError": "不明なネットワークエラー",
|
||||||
|
"networkRequestException": "ネットワーク要求例外、後でもう一度試してください。",
|
||||||
|
"restoreException": "復元例外",
|
||||||
|
"networkException": "ネットワーク例外、接続を確認してもう一度お試しください",
|
||||||
|
"invalidBackupFile": "無効なバックアップファイル",
|
||||||
|
"pruneCache": "キャッシュの削除",
|
||||||
|
"backupAndRestore": "バックアップと復元",
|
||||||
|
"backupAndRestoreDesc": "WebDAVまたはファイルを介してデータを同期する",
|
||||||
|
"restore": "復元",
|
||||||
|
"restoreSuccess": "復元に成功しました",
|
||||||
|
"restoreFromWebDAVDesc": "WebDAVを介してデータを復元する",
|
||||||
|
"restoreFromFileDesc": "ファイルを介してデータを復元する",
|
||||||
|
"restoreOnlyConfig": "設定ファイルのみを復元する",
|
||||||
|
"restoreAllData": "すべてのデータを復元する",
|
||||||
|
"addProfile": "プロファイルを追加",
|
||||||
|
"delayTest": "遅延テスト"
|
||||||
}
|
}
|
||||||
@@ -340,6 +340,8 @@
|
|||||||
"none": "Нет",
|
"none": "Нет",
|
||||||
"basicConfig": "Базовая конфигурация",
|
"basicConfig": "Базовая конфигурация",
|
||||||
"basicConfigDesc": "Глобальное изменение базовых настроек",
|
"basicConfigDesc": "Глобальное изменение базовых настроек",
|
||||||
|
"advancedConfig": "Расширенная конфигурация",
|
||||||
|
"advancedConfigDesc": "Предоставляет разнообразные варианты конфигурации",
|
||||||
"selectedCountTitle": "Выбрано {count} элементов",
|
"selectedCountTitle": "Выбрано {count} элементов",
|
||||||
"addRule": "Добавить правило",
|
"addRule": "Добавить правило",
|
||||||
"ruleName": "Название правила",
|
"ruleName": "Название правила",
|
||||||
@@ -380,9 +382,9 @@
|
|||||||
"systemApp": "Системное приложение",
|
"systemApp": "Системное приложение",
|
||||||
"noNetworkApp": "Приложение без сети",
|
"noNetworkApp": "Приложение без сети",
|
||||||
"contactMe": "Свяжитесь со мной",
|
"contactMe": "Свяжитесь со мной",
|
||||||
"recoveryStrategy": "Стратегия восстановления",
|
"restoreStrategy": "Стратегия восстановления",
|
||||||
"recoveryStrategy_override": "Переопределение",
|
"restoreStrategy_override": "Перезаписать",
|
||||||
"recoveryStrategy_compatible": "Совместимый",
|
"restoreStrategy_compatible": "Совместимый",
|
||||||
"logsTest": "Тест журналов",
|
"logsTest": "Тест журналов",
|
||||||
"emptyTip": "{label} не может быть пустым",
|
"emptyTip": "{label} не может быть пустым",
|
||||||
"urlTip": "{label} должен быть URL",
|
"urlTip": "{label} должен быть URL",
|
||||||
@@ -391,7 +393,7 @@
|
|||||||
"existsTip": "Текущий {label} уже существует",
|
"existsTip": "Текущий {label} уже существует",
|
||||||
"deleteTip": "Вы уверены, что хотите удалить текущий {label}?",
|
"deleteTip": "Вы уверены, что хотите удалить текущий {label}?",
|
||||||
"deleteMultipTip": "Вы уверены, что хотите удалить выбранные {label}?",
|
"deleteMultipTip": "Вы уверены, что хотите удалить выбранные {label}?",
|
||||||
"nullTip": "Сейчас {label} нет",
|
"nullTip": "{label} пока отсутствуют",
|
||||||
"script": "Скрипт",
|
"script": "Скрипт",
|
||||||
"color": "Цвет",
|
"color": "Цвет",
|
||||||
"rename": "Переименовать",
|
"rename": "Переименовать",
|
||||||
@@ -410,7 +412,7 @@
|
|||||||
"autoSetSystemDns": "Автоматическая настройка системного DNS",
|
"autoSetSystemDns": "Автоматическая настройка системного DNS",
|
||||||
"details": "Детали {}",
|
"details": "Детали {}",
|
||||||
"creationTime": "Время создания",
|
"creationTime": "Время создания",
|
||||||
"progress": "Прогресс",
|
"process": "процесс",
|
||||||
"host": "Хост",
|
"host": "Хост",
|
||||||
"destination": "Назначение",
|
"destination": "Назначение",
|
||||||
"destinationGeoIP": "Геолокация назначения",
|
"destinationGeoIP": "Геолокация назначения",
|
||||||
@@ -429,5 +431,59 @@
|
|||||||
"restartCoreTip": "Вы уверены, что хотите перезапустить ядро?",
|
"restartCoreTip": "Вы уверены, что хотите перезапустить ядро?",
|
||||||
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?",
|
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?",
|
||||||
"dnsHijacking": "DNS-перехват",
|
"dnsHijacking": "DNS-перехват",
|
||||||
"coreStatus": "Основной статус"
|
"coreStatus": "Основной статус",
|
||||||
|
"dataCollectionTip": "Уведомление о сборе данных",
|
||||||
|
"dataCollectionContent": "Это приложение использует Firebase Crashlytics для сбора информации о сбоях nhằm улучшения стабильности приложения.\nСобираемые данные включают информацию об устройстве и подробности о сбоях, но не содержат персональных конфиденциальных данных.\nВы можете отключить эту функцию в настройках.",
|
||||||
|
"crashlytics": "Анализ сбоев",
|
||||||
|
"crashlyticsTip": "При включении автоматически загружает журналы сбоев без конфиденциальной информации, когда приложение выходит из строя",
|
||||||
|
"appendSystemDns": "Добавить системный DNS",
|
||||||
|
"appendSystemDnsTip": "Принудительно добавить системный DNS к конфигурации",
|
||||||
|
"editRule": "Редактировать правило",
|
||||||
|
"overrideMode": "Режим переопределения",
|
||||||
|
"standardModeDesc": "Стандартный режим, переопределение базовой конфигурации, предоставление возможности простого добавления правил",
|
||||||
|
"scriptModeDesc": "Режим скрипта, использование внешних расширяющих скриптов, предоставление возможности переопределения конфигурации одним кликом",
|
||||||
|
"addedRules": "Добавленные правила",
|
||||||
|
"controlGlobalAddedRules": "Управление глобальными добавленными правилами",
|
||||||
|
"overrideScript": "Скрипт переопределения",
|
||||||
|
"goToConfigureScript": "Перейти к настройке скрипта",
|
||||||
|
"editGlobalRules": "Редактировать глобальные правила",
|
||||||
|
"externalFetch": "Внешнее получение",
|
||||||
|
"confirmForceCrashCore": "Вы уверены, что хотите принудительно аварийно завершить работу ядра?",
|
||||||
|
"confirmClearAllData": "Вы уверены, что хотите очистить все данные?",
|
||||||
|
"loading": "Загрузка...",
|
||||||
|
"loadTest": "Тест загрузки",
|
||||||
|
"yearsAgo": "{count, plural, one{{count} год назад} few{{count} года назад} many{{count} лет назад} other{{count} года назад}}",
|
||||||
|
"monthsAgo": "{count, plural, one{{count} месяц назад} few{{count} месяца назад} many{{count} месяцев назад} other{{count} месяца назад}}",
|
||||||
|
"daysAgo": "{count, plural, one{{count} день назад} few{{count} дня назад} many{{count} дней назад} other{{count} дня назад}}",
|
||||||
|
"hoursAgo": "{count, plural, one{{count} час назад} few{{count} часа назад} many{{count} часов назад} other{{count} часа назад}}",
|
||||||
|
"minutesAgo": "{count, plural, one{{count} минута назад} few{{count} минуты назад} many{{count} минут назад} other{{count} минуты назад}}",
|
||||||
|
"justNow": "Только что",
|
||||||
|
"noLongerRemind": "Больше не напоминать",
|
||||||
|
"accessControlSettings": "Настройки контроля доступа",
|
||||||
|
"turnOn": "Включить",
|
||||||
|
"turnOff": "Выключить",
|
||||||
|
"coreConfigChangeDetected": "Обнаружено изменение конфигурации ядра",
|
||||||
|
"reload": "Перезагрузить",
|
||||||
|
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
|
||||||
|
"restart": "Перезапустить",
|
||||||
|
"speedStatistics": "Статистика скорости",
|
||||||
|
"resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?",
|
||||||
|
"overwriteTypeCustom": "Пользовательский",
|
||||||
|
"overwriteTypeCustomDesc": "Пользовательский режим, полная настройка групп прокси и правил",
|
||||||
|
"unknownNetworkError": "Неизвестная сетевая ошибка",
|
||||||
|
"networkRequestException": "Исключение сетевого запроса, пожалуйста, попробуйте позже.",
|
||||||
|
"restoreException": "Ошибка восстановления",
|
||||||
|
"networkException": "Ошибка сети, проверьте соединение и попробуйте еще раз",
|
||||||
|
"invalidBackupFile": "Неверный файл резервной копии",
|
||||||
|
"pruneCache": "Очистить кэш",
|
||||||
|
"backupAndRestore": "Резервное копирование и восстановление",
|
||||||
|
"backupAndRestoreDesc": "Синхронизация данных через WebDAV или файлы",
|
||||||
|
"restore": "Восстановить",
|
||||||
|
"restoreSuccess": "Восстановление успешно",
|
||||||
|
"restoreFromWebDAVDesc": "Восстановить данные через WebDAV",
|
||||||
|
"restoreFromFileDesc": "Восстановить данные из файла",
|
||||||
|
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
|
||||||
|
"restoreAllData": "Восстановить все данные",
|
||||||
|
"addProfile": "Добавить профиль",
|
||||||
|
"delayTest": "Тест задержки"
|
||||||
}
|
}
|
||||||
@@ -129,14 +129,8 @@
|
|||||||
"compatibleDesc": "开启将失去部分应用能力,获得全量的Clash的支持",
|
"compatibleDesc": "开启将失去部分应用能力,获得全量的Clash的支持",
|
||||||
"notSelectedTip": "当前代理组无法选中",
|
"notSelectedTip": "当前代理组无法选中",
|
||||||
"tip": "提示",
|
"tip": "提示",
|
||||||
"backupAndRecovery": "备份与恢复",
|
|
||||||
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
|
|
||||||
"account": "账号",
|
"account": "账号",
|
||||||
"backup": "备份",
|
"backup": "备份",
|
||||||
"recovery": "恢复",
|
|
||||||
"recoveryProfiles": "仅恢复配置文件",
|
|
||||||
"recoveryAll": "恢复所有数据",
|
|
||||||
"recoverySuccess": "恢复成功",
|
|
||||||
"backupSuccess": "备份成功",
|
"backupSuccess": "备份成功",
|
||||||
"noInfo": "暂无信息",
|
"noInfo": "暂无信息",
|
||||||
"pleaseBindWebDAV": "请绑定WebDAV",
|
"pleaseBindWebDAV": "请绑定WebDAV",
|
||||||
@@ -219,9 +213,7 @@
|
|||||||
"local": "本地",
|
"local": "本地",
|
||||||
"remote": "远程",
|
"remote": "远程",
|
||||||
"remoteBackupDesc": "备份数据到WebDAV",
|
"remoteBackupDesc": "备份数据到WebDAV",
|
||||||
"remoteRecoveryDesc": "通过WebDAV恢复数据",
|
|
||||||
"localBackupDesc": "备份数据到本地",
|
"localBackupDesc": "备份数据到本地",
|
||||||
"localRecoveryDesc": "通过文件恢复数据",
|
|
||||||
"mode": "模式",
|
"mode": "模式",
|
||||||
"time": "时间",
|
"time": "时间",
|
||||||
"source": "来源",
|
"source": "来源",
|
||||||
@@ -340,6 +332,8 @@
|
|||||||
"none": "无",
|
"none": "无",
|
||||||
"basicConfig": "基本配置",
|
"basicConfig": "基本配置",
|
||||||
"basicConfigDesc": "全局修改基本配置",
|
"basicConfigDesc": "全局修改基本配置",
|
||||||
|
"advancedConfig": "进阶配置",
|
||||||
|
"advancedConfigDesc": "提供多样化配置",
|
||||||
"selectedCountTitle": "已选择 {count} 项",
|
"selectedCountTitle": "已选择 {count} 项",
|
||||||
"addRule": "添加规则",
|
"addRule": "添加规则",
|
||||||
"ruleName": "规则名称",
|
"ruleName": "规则名称",
|
||||||
@@ -380,9 +374,9 @@
|
|||||||
"systemApp": "系统应用",
|
"systemApp": "系统应用",
|
||||||
"noNetworkApp": "无网络应用",
|
"noNetworkApp": "无网络应用",
|
||||||
"contactMe": "联系我",
|
"contactMe": "联系我",
|
||||||
"recoveryStrategy": "恢复策略",
|
"restoreStrategy": "恢复策略",
|
||||||
"recoveryStrategy_override": "覆盖",
|
"restoreStrategy_override": "覆盖",
|
||||||
"recoveryStrategy_compatible": "兼容",
|
"restoreStrategy_compatible": "兼容",
|
||||||
"logsTest": "日志测试",
|
"logsTest": "日志测试",
|
||||||
"emptyTip": "{label}不能为空",
|
"emptyTip": "{label}不能为空",
|
||||||
"urlTip": "{label}必须为URL",
|
"urlTip": "{label}必须为URL",
|
||||||
@@ -410,7 +404,7 @@
|
|||||||
"autoSetSystemDns": "自动设置系统DNS",
|
"autoSetSystemDns": "自动设置系统DNS",
|
||||||
"details": "{label}详情",
|
"details": "{label}详情",
|
||||||
"creationTime": "创建时间",
|
"creationTime": "创建时间",
|
||||||
"progress": "进度",
|
"process": "进程",
|
||||||
"host": "主机",
|
"host": "主机",
|
||||||
"destination": "目标地址",
|
"destination": "目标地址",
|
||||||
"destinationGeoIP": "目标地理定位",
|
"destinationGeoIP": "目标地理定位",
|
||||||
@@ -429,5 +423,59 @@
|
|||||||
"restartCoreTip": "您确定要重启核心吗?",
|
"restartCoreTip": "您确定要重启核心吗?",
|
||||||
"forceRestartCoreTip": "您确定要强制重启核心吗?",
|
"forceRestartCoreTip": "您确定要强制重启核心吗?",
|
||||||
"dnsHijacking": "DNS劫持",
|
"dnsHijacking": "DNS劫持",
|
||||||
"coreStatus": "核心状态"
|
"coreStatus": "核心状态",
|
||||||
|
"dataCollectionTip": "数据收集说明",
|
||||||
|
"dataCollectionContent": "本应用使用 Firebase Crashlytics 收集崩溃信息以改进应用稳定性。\n收集的数据包括设备信息和崩溃详情,不包含个人敏感数据。\n您可以在设置中关闭此功能。",
|
||||||
|
"crashlytics": "崩溃分析",
|
||||||
|
"crashlyticsTip": "开启后,应用崩溃时自动上传不包含敏感信息的崩溃日志",
|
||||||
|
"appendSystemDns": "追加系统DNS",
|
||||||
|
"appendSystemDnsTip": "强制为配置附加系统DNS",
|
||||||
|
"editRule": "编辑规则",
|
||||||
|
"overrideMode": "覆写模式",
|
||||||
|
"standardModeDesc": "标准模式,覆写基本配置,提供简单追加规则能力",
|
||||||
|
"scriptModeDesc": "脚本模式,使用外部扩展脚本,提供一键覆写配置的能力",
|
||||||
|
"addedRules": "附加规则",
|
||||||
|
"controlGlobalAddedRules": "控制全局附加规则",
|
||||||
|
"overrideScript": "覆写脚本",
|
||||||
|
"goToConfigureScript": "前往配置脚本",
|
||||||
|
"editGlobalRules": "编辑全局规则",
|
||||||
|
"externalFetch": "外部获取",
|
||||||
|
"confirmForceCrashCore": "确定要强制崩溃核心?",
|
||||||
|
"confirmClearAllData": "确定要清除所有数据?",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"loadTest": "加载测试",
|
||||||
|
"yearsAgo": "{count} 年前",
|
||||||
|
"monthsAgo": "{count} 个月前",
|
||||||
|
"daysAgo": "{count} 天前",
|
||||||
|
"hoursAgo": "{count} 小时前",
|
||||||
|
"minutesAgo": "{count} 分钟前",
|
||||||
|
"justNow": "刚刚",
|
||||||
|
"noLongerRemind": "不再提示",
|
||||||
|
"accessControlSettings": "访问控制设置",
|
||||||
|
"turnOn": "开启",
|
||||||
|
"turnOff": "关闭",
|
||||||
|
"coreConfigChangeDetected": "检测到核心配置更改",
|
||||||
|
"reload": "重载",
|
||||||
|
"vpnConfigChangeDetected": "检测到VPN相关配置改动",
|
||||||
|
"restart": "重启",
|
||||||
|
"speedStatistics": "网速统计",
|
||||||
|
"resetPageChangesTip": "当前页面存在更改,确定重置吗?",
|
||||||
|
"overwriteTypeCustom": "自定义",
|
||||||
|
"overwriteTypeCustomDesc": "自定义模式,支持完全自定义修改代理组以及规则",
|
||||||
|
"unknownNetworkError": "未知网络错误",
|
||||||
|
"networkRequestException": "网络请求异常,请稍后再试。",
|
||||||
|
"restoreException": "恢复异常",
|
||||||
|
"networkException": "网络异常,请检查连接后重试",
|
||||||
|
"invalidBackupFile": "无效备份文件",
|
||||||
|
"pruneCache": "修剪缓存",
|
||||||
|
"backupAndRestore": "备份与恢复",
|
||||||
|
"backupAndRestoreDesc": "通过WebDAV或者文件同步数据",
|
||||||
|
"restore": "恢复",
|
||||||
|
"restoreSuccess": "恢复成功",
|
||||||
|
"restoreFromWebDAVDesc": "通过WebDAV恢复数据",
|
||||||
|
"restoreFromFileDesc": "通过文件恢复数据",
|
||||||
|
"restoreOnlyConfig": "仅恢复配置文件",
|
||||||
|
"restoreAllData": "恢复所有数据",
|
||||||
|
"addProfile": "添加配置",
|
||||||
|
"delayTest": "延迟测试"
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/data/GEOIP.metadb
Normal file
45523
assets/data/GEOSITE.dat
Normal file
35960
assets/data/GeoSite.dat
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
23
assets/images/empty/connection.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
|
||||||
|
|
||||||
|
<rect x="95" y="90" width="36" height="12" rx="4" fill="#E8DEF8"/>
|
||||||
|
<rect x="140" y="90" width="65" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<path d="M95 118H205" stroke="#E8DEF8" stroke-width="2" stroke-dasharray="4 4"/>
|
||||||
|
|
||||||
|
<rect x="95" y="138" width="40" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
|
||||||
|
<rect x="145" y="138" width="50" height="12" rx="6" fill="#E8DEF8" opacity="0.5"/>
|
||||||
|
|
||||||
|
<rect x="95" y="162" width="55" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
|
||||||
|
<rect x="160" y="162" width="30" height="12" rx="6" fill="#E8DEF8" opacity="0.5"/>
|
||||||
|
|
||||||
|
<g transform="translate(210, 210)">
|
||||||
|
<circle cx="0" cy="0" r="38" fill="#6750A4" stroke="#FDF7FF" stroke-width="6"/>
|
||||||
|
|
||||||
|
<path d="M-10 16V-16M-10 -16L-18 -8M-10 -16L-2 -8" stroke="#FDF7FF" stroke-width="5" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
<path d="M10 -16V16M10 16L2 8M10 16L18 8" stroke="#FDF7FF" stroke-width="5" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
9
assets/images/empty/data.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
|
||||||
|
<rect x="100" y="90" width="100" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="100" y="115" width="70" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="100" y="140" width="80" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="155" y="170" width="80" height="60" rx="12" fill="#6750A4"/>
|
||||||
|
<rect x="150" y="165" width="90" height="18" rx="6" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
|
||||||
|
<rect x="185" y="200" width="20" height="6" rx="3" fill="#FDF7FF" opacity="0.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 700 B |
25
assets/images/empty/log.svg
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M90 200C90 180 100 165 150 165C200 165 210 180 210 200V220C210 231.046 201.046 240 190 240H110C98.9543 240 90 231.046 90 220V200Z"
|
||||||
|
fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<rect x="75" y="85" width="150" height="100" rx="30" fill="#6750A4"/>
|
||||||
|
|
||||||
|
<rect x="85" y="95" width="130" height="80" rx="22" fill="#6750A4" stroke="#7D66B5" stroke-width="2"/>
|
||||||
|
|
||||||
|
<path d="M110 135 C110 142 118 148 128 148 C138 148 146 142 146 135" stroke="#FDF7FF" stroke-width="4"
|
||||||
|
stroke-linecap="round"/>
|
||||||
|
<path d="M154 135 C154 142 162 148 172 148 C182 148 190 142 190 135" stroke="#FDF7FF" stroke-width="4"
|
||||||
|
stroke-linecap="round"/>
|
||||||
|
<circle cx="150" cy="160" r="4" fill="#E8DEF8" opacity="0.5"/>
|
||||||
|
|
||||||
|
<path d="M150 85 V 65" stroke="#6750A4" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<circle cx="150" cy="60" r="8" fill="#6750A4"/>
|
||||||
|
<circle cx="150" cy="60" r="3" fill="#FDF7FF"/>
|
||||||
|
|
||||||
|
<path d="M220 70 L235 70 L220 85 H235" stroke="#6750A4" stroke-width="3" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
<path d="M245 40 L255 40 L245 50 H255" stroke="#E8DEF8" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<path d="M90 185 H210" stroke="#000" stroke-width="4" stroke-opacity="0.1" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
37
assets/images/empty/profile.svg
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<path d="M60 94V84C60 72.9543 68.9543 64 80 64H130C141.046 64 150 72.9543 150 84V94H220C231.046 94 240 102.954 240 114V210C240 221.046 231.046 230 220 230H80C68.9543 230 60 221.046 60 210V94Z"
|
||||||
|
fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
|
||||||
|
|
||||||
|
|
||||||
|
<rect x="90" y="124" width="60" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
|
||||||
|
<rect x="90" y="154" width="50" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<rect x="90" y="184" width="40" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="180" y="184" width="30" height="12" rx="6" fill="#E8DEF8" opacity="0.6"/>
|
||||||
|
<circle cx="186" cy="190" r="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<g transform="translate(210, 210)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M0 -32 C-17.67 -32 -32 -17.67 -32 0 C-32 17.67 -17.67 32 0 32 C17.67 32 32 17.67 32 0 C32 -17.67 17.67 -32 0 -32ZM0 -8 C-4.42 -8 -8 -4.42 -8 0 C-8 4.42 -4.42 8 0 8 C4.42 8 8 4.42 8 0 C8 -4.42 4.42 -8 0 -8Z"
|
||||||
|
fill="#6750A4" stroke="#FDF7FF" stroke-width="6"/>
|
||||||
|
|
||||||
|
<rect x="-5" y="-38" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
|
||||||
|
<rect x="-5" y="26" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
|
||||||
|
<rect x="-38" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
|
||||||
|
<rect x="26" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"/>
|
||||||
|
<rect x="-5" y="-38" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
|
||||||
|
transform="rotate(45)"/>
|
||||||
|
<rect x="-5" y="26" width="10" height="12" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
|
||||||
|
transform="rotate(45)"/>
|
||||||
|
<rect x="-38" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
|
||||||
|
transform="rotate(45)"/>
|
||||||
|
<rect x="26" y="-5" width="12" height="10" rx="3" fill="#6750A4" stroke="#FDF7FF" stroke-width="2"
|
||||||
|
transform="rotate(45)"/>
|
||||||
|
|
||||||
|
<circle cx="0" cy="0" r="22" fill="#6750A4"/>
|
||||||
|
<circle cx="0" cy="0" r="8" fill="#FDF7FF" opacity="0.8"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
18
assets/images/empty/proxy.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
|
||||||
|
<rect x="95" y="90" width="110" height="16" rx="4" fill="#E8DEF8"/>
|
||||||
|
<circle cx="105" cy="98" r="3" fill="#FDF7FF"/>
|
||||||
|
<circle cx="115" cy="98" r="3" fill="#FDF7FF"/>
|
||||||
|
<rect x="95" y="120" width="110" height="16" rx="4" fill="#E8DEF8"/>
|
||||||
|
<circle cx="105" cy="128" r="3" fill="#FDF7FF"/>
|
||||||
|
<circle cx="115" cy="128" r="3" fill="#FDF7FF"/>
|
||||||
|
<rect x="95" y="150" width="80" height="16" rx="4" fill="#E8DEF8"/>
|
||||||
|
<circle cx="105" cy="158" r="3" fill="#FDF7FF"/>
|
||||||
|
<circle cx="115" cy="158" r="3" fill="#FDF7FF"/>
|
||||||
|
<circle cx="195" cy="195" r="30" fill="#6750A4"/>
|
||||||
|
<rect x="180" y="193" width="30" height="4" rx="2" fill="#FDF7FF" transform="rotate(45 195 195)"/>
|
||||||
|
<circle cx="183" cy="183" r="4" fill="#FDF7FF"/>
|
||||||
|
<circle cx="207" cy="207" r="4" fill="#FDF7FF"/>
|
||||||
|
<path d="M175 158 H190 V165" stroke="#E8DEF8" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
31
assets/images/empty/rule.svg
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
|
||||||
|
|
||||||
|
|
||||||
|
<rect x="95" y="90" width="80" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<rect x="185" y="90" width="25" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
|
||||||
|
|
||||||
|
|
||||||
|
<rect x="95" y="125" width="60" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="165" y="125" width="45" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
|
||||||
|
|
||||||
|
|
||||||
|
<rect x="95" y="160" width="70" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="175" y="160" width="35" height="12" rx="6" fill="#E8DEF8" opacity="0.7"/>
|
||||||
|
|
||||||
|
|
||||||
|
<g transform="translate(210, 210)">
|
||||||
|
<circle cx="0" cy="0" r="36" fill="#6750A4" stroke="#FDF7FF" stroke-width="6"/>
|
||||||
|
|
||||||
|
<circle cx="0" cy="0" r="24" stroke="#FDF7FF" stroke-width="3"/>
|
||||||
|
|
||||||
|
<path d="M-24 0C-24 0 -12 8 0 8C12 8 24 0 24 0" stroke="#FDF7FF" stroke-width="3" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<ellipse cx="0" cy="0" rx="10" ry="24" stroke="#FDF7FF" stroke-width="3"/>
|
||||||
|
|
||||||
|
<circle cx="14" cy="-12" r="3" fill="#FDF7FF"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
19
assets/images/empty/script.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="75" y="50" width="150" height="180" rx="24" fill="#FDF7FF" stroke="#E8DEF8" stroke-width="2"/>
|
||||||
|
|
||||||
|
<rect x="100" y="90" width="30" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
<rect x="136" y="90" width="50" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<rect x="120" y="120" width="80" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<rect x="120" y="150" width="50" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<rect x="100" y="180" width="20" height="12" rx="6" fill="#E8DEF8"/>
|
||||||
|
|
||||||
|
<g transform="translate(165, 160)">
|
||||||
|
<rect x="0" y="0" width="80" height="80" rx="20" fill="#6750A4" stroke="#FDF7FF" stroke-width="4"/>
|
||||||
|
<path d="M28 30L18 40L28 50" stroke="#FDF7FF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M52 30L62 40L52 50" stroke="#FDF7FF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M46 26L34 54" stroke="#FDF7FF" stroke-width="4" stroke-linecap="round" opacity="0.8"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/images/icon/status_1.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
assets/images/icon/status_1.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
assets/images/icon/status_2.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
assets/images/icon/status_2.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
assets/images/icon/status_3.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
assets/images/icon/status_3.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -6,6 +6,7 @@ targets:
|
|||||||
build_extensions:
|
build_extensions:
|
||||||
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
|
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
|
||||||
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
|
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
|
||||||
|
'^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart'
|
||||||
freezed:
|
freezed:
|
||||||
options:
|
options:
|
||||||
build_extensions:
|
build_extensions:
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ func handleAction(action *Action, result ActionResult) {
|
|||||||
result.success(handleShutdown())
|
result.success(handleShutdown())
|
||||||
return
|
return
|
||||||
case validateConfigMethod:
|
case validateConfigMethod:
|
||||||
data := []byte(action.Data.(string))
|
path := action.Data.(string)
|
||||||
result.success(handleValidateConfig(data))
|
result.success(handleValidateConfig(path))
|
||||||
return
|
return
|
||||||
case updateConfigMethod:
|
case updateConfigMethod:
|
||||||
data := []byte(action.Data.(string))
|
data := []byte(action.Data.(string))
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
void (*release_object_func)(void *obj);
|
void (*release_object_func)(void *obj);
|
||||||
|
|
||||||
|
void (*free_string_func)(char *data);
|
||||||
|
|
||||||
void (*protect_func)(void *tun_interface, int fd);
|
void (*protect_func)(void *tun_interface, int fd);
|
||||||
|
|
||||||
char* (*resolve_process_func)(void *tun_interface,int protocol, const char *source, const char *target, int uid);
|
char* (*resolve_process_func)(void *tun_interface,int protocol, const char *source, const char *target, int uid);
|
||||||
@@ -20,6 +22,10 @@ void release_object(void *obj) {
|
|||||||
release_object_func(obj);
|
release_object_func(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void free_string(char *data) {
|
||||||
|
free_string_func(data);
|
||||||
|
}
|
||||||
|
|
||||||
void result(void *invoke_Interface, const char *data) {
|
void result(void *invoke_Interface, const char *data) {
|
||||||
return result_func(invoke_Interface, data);
|
return result_func(invoke_Interface, data);
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ func resolveProcess(callback unsafe.Pointer, protocol int, source, target string
|
|||||||
t := C.CString(target)
|
t := C.CString(target)
|
||||||
defer C.free(unsafe.Pointer(t))
|
defer C.free(unsafe.Pointer(t))
|
||||||
res := C.resolve_process(callback, C.int(protocol), s, t, C.int(uid))
|
res := C.resolve_process(callback, C.int(protocol), s, t, C.int(uid))
|
||||||
return parseCString(res)
|
return takeCString(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeResult(callback unsafe.Pointer, data string) {
|
func invokeResult(callback unsafe.Pointer, data string) {
|
||||||
@@ -29,7 +29,7 @@ func releaseObject(callback unsafe.Pointer) {
|
|||||||
C.release_object(callback)
|
C.release_object(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCString(s *C.char) string {
|
func takeCString(s *C.char) string {
|
||||||
//defer C.free(unsafe.Pointer(s))
|
defer C.free_string(s)
|
||||||
return C.GoString(s)
|
return C.GoString(s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
extern void (*release_object_func)(void *obj);
|
extern void (*release_object_func)(void *obj);
|
||||||
|
|
||||||
|
extern void (*free_string_func)(char *data);
|
||||||
|
|
||||||
extern void (*protect_func)(void *tun_interface, int fd);
|
extern void (*protect_func)(void *tun_interface, int fd);
|
||||||
|
|
||||||
extern char* (*resolve_process_func)(void *tun_interface, int protocol, const char *source, const char *target, int uid);
|
extern char* (*resolve_process_func)(void *tun_interface, int protocol, const char *source, const char *target, int uid);
|
||||||
@@ -16,4 +18,6 @@ extern char* resolve_process(void *tun_interface, int protocol, const char *sour
|
|||||||
|
|
||||||
extern void release_object(void *obj);
|
extern void release_object(void *obj);
|
||||||
|
|
||||||
|
extern void free_string(char *data);
|
||||||
|
|
||||||
extern void result(void *invoke_Interface, const char *data);
|
extern void result(void *invoke_Interface, const char *data);
|
||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/tunnel"
|
"github.com/metacubex/mihomo/tunnel"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,12 +37,6 @@ var (
|
|||||||
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExternalProviders []ExternalProvider
|
|
||||||
|
|
||||||
func (a ExternalProviders) Len() int { return len(a) }
|
|
||||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
|
||||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
func getExternalProvidersRaw() map[string]cp.Provider {
|
func getExternalProvidersRaw() map[string]cp.Provider {
|
||||||
eps := make(map[string]cp.Provider)
|
eps := make(map[string]cp.Provider)
|
||||||
for n, p := range tunnel.Providers() {
|
for n, p := range tunnel.Providers() {
|
||||||
@@ -116,11 +111,15 @@ func updateListeners() {
|
|||||||
listeners := currentConfig.Listeners
|
listeners := currentConfig.Listeners
|
||||||
general := currentConfig.General
|
general := currentConfig.General
|
||||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
||||||
listener.SetAllowLan(general.AllowLan)
|
|
||||||
|
allowLan := general.AllowLan
|
||||||
|
listener.SetAllowLan(allowLan)
|
||||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||||
inbound.SetAllowedIPs(general.LanAllowedIPs)
|
inbound.SetAllowedIPs(general.LanAllowedIPs)
|
||||||
inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
|
inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
|
||||||
listener.SetBindAddress(general.BindAddress)
|
|
||||||
|
bindAddress := general.BindAddress
|
||||||
|
listener.SetBindAddress(bindAddress)
|
||||||
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
||||||
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
||||||
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
||||||
@@ -236,7 +235,8 @@ func updateConfig(params *UpdateParams) {
|
|||||||
updateListeners()
|
updateListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupConfig(params *SetupParams) error {
|
func applyConfig(params *SetupParams) error {
|
||||||
|
runtime.GC()
|
||||||
runLock.Lock()
|
runLock.Lock()
|
||||||
defer runLock.Unlock()
|
defer runLock.Unlock()
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ type ExternalProvider struct {
|
|||||||
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
|
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxiesData struct {
|
||||||
|
Proxies map[string]constant.Proxy `json:"proxies"`
|
||||||
|
All []string `json:"all"`
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
messageMethod Method = "message"
|
messageMethod Method = "message"
|
||||||
initClashMethod Method = "initClash"
|
initClashMethod Method = "initClash"
|
||||||
|
|||||||
65
core/go.mod
@@ -10,6 +10,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
|
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
|
||||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
@@ -18,87 +19,85 @@ require (
|
|||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/enfein/mieru/v3 v3.26.2 // indirect
|
||||||
github.com/enfein/mieru/v3 v3.19.1 // indirect
|
|
||||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
|
||||||
github.com/go-chi/render v1.0.3 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.4.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
github.com/gofrs/uuid/v5 v5.3.2 // indirect
|
github.com/gofrs/uuid/v5 v5.4.0 // indirect
|
||||||
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
|
github.com/klauspost/reedsolomon v1.12.3 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a // indirect
|
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d // indirect
|
||||||
github.com/metacubex/ascon v0.1.0 // indirect
|
github.com/metacubex/ascon v0.1.0 // indirect
|
||||||
github.com/metacubex/bart v0.20.5 // indirect
|
github.com/metacubex/bart v0.26.0 // indirect
|
||||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
|
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
|
||||||
github.com/metacubex/blake3 v0.1.0 // indirect
|
github.com/metacubex/blake3 v0.1.0 // indirect
|
||||||
github.com/metacubex/chacha v0.1.5 // indirect
|
github.com/metacubex/chacha v0.1.5 // indirect
|
||||||
|
github.com/metacubex/chi v0.1.0 // indirect
|
||||||
|
github.com/metacubex/cpu v0.1.0 // indirect
|
||||||
github.com/metacubex/fswatch v0.1.1 // indirect
|
github.com/metacubex/fswatch v0.1.1 // indirect
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect
|
||||||
|
github.com/metacubex/hkdf v0.1.0 // indirect
|
||||||
|
github.com/metacubex/hpke v0.1.0 // indirect
|
||||||
|
github.com/metacubex/http v0.1.0 // indirect
|
||||||
|
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
|
||||||
|
github.com/metacubex/mlkem v0.1.0 // indirect
|
||||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
|
github.com/metacubex/qpack v0.6.0 // indirect
|
||||||
|
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 // indirect
|
||||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||||
github.com/metacubex/restls-client-go v0.1.7 // indirect
|
github.com/metacubex/restls-client-go v0.1.7 // indirect
|
||||||
github.com/metacubex/sing v0.5.5 // indirect
|
github.com/metacubex/sing v0.5.6 // indirect
|
||||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac // indirect
|
github.com/metacubex/sing-mux v0.3.4 // indirect
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb // indirect
|
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e // indirect
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 // indirect
|
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||||
github.com/metacubex/sing-tun v0.4.7 // indirect
|
github.com/metacubex/sing-tun v0.4.11 // indirect
|
||||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 // indirect
|
github.com/metacubex/sing-vmess v0.2.4 // indirect
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
|
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
|
||||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 // indirect
|
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
|
||||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 // indirect
|
github.com/metacubex/tls v0.1.1 // indirect
|
||||||
|
github.com/metacubex/utls v1.8.4 // indirect
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
|
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
|
||||||
|
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
|
||||||
github.com/miekg/dns v1.1.63 // indirect
|
github.com/miekg/dns v1.1.63 // indirect
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
|
||||||
github.com/openacid/low v0.1.21 // indirect
|
github.com/openacid/low v0.1.21 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
|
||||||
github.com/sagernet/cors v1.2.1 // indirect
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
github.com/samber/lo v1.51.0 // indirect
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
||||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
|
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
|
||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/crypto v0.33.0 // indirect
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||||
@@ -106,7 +105,7 @@ require (
|
|||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.10.0 // indirect
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
golang.org/x/tools v0.24.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
157
core/go.sum
@@ -1,3 +1,5 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
|
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
|
||||||
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
|
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
|
||||||
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
|
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
|
||||||
@@ -12,9 +14,6 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
|
|||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -22,10 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
|
||||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||||
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
|
|
||||||
github.com/enfein/mieru/v3 v3.19.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
|
||||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||||
@@ -39,37 +36,26 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
|||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
|
||||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||||
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
|
||||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
@@ -78,76 +64,94 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
|||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
|
||||||
|
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
|
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d h1:vAJ0ZT4aO803F1uw2roIA9yH7Sxzox34tVVyye1bz6c=
|
||||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||||
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
||||||
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
||||||
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
|
github.com/metacubex/bart v0.26.0 h1:d/bBTvVatfVWGfQbiDpYKI1bXUJgjaabB2KpK1Tnk6w=
|
||||||
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
github.com/metacubex/bart v0.26.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
||||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
||||||
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
||||||
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
|
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
|
||||||
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
||||||
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||||
|
github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg=
|
||||||
|
github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g=
|
||||||
|
github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk=
|
||||||
|
github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
|
||||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||||
|
github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||||
|
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||||
|
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||||
|
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||||
|
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||||
|
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||||
|
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||||
|
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||||
|
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
|
||||||
|
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
|
||||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
|
github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
|
||||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
|
github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
|
||||||
|
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 h1:RlT3bFCIDM/NR9GWaDbFCrweOwpHRfgaT9c0zuRlPhY=
|
||||||
|
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
|
||||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
|
||||||
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
|
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||||
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
|
||||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
|
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
|
||||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
|
github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||||
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
|
github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
|
||||||
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
|
github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
|
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
|
||||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
|
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
|
github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0=
|
||||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
|
github.com/metacubex/tls v0.1.1/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||||
|
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||||
|
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
||||||
|
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
|
||||||
|
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49/go.mod h1:MBeEa9IVBphH7vc3LNtW6ZujVXFizotPo3OEiHQ+TNU=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
|
||||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
|
||||||
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
|
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
|
||||||
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
|
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
|
||||||
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
|
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
|
||||||
@@ -160,18 +164,10 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
|
||||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
|
||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||||
@@ -181,21 +177,10 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e
|
|||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
@@ -209,14 +194,12 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
|
|||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
|
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
|
||||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
|
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
|
||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@@ -240,30 +223,24 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
102
core/hub.go
@@ -1,9 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/metacubex/mihomo/adapter"
|
"github.com/metacubex/mihomo/adapter"
|
||||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||||
"github.com/metacubex/mihomo/common/observable"
|
"github.com/metacubex/mihomo/common/observable"
|
||||||
@@ -13,16 +13,18 @@ import (
|
|||||||
"github.com/metacubex/mihomo/component/updater"
|
"github.com/metacubex/mihomo/component/updater"
|
||||||
"github.com/metacubex/mihomo/config"
|
"github.com/metacubex/mihomo/config"
|
||||||
"github.com/metacubex/mihomo/constant"
|
"github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/constant/features"
|
||||||
cp "github.com/metacubex/mihomo/constant/provider"
|
cp "github.com/metacubex/mihomo/constant/provider"
|
||||||
"github.com/metacubex/mihomo/hub/executor"
|
"github.com/metacubex/mihomo/hub/executor"
|
||||||
"github.com/metacubex/mihomo/listener"
|
"github.com/metacubex/mihomo/listener"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
"github.com/metacubex/mihomo/tunnel"
|
"github.com/metacubex/mihomo/tunnel"
|
||||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -34,16 +36,16 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func handleInitClash(paramsString string) bool {
|
func handleInitClash(paramsString string) bool {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
var params = InitParams{}
|
var params = InitParams{}
|
||||||
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
err := json.Unmarshal([]byte(paramsString), ¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
version = params.Version
|
version = params.Version
|
||||||
if !isInit {
|
constant.SetHomeDir(params.HomeDir)
|
||||||
constant.SetHomeDir(params.HomeDir)
|
isInit = true
|
||||||
isInit = true
|
|
||||||
}
|
|
||||||
return isInit
|
return isInit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +63,7 @@ func handleStopListener() bool {
|
|||||||
defer runLock.Unlock()
|
defer runLock.Unlock()
|
||||||
isRunning = false
|
isRunning = false
|
||||||
listener.StopListener()
|
listener.StopListener()
|
||||||
|
resolver.ResetConnection()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,32 +72,76 @@ func handleGetIsInit() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleForceGC() {
|
func handleForceGC() {
|
||||||
go func() {
|
log.Infoln("[APP] request force GC")
|
||||||
log.Infoln("[APP] request force GC")
|
runtime.GC()
|
||||||
runtime.GC()
|
if features.Android {
|
||||||
}()
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleShutdown() bool {
|
func handleShutdown() bool {
|
||||||
stopListeners()
|
stopListeners()
|
||||||
executor.Shutdown()
|
executor.Shutdown()
|
||||||
runtime.GC()
|
handleForceGC()
|
||||||
isInit = false
|
isInit = false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleValidateConfig(bytes []byte) string {
|
func handleValidateConfig(path string) string {
|
||||||
_, err := config.UnmarshalRawConfig(bytes)
|
buf, err := readFile(path)
|
||||||
|
_, err = config.UnmarshalRawConfig(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetProxies() map[string]constant.Proxy {
|
func handleGetProxies() ProxiesData {
|
||||||
runLock.Lock()
|
runLock.Lock()
|
||||||
defer runLock.Unlock()
|
defer runLock.Unlock()
|
||||||
return tunnel.ProxiesWithProviders()
|
|
||||||
|
nameList := config.GetProxyNameList()
|
||||||
|
|
||||||
|
proxies := make(map[string]constant.Proxy)
|
||||||
|
|
||||||
|
for name, proxy := range tunnel.Proxies() {
|
||||||
|
proxies[name] = proxy
|
||||||
|
}
|
||||||
|
for _, p := range tunnel.Providers() {
|
||||||
|
for _, proxy := range p.Proxies() {
|
||||||
|
proxies[proxy.Name()] = proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasGlobal := false
|
||||||
|
allNames := make([]string, 0, len(nameList)+1)
|
||||||
|
|
||||||
|
for _, name := range nameList {
|
||||||
|
if name == "GLOBAL" {
|
||||||
|
hasGlobal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ok := proxies[name]
|
||||||
|
if !ok || p == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch p.Type() {
|
||||||
|
case constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance:
|
||||||
|
allNames = append(allNames, name)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasGlobal {
|
||||||
|
if p, ok := proxies["GLOBAL"]; ok && p != nil {
|
||||||
|
allNames = append([]string{"GLOBAL"}, allNames...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProxiesData{
|
||||||
|
All: allNames,
|
||||||
|
Proxies: proxies,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleChangeProxy(data string, fn func(string string)) {
|
func handleChangeProxy(data string, fn func(string string)) {
|
||||||
@@ -137,28 +184,28 @@ func handleChangeProxy(data string, fn func(string string)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleGetTraffic(onlyStatisticsProxy bool) string {
|
func handleGetTraffic(onlyStatisticsProxy bool) string {
|
||||||
up, down := statistic.DefaultManager.Current(onlyStatisticsProxy)
|
up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy)
|
||||||
traffic := map[string]int64{
|
traffic := map[string]int64{
|
||||||
"up": up,
|
"up": up,
|
||||||
"down": down,
|
"down": down,
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(traffic)
|
data, err := json.Marshal(traffic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error:", err)
|
log.Errorln("Error: %s", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(data)
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
|
func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
|
||||||
up, down := statistic.DefaultManager.Total(onlyStatisticsProxy)
|
up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy)
|
||||||
traffic := map[string]int64{
|
traffic := map[string]int64{
|
||||||
"up": up,
|
"up": up,
|
||||||
"down": down,
|
"down": down,
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(traffic)
|
data, err := json.Marshal(traffic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error:", err)
|
log.Errorln("Error: %s", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(data)
|
return string(data)
|
||||||
@@ -228,7 +275,7 @@ func handleGetConnections() string {
|
|||||||
snapshot := statistic.DefaultManager.Snapshot()
|
snapshot := statistic.DefaultManager.Snapshot()
|
||||||
data, err := json.Marshal(snapshot)
|
data, err := json.Marshal(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error:", err)
|
log.Errorln("Error: %s", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(data)
|
return string(data)
|
||||||
@@ -281,7 +328,9 @@ func handleGetExternalProviders() string {
|
|||||||
}
|
}
|
||||||
eps = append(eps, *externalProvider)
|
eps = append(eps, *externalProvider)
|
||||||
}
|
}
|
||||||
sort.Sort(ExternalProviders(eps))
|
slices.SortFunc(eps, func(a, b ExternalProvider) int {
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
data, err := json.Marshal(eps)
|
data, err := json.Marshal(eps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -323,13 +372,13 @@ func handleUpdateGeoData(geoType string, geoName string, fn func(value string))
|
|||||||
fn(err.Error())
|
fn(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "GeoIp":
|
case "GEOIP":
|
||||||
err := updater.UpdateGeoIpWithPath(path)
|
err := updater.UpdateGeoIpWithPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fn(err.Error())
|
fn(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "GeoSite":
|
case "GEOSITE":
|
||||||
err := updater.UpdateGeoSiteWithPath(path)
|
err := updater.UpdateGeoSiteWithPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fn(err.Error())
|
fn(err.Error())
|
||||||
@@ -483,14 +532,17 @@ func handleDelFile(path string, result ActionResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleSetupConfig(bytes []byte) string {
|
func handleSetupConfig(bytes []byte) string {
|
||||||
|
if !isInit {
|
||||||
|
return "not initialized"
|
||||||
|
}
|
||||||
var params = defaultSetupParams()
|
var params = defaultSetupParams()
|
||||||
err := UnmarshalJson(bytes, params)
|
err := UnmarshalJson(bytes, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("unmarshalRawConfig error %v", err)
|
log.Errorln("unmarshalRawConfig error %v", err)
|
||||||
_ = setupConfig(defaultSetupParams())
|
_ = applyConfig(defaultSetupParams())
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
err = setupConfig(params)
|
err = applyConfig(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|||||||
55
core/lib.go
@@ -27,7 +27,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var messageCallback unsafe.Pointer
|
var eventListener unsafe.Pointer
|
||||||
|
|
||||||
type TunHandler struct {
|
type TunHandler struct {
|
||||||
listener *sing_tun.Listener
|
listener *sing_tun.Listener
|
||||||
@@ -37,6 +37,8 @@ type TunHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (th *TunHandler) start(fd int, stack, address, dns string) {
|
func (th *TunHandler) start(fd int, stack, address, dns string) {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
_ = th.limit.Acquire(context.TODO(), 4)
|
_ = th.limit.Acquire(context.TODO(), 4)
|
||||||
defer th.limit.Release(4)
|
defer th.limit.Release(4)
|
||||||
th.initHook()
|
th.initHook()
|
||||||
@@ -181,7 +183,7 @@ func nextHandle(action *Action, result ActionResult) bool {
|
|||||||
|
|
||||||
//export invokeAction
|
//export invokeAction
|
||||||
func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
||||||
params := parseCString(paramsChar)
|
params := takeCString(paramsChar)
|
||||||
var action = &Action{}
|
var action = &Action{}
|
||||||
err := json.Unmarshal([]byte(params), action)
|
err := json.Unmarshal([]byte(params), action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -198,35 +200,59 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
|
|||||||
|
|
||||||
//export startTUN
|
//export startTUN
|
||||||
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
|
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
|
||||||
handleStartTun(callback, int(fd), parseCString(stackChar), parseCString(addressChar), parseCString(dnsChar))
|
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
|
||||||
|
if !isRunning {
|
||||||
|
handleStartListener()
|
||||||
|
} else {
|
||||||
|
handleResetConnections()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
//export setMessageCallback
|
//export quickSetup
|
||||||
func setMessageCallback(callback unsafe.Pointer) {
|
func quickSetup(callback unsafe.Pointer, initParamsChar *C.char, setupParamsChar *C.char) {
|
||||||
if messageCallback != nil {
|
go func() {
|
||||||
releaseObject(messageCallback)
|
initParamsString := takeCString(initParamsChar)
|
||||||
|
setupParamsString := takeCString(setupParamsChar)
|
||||||
|
if !handleInitClash(initParamsString) {
|
||||||
|
invokeResult(callback, "init failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isRunning = true
|
||||||
|
message := handleSetupConfig([]byte(setupParamsString))
|
||||||
|
invokeResult(callback, message)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export setEventListener
|
||||||
|
func setEventListener(listener unsafe.Pointer) {
|
||||||
|
if eventListener != nil || listener == nil {
|
||||||
|
releaseObject(eventListener)
|
||||||
}
|
}
|
||||||
messageCallback = callback
|
eventListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
//export getTotalTraffic
|
//export getTotalTraffic
|
||||||
func getTotalTraffic(onlyStatisticsProxy bool) *C.char {
|
func getTotalTraffic(onlyStatisticsProxy bool) *C.char {
|
||||||
return C.CString(handleGetTotalTraffic(onlyStatisticsProxy))
|
data := C.CString(handleGetTotalTraffic(onlyStatisticsProxy))
|
||||||
|
defer C.free(unsafe.Pointer(data))
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
//export getTraffic
|
//export getTraffic
|
||||||
func getTraffic(onlyStatisticsProxy bool) *C.char {
|
func getTraffic(onlyStatisticsProxy bool) *C.char {
|
||||||
return C.CString(handleGetTraffic(onlyStatisticsProxy))
|
data := C.CString(handleGetTraffic(onlyStatisticsProxy))
|
||||||
|
defer C.free(unsafe.Pointer(data))
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(message Message) {
|
func sendMessage(message Message) {
|
||||||
if messageCallback == nil {
|
if eventListener == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result := ActionResult{
|
result := ActionResult{
|
||||||
Method: messageMethod,
|
Method: messageMethod,
|
||||||
callback: messageCallback,
|
callback: eventListener,
|
||||||
Data: message,
|
Data: message,
|
||||||
}
|
}
|
||||||
result.send()
|
result.send()
|
||||||
@@ -235,6 +261,9 @@ func sendMessage(message Message) {
|
|||||||
//export stopTun
|
//export stopTun
|
||||||
func stopTun() {
|
func stopTun() {
|
||||||
handleStopTun()
|
handleStopTun()
|
||||||
|
if isRunning {
|
||||||
|
handleStopListener()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export suspend
|
//export suspend
|
||||||
@@ -249,5 +278,5 @@ func forceGC() {
|
|||||||
|
|
||||||
//export updateDns
|
//export updateDns
|
||||||
func updateDns(s *C.char) {
|
func updateDns(s *C.char) {
|
||||||
handleUpdateDns(parseCString(s))
|
handleUpdateDns(takeCString(s))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
@@ -24,15 +25,15 @@ class Application extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ApplicationState extends ConsumerState<Application> {
|
class ApplicationState extends ConsumerState<Application> {
|
||||||
Timer? _autoUpdateGroupTaskTimer;
|
|
||||||
Timer? _autoUpdateProfilesTaskTimer;
|
Timer? _autoUpdateProfilesTaskTimer;
|
||||||
|
bool _preHasVpn = false;
|
||||||
|
|
||||||
final _pageTransitionsTheme = const PageTransitionsTheme(
|
final _pageTransitionsTheme = const PageTransitionsTheme(
|
||||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||||
TargetPlatform.android: CommonPageTransitionsBuilder(),
|
TargetPlatform.android: commonSharedXPageTransitions,
|
||||||
TargetPlatform.windows: CommonPageTransitionsBuilder(),
|
TargetPlatform.windows: commonSharedXPageTransitions,
|
||||||
TargetPlatform.linux: CommonPageTransitionsBuilder(),
|
TargetPlatform.linux: commonSharedXPageTransitions,
|
||||||
TargetPlatform.macOS: CommonPageTransitionsBuilder(),
|
TargetPlatform.macOS: commonSharedXPageTransitions,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -46,27 +47,27 @@ class ApplicationState extends ConsumerState<Application> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_autoUpdateProfilesTask();
|
|
||||||
globalState.appController = AppController(context, ref);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
final currentContext = globalState.navigatorKey.currentContext;
|
final currentContext = globalState.navigatorKey.currentContext;
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
globalState.appController = AppController(currentContext, ref);
|
await appController.attach(currentContext, ref);
|
||||||
|
} else {
|
||||||
|
exit(0);
|
||||||
}
|
}
|
||||||
await globalState.appController.init();
|
_autoUpdateProfilesTask();
|
||||||
globalState.appController.initLink();
|
appController.initLink();
|
||||||
app?.initShortcuts();
|
app?.initShortcuts();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _autoUpdateProfilesTask() {
|
void _autoUpdateProfilesTask() {
|
||||||
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
|
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
|
||||||
await globalState.appController.autoUpdateProfiles();
|
await appController.autoUpdateProfiles();
|
||||||
_autoUpdateProfilesTask();
|
_autoUpdateProfilesTask();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPlatformState(Widget child) {
|
Widget _buildPlatformState({required Widget child}) {
|
||||||
if (system.isDesktop) {
|
if (system.isDesktop) {
|
||||||
return WindowManager(
|
return WindowManager(
|
||||||
child: TrayManager(
|
child: TrayManager(
|
||||||
@@ -77,16 +78,18 @@ class ApplicationState extends ConsumerState<Application> {
|
|||||||
return AndroidManager(child: TileManager(child: child));
|
return AndroidManager(child: TileManager(child: child));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildState(Widget child) {
|
Widget _buildState({required Widget child}) {
|
||||||
return AppStateManager(
|
return AppStateManager(
|
||||||
child: CoreManager(
|
child: CoreManager(
|
||||||
child: ConnectivityManager(
|
child: ConnectivityManager(
|
||||||
onConnectivityChanged: (results) async {
|
onConnectivityChanged: (results) async {
|
||||||
if (!results.contains(ConnectivityResult.vpn)) {
|
commonPrint.log('connectivityChanged ${results.toString()}');
|
||||||
coreController.closeConnections();
|
appController.updateLocalIp();
|
||||||
|
final hasVpn = results.contains(ConnectivityResult.vpn);
|
||||||
|
if (_preHasVpn == hasVpn) {
|
||||||
|
appController.addCheckIp();
|
||||||
}
|
}
|
||||||
globalState.appController.updateLocalIp();
|
_preHasVpn = hasVpn;
|
||||||
globalState.appController.addCheckIpNumDebounce();
|
|
||||||
},
|
},
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
@@ -94,81 +97,77 @@ class ApplicationState extends ConsumerState<Application> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPlatformApp(Widget child) {
|
Widget _buildPlatformApp({required Widget child}) {
|
||||||
if (system.isDesktop) {
|
if (system.isDesktop) {
|
||||||
return WindowHeaderContainer(child: child);
|
return WindowHeaderContainer(child: child);
|
||||||
}
|
}
|
||||||
return VpnManager(child: child);
|
return VpnManager(child: child);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildApp(Widget child) {
|
Widget _buildApp({required Widget child}) {
|
||||||
return MessageManager(child: ThemeManager(child: child));
|
return StatusManager(child: ThemeManager(child: child));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(context) {
|
Widget build(context) {
|
||||||
return _buildPlatformState(
|
return Consumer(
|
||||||
_buildState(
|
builder: (_, ref, child) {
|
||||||
Consumer(
|
final locale = ref.watch(
|
||||||
builder: (_, ref, child) {
|
appSettingProvider.select((state) => state.locale),
|
||||||
final locale = ref.watch(
|
);
|
||||||
appSettingProvider.select((state) => state.locale),
|
final themeProps = ref.watch(themeSettingProvider);
|
||||||
);
|
return MaterialApp(
|
||||||
final themeProps = ref.watch(themeSettingProvider);
|
debugShowCheckedModeBanner: false,
|
||||||
return MaterialApp(
|
navigatorKey: globalState.navigatorKey,
|
||||||
debugShowCheckedModeBanner: false,
|
localizationsDelegates: const [
|
||||||
navigatorKey: globalState.navigatorKey,
|
AppLocalizations.delegate,
|
||||||
localizationsDelegates: const [
|
GlobalMaterialLocalizations.delegate,
|
||||||
AppLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate,
|
||||||
GlobalCupertinoLocalizations.delegate,
|
],
|
||||||
GlobalWidgetsLocalizations.delegate,
|
builder: (_, child) {
|
||||||
],
|
return AppEnvManager(
|
||||||
builder: (_, child) {
|
child: _buildApp(
|
||||||
return AppEnvManager(
|
child: _buildPlatformState(
|
||||||
child: _buildApp(
|
child: _buildState(child: _buildPlatformApp(child: child!)),
|
||||||
AppSidebarContainer(child: _buildPlatformApp(child!)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
scrollBehavior: BaseScrollBehavior(),
|
|
||||||
title: appName,
|
|
||||||
locale: utils.getLocaleForString(locale),
|
|
||||||
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
|
||||||
themeMode: themeProps.themeMode,
|
|
||||||
theme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
pageTransitionsTheme: _pageTransitionsTheme,
|
|
||||||
colorScheme: _getAppColorScheme(
|
|
||||||
brightness: Brightness.light,
|
|
||||||
primaryColor: themeProps.primaryColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
pageTransitionsTheme: _pageTransitionsTheme,
|
|
||||||
colorScheme: _getAppColorScheme(
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
primaryColor: themeProps.primaryColor,
|
|
||||||
).toPureBlack(themeProps.pureBlack),
|
|
||||||
),
|
|
||||||
home: child!,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const HomePage(),
|
scrollBehavior: BaseScrollBehavior(),
|
||||||
),
|
title: appName,
|
||||||
),
|
locale: utils.getLocaleForString(locale),
|
||||||
|
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||||
|
themeMode: themeProps.themeMode,
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: _pageTransitionsTheme,
|
||||||
|
colorScheme: _getAppColorScheme(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
primaryColor: themeProps.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: _pageTransitionsTheme,
|
||||||
|
colorScheme: _getAppColorScheme(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: themeProps.primaryColor,
|
||||||
|
).toPureBlack(themeProps.pureBlack),
|
||||||
|
),
|
||||||
|
home: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const HomePage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
linkManager.destroy();
|
linkManager.destroy();
|
||||||
_autoUpdateGroupTaskTimer?.cancel();
|
|
||||||
_autoUpdateProfilesTaskTimer?.cancel();
|
_autoUpdateProfilesTaskTimer?.cancel();
|
||||||
await coreController.destroy();
|
await coreController.destroy();
|
||||||
await globalState.appController.savePreferences();
|
await appController.handleExit();
|
||||||
await globalState.appController.handleExit();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import 'package:fl_clash/plugins/service.dart';
|
|
||||||
import 'package:fl_clash/state.dart';
|
|
||||||
|
|
||||||
import 'system.dart';
|
|
||||||
|
|
||||||
class Android {
|
|
||||||
Future<void> init() async {
|
|
||||||
await service?.init();
|
|
||||||
await service?.syncAndroidState(globalState.getAndroidState());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final android = system.isAndroid ? Android() : null;
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import 'package:fl_clash/l10n/l10n.dart';
|
import 'package:fl_clash/l10n/l10n.dart';
|
||||||
|
|
||||||
final appLocalizations = AppLocalizations.current;
|
final appLocalizations = AppLocalizations.current;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
@@ -7,6 +6,9 @@ import 'package:path/path.dart';
|
|||||||
extension ArchiveExt on Archive {
|
extension ArchiveExt on Archive {
|
||||||
void addDirectoryToArchive(String dirPath, String parentPath) {
|
void addDirectoryToArchive(String dirPath, String parentPath) {
|
||||||
final dir = Directory(dirPath);
|
final dir = Directory(dirPath);
|
||||||
|
if (!dir.existsSync()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final entities = dir.listSync(recursive: false);
|
final entities = dir.listSync(recursive: false);
|
||||||
for (final entity in entities) {
|
for (final entity in entities) {
|
||||||
final relativePath = relative(entity.path, from: parentPath);
|
final relativePath = relative(entity.path, from: parentPath);
|
||||||
@@ -15,14 +17,11 @@ extension ArchiveExt on Archive {
|
|||||||
final archiveFile = ArchiveFile(relativePath, data.length, data);
|
final archiveFile = ArchiveFile(relativePath, data.length, data);
|
||||||
addFile(archiveFile);
|
addFile(archiveFile);
|
||||||
}
|
}
|
||||||
// else if (entity is Directory) {
|
|
||||||
// addDirectoryToArchive(entity.path, parentPath);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addTextFile<T>(String name, T raw) {
|
// void addTextFile<T>(String name, T raw) {
|
||||||
final data = json.encode(raw);
|
// final data = json.encode(raw);
|
||||||
addFile(ArchiveFile.string(name, data));
|
// addFile(ArchiveFile.string(name, data));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
185
lib/common/cache.dart
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
|
||||||
|
class LocalImageCacheManager extends CacheManager {
|
||||||
|
static const key = 'ImageCaches';
|
||||||
|
|
||||||
|
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
|
||||||
|
|
||||||
|
factory LocalImageCacheManager() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalImageCacheManager._()
|
||||||
|
: super(Config(key, fileService: _LocalImageCacheFileService()));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LocalImageCacheFileService extends FileService {
|
||||||
|
_LocalImageCacheFileService();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<FileServiceResponse> get(
|
||||||
|
String url, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
}) async {
|
||||||
|
final response = await request.dio.get<ResponseBody>(
|
||||||
|
url,
|
||||||
|
options: Options(headers: headers, responseType: ResponseType.stream),
|
||||||
|
);
|
||||||
|
return _LocalImageResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LocalImageResponse implements FileServiceResponse {
|
||||||
|
_LocalImageResponse(this._response);
|
||||||
|
|
||||||
|
final DateTime _receivedTime = DateTime.now();
|
||||||
|
|
||||||
|
final Response<ResponseBody> _response;
|
||||||
|
|
||||||
|
String? _header(String name) {
|
||||||
|
return _response.headers.value(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get statusCode => _response.statusCode ?? 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<int>> get content =>
|
||||||
|
_response.data!.stream.transform(uint8ListToListIntConverter);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int? get contentLength => _response.data?.contentLength;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get validTill {
|
||||||
|
var ageDuration = const Duration(days: 7);
|
||||||
|
final controlHeader = _header(HttpHeaders.cacheControlHeader);
|
||||||
|
if (controlHeader != null) {
|
||||||
|
final controlSettings = controlHeader.split(',');
|
||||||
|
for (final setting in controlSettings) {
|
||||||
|
final sanitizedSetting = setting.trim().toLowerCase();
|
||||||
|
if (sanitizedSetting == 'no-cache') {
|
||||||
|
ageDuration = Duration.zero;
|
||||||
|
}
|
||||||
|
if (sanitizedSetting.startsWith('max-age=')) {
|
||||||
|
final validSeconds =
|
||||||
|
int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
|
||||||
|
if (validSeconds > 0) {
|
||||||
|
ageDuration = Duration(seconds: validSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ageDuration > const Duration(days: 7)) {
|
||||||
|
return _receivedTime.add(ageDuration);
|
||||||
|
}
|
||||||
|
return _receivedTime.add(const Duration(days: 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get eTag => _header(HttpHeaders.etagHeader);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fileExtension {
|
||||||
|
var fileExtension = '';
|
||||||
|
final contentTypeHeader = _header(HttpHeaders.contentTypeHeader);
|
||||||
|
if (contentTypeHeader != null) {
|
||||||
|
final contentType = ContentType.parse(contentTypeHeader);
|
||||||
|
fileExtension = contentType.fileExtension;
|
||||||
|
}
|
||||||
|
return fileExtension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ContentTypeConverter on ContentType {
|
||||||
|
String get fileExtension => mimeTypes[mimeType] ?? '.$subType';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeTypes = {
|
||||||
|
'application/vnd.android.package-archive': '.apk',
|
||||||
|
'application/epub+zip': '.epub',
|
||||||
|
'application/gzip': '.gz',
|
||||||
|
'application/java-archive': '.jar',
|
||||||
|
'application/json': '.json',
|
||||||
|
'application/ld+json': '.jsonld',
|
||||||
|
'application/msword': '.doc',
|
||||||
|
'application/octet-stream': '.bin',
|
||||||
|
'application/ogg': '.ogx',
|
||||||
|
'application/pdf': '.pdf',
|
||||||
|
'application/php': '.php',
|
||||||
|
'application/rtf': '.rtf',
|
||||||
|
'application/vnd.amazon.ebook': '.azw',
|
||||||
|
'application/vnd.apple.installer+xml': '.mpkg',
|
||||||
|
'application/vnd.mozilla.xul+xml': '.xul',
|
||||||
|
'application/vnd.ms-excel': '.xls',
|
||||||
|
'application/vnd.ms-fontobject': '.eot',
|
||||||
|
'application/vnd.ms-powerpoint': '.ppt',
|
||||||
|
'application/vnd.oasis.opendocument.presentation': '.odp',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
|
||||||
|
'application/vnd.oasis.opendocument.text': '.odt',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||||
|
'.pptx',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||||
|
'.docx',
|
||||||
|
'application/vnd.rar': '.rar',
|
||||||
|
'application/vnd.visio': '.vsd',
|
||||||
|
'application/x-7z-compressed': '.7z',
|
||||||
|
'application/x-abiword': '.abw',
|
||||||
|
'application/x-bzip': '.bz',
|
||||||
|
'application/x-bzip2': '.bz2',
|
||||||
|
'application/x-csh': '.csh',
|
||||||
|
'application/x-freearc': '.arc',
|
||||||
|
'application/x-sh': '.sh',
|
||||||
|
'application/x-shockwave-flash': '.swf',
|
||||||
|
'application/x-tar': '.tar',
|
||||||
|
'application/xhtml+xml': '.xhtml',
|
||||||
|
'application/xml': '.xml',
|
||||||
|
'application/zip': '.zip',
|
||||||
|
'audio/3gpp': '.3gp',
|
||||||
|
'audio/3gpp2': '.3g2',
|
||||||
|
'audio/aac': '.aac',
|
||||||
|
'audio/x-aac': '.aac',
|
||||||
|
'audio/midi': '.midi',
|
||||||
|
'audio/x-midi': '.midi',
|
||||||
|
'audio/x-m4a': '.m4a',
|
||||||
|
'audio/m4a': '.m4a',
|
||||||
|
'audio/mpeg': '.mp3',
|
||||||
|
'audio/ogg': '.oga',
|
||||||
|
'audio/opus': '.opus',
|
||||||
|
'audio/wav': '.wav',
|
||||||
|
'audio/x-wav': '.wav',
|
||||||
|
'audio/webm': '.weba',
|
||||||
|
'font/otf': '.otf',
|
||||||
|
'font/ttf': '.ttf',
|
||||||
|
'font/woff': '.woff',
|
||||||
|
'font/woff2': '.woff2',
|
||||||
|
'image/bmp': '.bmp',
|
||||||
|
'image/gif': '.gif',
|
||||||
|
'image/jpeg': '.jpg',
|
||||||
|
'image/png': '.png',
|
||||||
|
'image/svg+xml': '.svg',
|
||||||
|
'image/tiff': '.tiff',
|
||||||
|
'image/vnd.microsoft.icon': '.ico',
|
||||||
|
'image/webp': '.webp',
|
||||||
|
'text/calendar': '.ics',
|
||||||
|
'text/css': '.css',
|
||||||
|
'text/csv': '.csv',
|
||||||
|
'text/html': '.html',
|
||||||
|
'text/javascript': '.js',
|
||||||
|
'text/plain': '.txt',
|
||||||
|
'text/xml': '.xml',
|
||||||
|
'video/3gpp': '.3gp',
|
||||||
|
'video/3gpp2': '.3g2',
|
||||||
|
'video/mp2t': '.ts',
|
||||||
|
'video/mpeg': '.mpeg',
|
||||||
|
'video/ogg': '.ogv',
|
||||||
|
'video/webm': '.webm',
|
||||||
|
'video/x-msvideo': '.avi',
|
||||||
|
'video/quicktime': '.mov',
|
||||||
|
};
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
export 'android.dart';
|
|
||||||
export 'app_localizations.dart';
|
export 'app_localizations.dart';
|
||||||
|
export 'cache.dart';
|
||||||
export 'color.dart';
|
export 'color.dart';
|
||||||
export 'compute.dart';
|
export 'compute.dart';
|
||||||
export 'constant.dart';
|
export 'constant.dart';
|
||||||
export 'context.dart';
|
export 'context.dart';
|
||||||
export 'converter.dart';
|
export 'converter.dart';
|
||||||
export 'datetime.dart';
|
export 'datetime.dart';
|
||||||
|
export 'file.dart';
|
||||||
export 'fixed.dart';
|
export 'fixed.dart';
|
||||||
export 'function.dart';
|
export 'function.dart';
|
||||||
export 'future.dart';
|
export 'future.dart';
|
||||||
|
export 'hive.dart';
|
||||||
export 'http.dart';
|
export 'http.dart';
|
||||||
export 'icons.dart';
|
export 'icons.dart';
|
||||||
|
export 'indexing.dart';
|
||||||
export 'iterable.dart';
|
export 'iterable.dart';
|
||||||
export 'keyboard.dart';
|
export 'keyboard.dart';
|
||||||
export 'launch.dart';
|
export 'launch.dart';
|
||||||
export 'link.dart';
|
export 'link.dart';
|
||||||
export 'lock.dart';
|
export 'lock.dart';
|
||||||
export 'measure.dart';
|
export 'measure.dart';
|
||||||
|
export 'migration.dart';
|
||||||
export 'mixin.dart';
|
export 'mixin.dart';
|
||||||
export 'navigation.dart';
|
export 'navigation.dart';
|
||||||
export 'navigator.dart';
|
export 'navigator.dart';
|
||||||
@@ -32,9 +36,12 @@ export 'proxy.dart';
|
|||||||
export 'render.dart';
|
export 'render.dart';
|
||||||
export 'request.dart';
|
export 'request.dart';
|
||||||
export 'scroll.dart';
|
export 'scroll.dart';
|
||||||
|
export 'snowflake.dart';
|
||||||
export 'string.dart';
|
export 'string.dart';
|
||||||
export 'system.dart';
|
export 'system.dart';
|
||||||
|
export 'task.dart';
|
||||||
export 'text.dart';
|
export 'text.dart';
|
||||||
export 'tray.dart';
|
export 'tray.dart';
|
||||||
export 'utils.dart';
|
export 'utils.dart';
|
||||||
export 'window.dart';
|
export 'window.dart';
|
||||||
|
export 'yaml.dart';
|
||||||
|
|||||||